From 584ed9ae74b80d6d996b60723c09fd772dbe6ddd Mon Sep 17 00:00:00 2001 From: vizhur Date: Tue, 25 Jun 2019 01:46:55 +0000 Subject: [PATCH] update samples - test --- README.md | 5 - configuration.ipynb | 4 +- configuration.yml | 4 + .../RAPIDS/azure-ml-with-nvidia-rapids.ipynb | 1112 ++--- contrib/datadrift/azure-ml-datadrift.ipynb | 709 +++ contrib/datadrift/azure-ml-datadrift.yml | 8 + contrib/datadrift/score.py | 58 + .../automated-machine-learning/README.md | 20 + .../automl_setup.cmd | 11 + ...uto-ml-classification-bank-marketing.ipynb | 729 +++ .../auto-ml-classification-bank-marketing.yml | 8 + ...-ml-classification-credit-card-fraud.ipynb | 712 +++ ...to-ml-classification-credit-card-fraud.yml | 8 + ...auto-ml-classification-with-deployment.yml | 8 + .../auto-ml-classification-with-onnx.ipynb | 27 +- .../auto-ml-classification-with-onnx.yml | 9 + ...to-ml-classification-with-whitelisting.yml | 8 + .../classification/auto-ml-classification.yml | 8 + .../auto-ml-dataprep-remote-execution.ipynb | 2 +- .../auto-ml-dataprep-remote-execution.yml | 8 + .../dataprep/auto-ml-dataprep.yml | 8 + .../auto-ml-exploring-previous-runs.yml | 8 + .../auto-ml-forecasting-bike-share.ipynb | 214 +- .../auto-ml-forecasting-bike-share.yml | 9 + .../auto-ml-forecasting-energy-demand.ipynb | 165 +- .../auto-ml-forecasting-energy-demand.yml | 10 + ...to-ml-forecasting-orange-juice-sales.ipynb | 23 +- ...auto-ml-forecasting-orange-juice-sales.yml | 9 + ...ssing-data-blacklist-early-termination.yml | 8 + .../auto-ml-model-explanation.yml | 9 + ...auto-ml-regression-concrete-strength.ipynb | 800 +++ .../auto-ml-regression-concrete-strength.yml | 8 + ...o-ml-regression-hardware-performance.ipynb | 800 +++ ...uto-ml-regression-hardware-performance.yml | 8 + .../regression/auto-ml-regression.yml | 9 + .../auto-ml-remote-amlcompute.yml | 8 + .../sample-weight/auto-ml-sample-weight.yml | 8 + .../auto-ml-sparse-data-train-test-split.yml | 8 + .../subsampling/auto-ml-subsampling-local.yml | 8 + .../automl_hdi_local_classification.ipynb | 1229 +++-- .../data-drift/azure-ml-datadrift.ipynb | 1412 +++--- .../model-register-and-deploy.yml | 4 + .../accelerated-models-object-detection.ipynb | 982 ++-- .../accelerated-models-quickstart.ipynb | 1090 ++-- .../accelerated-models-training.ipynb | 1718 +++---- ...ble-app-insights-in-production-service.yml | 4 + ...able-data-collection-for-models-in-aks.yml | 4 + .../onnx/onnx-convert-aml-deploy-tinyyolo.yml | 6 + ...e-facial-expression-recognition-deploy.yml | 9 + .../onnx/onnx-inference-mnist-deploy.yml | 9 + .../onnx-modelzoo-aml-deploy-resnet50.yml | 4 + .../onnx-train-pytorch-aml-deploy-mnist.yml | 5 + .../production-deploy-to-aks-gpu.ipynb | 808 +-- .../production-deploy-to-aks.yml | 8 + ...ster-model-create-image-deploy-service.yml | 8 + .../regression-sklearn-on-amlcompute.ipynb | 6 +- .../regression-sklearn-on-amlcompute.yml | 6 + ...in-local-sklearn-binary-classification.yml | 6 + ...ocal-sklearn-multiclass-classification.yml | 6 + .../explain-local-sklearn-regression.yml | 6 + .../explain-sklearn-raw-features.ipynb | 73 +- .../explain-sklearn-raw-features.yml | 7 + ...ain-run-history-sklearn-classification.yml | 6 + ...explain-run-history-sklearn-regression.yml | 6 + .../aml-pipelines-data-transfer.yml | 5 + .../aml-pipelines-getting-started.yml | 5 + ...aml-pipelines-how-to-use-estimatorstep.yml | 5 + ...es-publish-and-run-using-rest-endpoint.yml | 6 + ...s-with-automated-machine-learning-step.yml | 8 + ...l-pipelines-with-data-dependency-steps.yml | 5 + .../intro-to-pipelines/compare/compare.py | 24 + .../intro-to-pipelines/extract/extract.py | 21 + .../intro-to-pipelines/train/train.py | 22 + ...yc-taxi-data-regression-model-building.yml | 9 + .../pipeline-batch-scoring.yml | 7 + .../pipeline-style-transfer.yml | 6 + .../authentication-in-azure-ml.ipynb | 8 +- .../authentication-in-azureml.ipynb | 57 +- .../images/svc-pr-1.PNG | Bin 38043 -> 99315 bytes .../images/svc-pr-2.PNG | Bin 69230 -> 84045 bytes .../images/svc-pr-3.PNG | Bin 68199 -> 63736 bytes .../distributed-chainer.yml | 5 + .../distributed-cntk-with-custom-docker.yml | 6 + .../distributed-pytorch-with-horovod.yml | 5 + .../distributed-tensorflow-with-horovod.yml | 5 + ...buted-tensorflow-with-parameter-server.yml | 5 + .../export-run-history-to-tensorboard.yml | 9 + .../how-to-use-estimator.ipynb | 2 +- .../how-to-use-estimator.yml | 6 + .../tensorboard/tensorboard.yml | 6 + ...yperparameter-tune-deploy-with-chainer.yml | 7 + ...-hyperparameter-tune-deploy-with-keras.yml | 8 + ...erparameter-tune-deploy-with-pytorch.ipynb | 1486 +++--- ...rparameter-tune-deploy-with-tensorflow.yml | 8 + how-to-use-azureml/training/README.md | 5 +- .../training/logging-api/logging-api.ipynb | 2 +- .../training/logging-api/logging-api.yml | 8 + .../training/manage-runs/manage-runs.yml | 4 + ...yperparameter-tune-deploy-with-sklearn.yml | 6 + .../train-on-amlcompute.yml | 6 + .../train-on-local/train-on-local.yml | 7 + .../train-on-remote-vm.ipynb | 10 +- .../train-on-remote-vm/train-on-remote-vm.yml | 8 + .../train-within-notebook.yml | 8 + .../using-environments/using-environments.yml | 4 + .../deploy-model/deploy-model.ipynb | 638 +-- .../deploy-model/deploy-model.yml | 8 + .../train-and-deploy-pytorch.ipynb | 956 ++-- .../train-and-deploy-pytorch.yml | 8 + .../train-local/train-local.ipynb | 490 +- .../using-mlflow/train-local/train-local.yml | 7 + .../train-remote/train-remote.ipynb | 630 +-- .../train-remote/train-remote.yml | 4 + .../datasets/datasets-tutorial.ipynb | 854 ++-- model-deployment/README.md | 0 setup-environment/NBSETUP.md | 95 + setup-environment/configuration.ipynb | 291 ++ setup-environment/configuration.yml | 4 + training/README.md | 0 .../img-classification-part1-training.yml | 7 + tutorials/img-classification-part2-deploy.yml | 6 + tutorials/regression-part1-data-prep.yml | 5 + tutorials/regression-part2-automated-ml.yml | 10 + tutorials/sklearn_mnist_model.pkl | Bin 63684 -> 63684 bytes work-with-data/README.md | 9 + work-with-data/dataprep/README.md | 255 + .../new-york-taxi/new-york-taxi.ipynb | 514 ++ .../new-york-taxi_scale-out.ipynb | 135 + .../dataprep/data/adls-dpreptestfiles.crt | 45 + .../dataprep/data/chicago-aldermen-2015.csv | 54 + work-with-data/dataprep/data/crime-dirty.csv | 15 + work-with-data/dataprep/data/crime-full.csv | 1001 ++++ work-with-data/dataprep/data/crime-spring.csv | 11 + work-with-data/dataprep/data/crime-winter.csv | 11 + work-with-data/dataprep/data/crime.dprep | 204 + work-with-data/dataprep/data/crime.parquet | Bin 0 -> 3607 bytes work-with-data/dataprep/data/crime.txt | 10 + work-with-data/dataprep/data/crime.xlsx | Bin 0 -> 16109 bytes work-with-data/dataprep/data/crime.zip | Bin 0 -> 3685 bytes .../dataprep/data/crime_duplicate_headers.csv | 12 + .../dataprep/data/crime_fixed_width_file.txt | 10 + .../data/crime_multiple_separators.csv | 11 + .../dataprep/data/crime_partfiles/_SUCCESS | 0 ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 914 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 921 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 930 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 953 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 923 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 887 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 971 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 759 +++ work-with-data/dataprep/data/json.json | 1306 +++++ work-with-data/dataprep/data/large_dflow.json | 4415 +++++++++++++++++ work-with-data/dataprep/data/map_func.py | 4 + .../dataprep/data/median_income.csv | 251 + .../data/median_income_transformed.csv | 251 + work-with-data/dataprep/data/parquet.parquet | Bin 0 -> 3091 bytes ...7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet | Bin 0 -> 6078 bytes ...7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet | Bin 0 -> 5083 bytes work-with-data/dataprep/data/secrets.dprep | 63 + work-with-data/dataprep/data/stream-path.csv | 11 + .../add-column-using-expression.ipynb | 360 ++ .../append-columns-and-rows.ipynb | 251 + .../dataprep/how-to-guides/assertions.ipynb | 133 + .../how-to-guides/auto-read-file.ipynb | 189 + .../dataprep/how-to-guides/cache.ipynb | 194 + .../how-to-guides/column-manipulations.ipynb | 563 +++ .../column-type-transforms.ipynb | 473 ++ .../custom-python-transforms.ipynb | 231 + .../how-to-guides/data-ingestion.ipynb | 978 ++++ .../dataprep/how-to-guides/data-profile.ipynb | 179 + .../dataprep/how-to-guides/datastore.ipynb | 215 + .../derive-column-by-example.ipynb | 187 + .../how-to-guides/external-references.ipynb | 118 + .../dataprep/how-to-guides/filtering.ipynb | 220 + .../dataprep/how-to-guides/fuzzy-group.ipynb | 211 + .../how-to-guides/impute-missing-values.ipynb | 147 + .../dataprep/how-to-guides/join.ipynb | 265 + .../how-to-guides/label-encoder.ipynb | 168 + .../how-to-guides/min-max-scaler.ipynb | 239 + .../how-to-guides/one-hot-encoder.ipynb | 179 + .../how-to-guides/open-save-dataflows.ipynb | 171 + .../quantile-transformation.ipynb | 91 + .../dataprep/how-to-guides/random-split.ipynb | 145 + ...replace-datasource-replace-reference.ipynb | 130 + .../how-to-guides/replace-fill-error.ipynb | 239 + .../dataprep/how-to-guides/secrets.ipynb | 140 + .../how-to-guides/semantic-types.ipynb | 164 + .../split-column-by-example.ipynb | 220 + .../how-to-guides/subsetting-sampling.ipynb | 217 + .../dataprep/how-to-guides/summarize.ipynb | 590 +++ .../working-with-file-streams.ipynb | 192 + .../dataprep/how-to-guides/writing-data.ipynb | 183 + .../getting-started/getting-started.ipynb | 441 ++ work-with-data/datasets/README.md | 159 + .../datasets-tutorial/datasets-tutorial.ipynb | 376 ++ .../train-dataset/Titanic.csv | 892 ++++ .../datasets-tutorial/train-dataset/train.py | 37 + 198 files changed, 37667 insertions(+), 6903 deletions(-) create mode 100644 configuration.yml create mode 100644 contrib/datadrift/azure-ml-datadrift.ipynb create mode 100644 contrib/datadrift/azure-ml-datadrift.yml create mode 100644 contrib/datadrift/score.py create mode 100644 how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml create mode 100644 how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml create mode 100644 how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml create mode 100644 how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml create mode 100644 how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml create mode 100644 how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml create mode 100644 how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml create mode 100644 how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml create mode 100644 how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml create mode 100644 how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml create mode 100644 how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml create mode 100644 how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml create mode 100644 how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml create mode 100644 how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml create mode 100644 how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml create mode 100644 how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml create mode 100644 how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml create mode 100644 how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml create mode 100644 how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/compare/compare.py create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/extract/extract.py create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/train/train.py create mode 100644 how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-chainer/distributed-chainer.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-cntk-with-custom-docker/distributed-cntk-with-custom-docker.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-pytorch-with-horovod/distributed-pytorch-with-horovod.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-tensorflow-with-horovod/distributed-tensorflow-with-horovod.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-tensorflow-with-parameter-server/distributed-tensorflow-with-parameter-server.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/export-run-history-to-tensorboard/export-run-history-to-tensorboard.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/how-to-use-estimator/how-to-use-estimator.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/tensorboard/tensorboard.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/train-hyperparameter-tune-deploy-with-chainer/train-hyperparameter-tune-deploy-with-chainer.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/train-hyperparameter-tune-deploy-with-keras/train-hyperparameter-tune-deploy-with-keras.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/train-hyperparameter-tune-deploy-with-tensorflow/train-hyperparameter-tune-deploy-with-tensorflow.yml create mode 100644 how-to-use-azureml/training/logging-api/logging-api.yml create mode 100644 how-to-use-azureml/training/manage-runs/manage-runs.yml create mode 100644 how-to-use-azureml/training/train-hyperparameter-tune-deploy-with-sklearn/train-hyperparameter-tune-deploy-with-sklearn.yml create mode 100644 how-to-use-azureml/training/train-on-amlcompute/train-on-amlcompute.yml create mode 100644 how-to-use-azureml/training/train-on-local/train-on-local.yml create mode 100644 how-to-use-azureml/training/train-on-remote-vm/train-on-remote-vm.yml create mode 100644 how-to-use-azureml/training/train-within-notebook/train-within-notebook.yml create mode 100644 how-to-use-azureml/training/using-environments/using-environments.yml create mode 100644 how-to-use-azureml/using-mlflow/deploy-model/deploy-model.yml create mode 100644 how-to-use-azureml/using-mlflow/train-deploy-pytorch/train-and-deploy-pytorch.yml create mode 100644 how-to-use-azureml/using-mlflow/train-local/train-local.yml create mode 100644 how-to-use-azureml/using-mlflow/train-remote/train-remote.yml create mode 100644 model-deployment/README.md create mode 100644 setup-environment/NBSETUP.md create mode 100644 setup-environment/configuration.ipynb create mode 100644 setup-environment/configuration.yml create mode 100644 training/README.md create mode 100644 tutorials/img-classification-part1-training.yml create mode 100644 tutorials/img-classification-part2-deploy.yml create mode 100644 tutorials/regression-part1-data-prep.yml create mode 100644 tutorials/regression-part2-automated-ml.yml create mode 100644 work-with-data/README.md create mode 100644 work-with-data/dataprep/README.md create mode 100644 work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb create mode 100644 work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb create mode 100644 work-with-data/dataprep/data/adls-dpreptestfiles.crt create mode 100644 work-with-data/dataprep/data/chicago-aldermen-2015.csv create mode 100644 work-with-data/dataprep/data/crime-dirty.csv create mode 100644 work-with-data/dataprep/data/crime-full.csv create mode 100644 work-with-data/dataprep/data/crime-spring.csv create mode 100644 work-with-data/dataprep/data/crime-winter.csv create mode 100644 work-with-data/dataprep/data/crime.dprep create mode 100644 work-with-data/dataprep/data/crime.parquet create mode 100644 work-with-data/dataprep/data/crime.txt create mode 100644 work-with-data/dataprep/data/crime.xlsx create mode 100644 work-with-data/dataprep/data/crime.zip create mode 100644 work-with-data/dataprep/data/crime_duplicate_headers.csv create mode 100644 work-with-data/dataprep/data/crime_fixed_width_file.txt create mode 100644 work-with-data/dataprep/data/crime_multiple_separators.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/_SUCCESS create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00000-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00001-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00002-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00003-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00004-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00005-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00006-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00007-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/json.json create mode 100644 work-with-data/dataprep/data/large_dflow.json create mode 100644 work-with-data/dataprep/data/map_func.py create mode 100644 work-with-data/dataprep/data/median_income.csv create mode 100644 work-with-data/dataprep/data/median_income_transformed.csv create mode 100644 work-with-data/dataprep/data/parquet.parquet create mode 100644 work-with-data/dataprep/data/parquet_dataset/Arrest=false/part-00000-34f8a7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet create mode 100644 work-with-data/dataprep/data/parquet_dataset/Arrest=true/part-00000-34f8a7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet create mode 100644 work-with-data/dataprep/data/secrets.dprep create mode 100644 work-with-data/dataprep/data/stream-path.csv create mode 100644 work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/assertions.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/auto-read-file.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/cache.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/column-manipulations.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/data-ingestion.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/data-profile.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/datastore.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/external-references.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/filtering.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/join.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/label-encoder.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/random-split.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/secrets.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/semantic-types.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/summarize.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/writing-data.ipynb create mode 100644 work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb create mode 100644 work-with-data/datasets/README.md create mode 100644 work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb create mode 100644 work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv create mode 100644 work-with-data/datasets/datasets-tutorial/train-dataset/train.py diff --git a/README.md b/README.md index 2d297a8e..37845282 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,6 @@ This repository contains example notebooks demonstrating the [Azure Machine Lear ![Azure ML workflow](https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/master/articles/machine-learning/service/media/overview-what-is-azure-ml/aml.png) -## News - - * [Try Azure Machine Learning with MLflow](./how-to-use-azureml/using-mlflow) - ## Quick installation ```sh pip install azureml-sdk @@ -56,7 +52,6 @@ The [How to use Azure ML](./how-to-use-azureml) folder contains specific example Visit following repos to see projects contributed by Azure ML users: - - [AMLSamples](https://github.com/Azure/AMLSamples) Number of end-to-end examples, including face recognition, predictive maintenance, customer churn and sentiment analysis. - [Fine tune natural language processing models using Azure Machine Learning service](https://github.com/Microsoft/AzureML-BERT) - [Fashion MNIST with Azure ML SDK](https://github.com/amynic/azureml-sdk-fashion) diff --git a/configuration.ipynb b/configuration.ipynb index 5db62bab..bd273ee0 100644 --- a/configuration.ipynb +++ b/configuration.ipynb @@ -103,7 +103,7 @@ "source": [ "import azureml.core\n", "\n", - "print(\"This notebook was created using version 1.0.43 of the Azure ML SDK\")\n", + "print(\"This notebook was created using version of the Azure ML SDK\")\n", "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" ] }, @@ -380,4 +380,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/configuration.yml b/configuration.yml new file mode 100644 index 00000000..4b1aed29 --- /dev/null +++ b/configuration.yml @@ -0,0 +1,4 @@ +name: configuration +dependencies: +- pip: + - azureml-sdk diff --git a/contrib/RAPIDS/azure-ml-with-nvidia-rapids.ipynb b/contrib/RAPIDS/azure-ml-with-nvidia-rapids.ipynb index fc13e0fa..97fecf56 100644 --- a/contrib/RAPIDS/azure-ml-with-nvidia-rapids.ipynb +++ b/contrib/RAPIDS/azure-ml-with-nvidia-rapids.ipynb @@ -1,559 +1,559 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# NVIDIA RAPIDS in Azure Machine Learning" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The [RAPIDS](https://www.developer.nvidia.com/rapids) suite of software libraries from NVIDIA enables the execution of end-to-end data science and analytics pipelines entirely on GPUs. In many machine learning projects, a significant portion of the model training time is spent in setting up the data; this stage of the process is known as Extraction, Transformation and Loading, or ETL. By using the DataFrame API for ETL and GPU-capable ML algorithms in RAPIDS, data preparation and training models can be done in GPU-accelerated end-to-end pipelines without incurring serialization costs between the pipeline stages. This notebook demonstrates how to use NVIDIA RAPIDS to prepare data and train model in Azure.\n", - " \n", - "In this notebook, we will do the following:\n", - " \n", - "* Create an Azure Machine Learning Workspace\n", - "* Create an AMLCompute target\n", - "* Use a script to process our data and train a model\n", - "* Obtain the data required to run this sample\n", - "* Create an AML run configuration to launch a machine learning job\n", - "* Run the script to prepare data for training and train the model\n", - " \n", - "Prerequisites:\n", - "* An Azure subscription to create a Machine Learning Workspace\n", - "* Familiarity with the Azure ML SDK (refer to [notebook samples](https://github.com/Azure/MachineLearningNotebooks))\n", - "* A Jupyter notebook environment with Azure Machine Learning SDK installed. Refer to instructions to [setup the environment](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-environment#local)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Verify if Azure ML SDK is installed" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import azureml.core\n", - "print(\"SDK version:\", azureml.core.VERSION)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from azureml.core import Workspace, Experiment\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "from azureml.core.compute import AmlCompute, ComputeTarget\n", - "from azureml.data.data_reference import DataReference\n", - "from azureml.core.runconfig import RunConfiguration\n", - "from azureml.core import ScriptRunConfig\n", - "from azureml.widgets import RunDetails" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Azure ML Workspace" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following step is optional if you already have a workspace. If you want to use an existing workspace, then\n", - "skip this workspace creation step and move on to the next step to load the workspace.\n", - " \n", - "Important: in the code cell below, be sure to set the correct values for the subscription_id, \n", - "resource_group, workspace_name, region before executing this code cell." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subscription_id = os.environ.get(\"SUBSCRIPTION_ID\", \"\")\n", - "resource_group = os.environ.get(\"RESOURCE_GROUP\", \"\")\n", - "workspace_name = os.environ.get(\"WORKSPACE_NAME\", \"\")\n", - "workspace_region = os.environ.get(\"WORKSPACE_REGION\", \"\")\n", - "\n", - "ws = Workspace.create(workspace_name, subscription_id=subscription_id, resource_group=resource_group, location=workspace_region)\n", - "\n", - "# write config to a local directory for future use\n", - "ws.write_config()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load existing Workspace" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "# if a locally-saved configuration file for the workspace is not available, use the following to load workspace\n", - "# ws = Workspace(subscription_id=subscription_id, resource_group=resource_group, workspace_name=workspace_name)\n", - "print('Workspace name: ' + ws.name, \n", - " 'Azure region: ' + ws.location, \n", - " 'Subscription id: ' + ws.subscription_id, \n", - " 'Resource group: ' + ws.resource_group, sep = '\\n')\n", - "\n", - "scripts_folder = \"scripts_folder\"\n", - "\n", - "if not os.path.isdir(scripts_folder):\n", - " os.mkdir(scripts_folder)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create AML Compute Target" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Because NVIDIA RAPIDS requires P40 or V100 GPUs, the user needs to specify compute targets from one of [NC_v3](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series), [NC_v2](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv2-series), [ND](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#nd-series) or [ND_v2](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ndv2-series-preview) virtual machine types in Azure; these are the families of virtual machines in Azure that are provisioned with these GPUs.\n", - " \n", - "Pick one of the supported VM SKUs based on the number of GPUs you want to use for ETL and training in RAPIDS.\n", - " \n", - "The script in this notebook is implemented for single-machine scenarios. An example supporting multiple nodes will be published later." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gpu_cluster_name = \"gpucluster\"\n", - "\n", - "if gpu_cluster_name in ws.compute_targets:\n", - " gpu_cluster = ws.compute_targets[gpu_cluster_name]\n", - " if gpu_cluster and type(gpu_cluster) is AmlCompute:\n", - " print('found compute target. just use it. ' + gpu_cluster_name)\n", - "else:\n", - " print(\"creating new cluster\")\n", - " # vm_size parameter below could be modified to one of the RAPIDS-supported VM types\n", - " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"Standard_NC6s_v2\", min_nodes=1, max_nodes = 1)\n", - "\n", - " # create the cluster\n", - " gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, provisioning_config)\n", - " gpu_cluster.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Script to process data and train model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The _process_data.py_ script used in the step below is a slightly modified implementation of [RAPIDS E2E example](https://github.com/rapidsai/notebooks/blob/master/mortgage/E2E.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# copy process_data.py into the script folder\n", - "import shutil\n", - "shutil.copy('./process_data.py', os.path.join(scripts_folder, 'process_data.py'))\n", - "\n", - "with open(os.path.join(scripts_folder, './process_data.py'), 'r') as process_data_script:\n", - " print(process_data_script.read())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Data required to run this sample" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample uses [Fannie Mae's Single-Family Loan Performance Data](http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html). Once you obtain access to the data, you will need to make this data available in an [Azure Machine Learning Datastore](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-access-data), for use in this sample. The following code shows how to do that." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Downloading Data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Important: Python package progressbar2 is necessary to run the following cell. If it is not available in your environment where this notebook is running, please install it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import tarfile\n", - "import hashlib\n", - "from urllib.request import urlretrieve\n", - "from progressbar import ProgressBar\n", - "\n", - "def validate_downloaded_data(path):\n", - " if(os.path.isdir(path) and os.path.exists(path + '//names.csv')) :\n", - " if(os.path.isdir(path + '//acq' ) and len(os.listdir(path + '//acq')) == 8):\n", - " if(os.path.isdir(path + '//perf' ) and len(os.listdir(path + '//perf')) == 11):\n", - " print(\"Data has been downloaded and decompressed at: {0}\".format(path))\n", - " return True\n", - " print(\"Data has not been downloaded and decompressed\")\n", - " return False\n", - "\n", - "def show_progress(count, block_size, total_size):\n", - " global pbar\n", - " global processed\n", - " \n", - " if count == 0:\n", - " pbar = ProgressBar(maxval=total_size)\n", - " processed = 0\n", - " \n", - " processed += block_size\n", - " processed = min(processed,total_size)\n", - " pbar.update(processed)\n", - "\n", - " \n", - "def download_file(fileroot):\n", - " filename = fileroot + '.tgz'\n", - " if(not os.path.exists(filename) or hashlib.md5(open(filename, 'rb').read()).hexdigest() != '82dd47135053303e9526c2d5c43befd5' ):\n", - " url_format = 'http://rapidsai-data.s3-website.us-east-2.amazonaws.com/notebook-mortgage-data/{0}.tgz'\n", - " url = url_format.format(fileroot)\n", - " print(\"...Downloading file :{0}\".format(filename))\n", - " urlretrieve(url, filename,show_progress)\n", - " pbar.finish()\n", - " print(\"...File :{0} finished downloading\".format(filename))\n", - " else:\n", - " print(\"...File :{0} has been downloaded already\".format(filename))\n", - " return filename\n", - "\n", - "def decompress_file(filename,path):\n", - " tar = tarfile.open(filename)\n", - " print(\"...Getting information from {0} about files to decompress\".format(filename))\n", - " members = tar.getmembers()\n", - " numFiles = len(members)\n", - " so_far = 0\n", - " for member_info in members:\n", - " tar.extract(member_info,path=path)\n", - " show_progress(so_far, 1, numFiles)\n", - " so_far += 1\n", - " pbar.finish()\n", - " print(\"...All {0} files have been decompressed\".format(numFiles))\n", - " tar.close()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fileroot = 'mortgage_2000-2001'\n", - "path = '.\\\\{0}'.format(fileroot)\n", - "pbar = None\n", - "processed = 0\n", - "\n", - "if(not validate_downloaded_data(path)):\n", - " print(\"Downloading and Decompressing Input Data\")\n", - " filename = download_file(fileroot)\n", - " decompress_file(filename,path)\n", - " print(\"Input Data has been Downloaded and Decompressed\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Uploading Data to Workspace" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds = ws.get_default_datastore()\n", - "\n", - "# download and uncompress data in a local directory before uploading to data store\n", - "# directory specified in src_dir parameter below should have the acq, perf directories with data and names.csv file\n", - "ds.upload(src_dir=path, target_path=fileroot, overwrite=True, show_progress=True)\n", - "\n", - "# data already uploaded to the datastore\n", - "data_ref = DataReference(data_reference_name='data', datastore=ds, path_on_datastore=fileroot)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create AML run configuration to launch a machine learning job" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "RunConfiguration is used to submit jobs to Azure Machine Learning service. When creating RunConfiguration for a job, users can either \n", - "1. specify a Docker image with prebuilt conda environment and use it without any modifications to run the job, or \n", - "2. specify a Docker image as the base image and conda or pip packages as dependnecies to let AML build a new Docker image with a conda environment containing specified dependencies to use in the job\n", - "\n", - "The second option is the recommended option in AML. \n", - "The following steps have code for both options. You can pick the one that is more appropriate for your requirements. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Specify prebuilt conda environment" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following code shows how to use an existing image from [Docker Hub](https://hub.docker.com/r/rapidsai/rapidsai/) that has a prebuilt conda environment named 'rapids' when creating a RunConfiguration. Note that this conda environment does not include azureml-defaults package that is required for using AML functionality like metrics tracking, model management etc. This package is automatically installed when you use 'Specify package dependencies' option and that is why it is the recommended option to create RunConfiguraiton in AML." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "run_config = RunConfiguration()\n", - "run_config.framework = 'python'\n", - "run_config.environment.python.user_managed_dependencies = True\n", - "run_config.environment.python.interpreter_path = '/conda/envs/rapids/bin/python'\n", - "run_config.target = gpu_cluster_name\n", - "run_config.environment.docker.enabled = True\n", - "run_config.environment.docker.gpu_support = True\n", - "run_config.environment.docker.base_image = \"rapidsai/rapidsai:cuda9.2-runtime-ubuntu18.04\"\n", - "# run_config.environment.docker.base_image_registry.address = '' # not required if the base_image is in Docker hub\n", - "# run_config.environment.docker.base_image_registry.username = '' # needed only for private images\n", - "# run_config.environment.docker.base_image_registry.password = '' # needed only for private images\n", - "run_config.environment.spark.precache_packages = False\n", - "run_config.data_references={'data':data_ref.to_config()}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Specify package dependencies" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following code shows how to list package dependencies in a conda environment definition file (rapids.yml) when creating a RunConfiguration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# cd = CondaDependencies(conda_dependencies_file_path='rapids.yml')\n", - "# run_config = RunConfiguration(conda_dependencies=cd)\n", - "# run_config.framework = 'python'\n", - "# run_config.target = gpu_cluster_name\n", - "# run_config.environment.docker.enabled = True\n", - "# run_config.environment.docker.gpu_support = True\n", - "# run_config.environment.docker.base_image = \"\"\n", - "# run_config.environment.docker.base_image_registry.address = '' # not required if the base_image is in Docker hub\n", - "# run_config.environment.docker.base_image_registry.username = '' # needed only for private images\n", - "# run_config.environment.docker.base_image_registry.password = '' # needed only for private images\n", - "# run_config.environment.spark.precache_packages = False\n", - "# run_config.data_references={'data':data_ref.to_config()}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Wrapper function to submit Azure Machine Learning experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# parameter cpu_predictor indicates if training should be done on CPU. If set to true, GPUs are used *only* for ETL and *not* for training\n", - "# parameter num_gpu indicates number of GPUs to use among the GPUs available in the VM for ETL and if cpu_predictor is false, for training as well \n", - "def run_rapids_experiment(cpu_training, gpu_count, part_count):\n", - " # any value between 1-4 is allowed here depending the type of VMs available in gpu_cluster\n", - " if gpu_count not in [1, 2, 3, 4]:\n", - " raise Exception('Value specified for the number of GPUs to use {0} is invalid'.format(gpu_count))\n", - "\n", - " # following data partition mapping is empirical (specific to GPUs used and current data partitioning scheme) and may need to be tweaked\n", - " max_gpu_count_data_partition_mapping = {1: 3, 2: 4, 3: 6, 4: 8}\n", - " \n", - " if part_count > max_gpu_count_data_partition_mapping[gpu_count]:\n", - " print(\"Too many partitions for the number of GPUs, exceeding memory threshold\")\n", - " \n", - " if part_count > 11:\n", - " print(\"Warning: Maximum number of partitions available is 11\")\n", - " part_count = 11\n", - " \n", - " end_year = 2000\n", - " \n", - " if part_count > 4:\n", - " end_year = 2001 # use more data with more GPUs\n", - "\n", - " src = ScriptRunConfig(source_directory=scripts_folder, \n", - " script='process_data.py', \n", - " arguments = ['--num_gpu', gpu_count, '--data_dir', str(data_ref),\n", - " '--part_count', part_count, '--end_year', end_year,\n", - " '--cpu_predictor', cpu_training\n", - " ],\n", - " run_config=run_config\n", - " )\n", - "\n", - " exp = Experiment(ws, 'rapidstest')\n", - " run = exp.submit(config=src)\n", - " RunDetails(run).show()\n", - " return run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit experiment (ETL & training on GPU)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cpu_predictor = False\n", - "# the value for num_gpu should be less than or equal to the number of GPUs available in the VM\n", - "num_gpu = 1\n", - "data_part_count = 1\n", - "# train using CPU, use GPU for both ETL and training\n", - "run = run_rapids_experiment(cpu_predictor, num_gpu, data_part_count)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit experiment (ETL on GPU, training on CPU)\n", - "\n", - "To observe performance difference between GPU-accelerated RAPIDS based training with CPU-only training, set 'cpu_predictor' predictor to 'True' and rerun the experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cpu_predictor = True\n", - "# the value for num_gpu should be less than or equal to the number of GPUs available in the VM\n", - "num_gpu = 1\n", - "data_part_count = 1\n", - "# train using CPU, use GPU for ETL\n", - "run = run_rapids_experiment(cpu_predictor, num_gpu, data_part_count)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Delete cluster" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# delete the cluster\n", - "# gpu_cluster.delete()" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "ksivas" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NVIDIA RAPIDS in Azure Machine Learning" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The [RAPIDS](https://www.developer.nvidia.com/rapids) suite of software libraries from NVIDIA enables the execution of end-to-end data science and analytics pipelines entirely on GPUs. In many machine learning projects, a significant portion of the model training time is spent in setting up the data; this stage of the process is known as Extraction, Transformation and Loading, or ETL. By using the DataFrame API for ETL\u00c3\u201a\u00c2\u00a0and GPU-capable ML algorithms in RAPIDS, data preparation and training models can be done in GPU-accelerated end-to-end pipelines without incurring serialization costs between the pipeline stages. This notebook demonstrates how to use NVIDIA RAPIDS to prepare data and train model\u00c2\u00a0in Azure.\n", + " \n", + "In this notebook, we will do the following:\n", + " \n", + "* Create an Azure Machine Learning Workspace\n", + "* Create an AMLCompute target\n", + "* Use a script to process our data and train a model\n", + "* Obtain the data required to run this sample\n", + "* Create an AML run configuration to launch a machine learning job\n", + "* Run the script to prepare data for training and train the model\n", + " \n", + "Prerequisites:\n", + "* An Azure subscription to create a Machine Learning Workspace\n", + "* Familiarity with the Azure ML SDK (refer to [notebook samples](https://github.com/Azure/MachineLearningNotebooks))\n", + "* A Jupyter notebook environment with Azure Machine Learning SDK installed. Refer to instructions to [setup the environment](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-environment#local)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Verify if Azure ML SDK is installed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.core\n", + "print(\"SDK version:\", azureml.core.VERSION)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "from azureml.core.compute import AmlCompute, ComputeTarget\n", + "from azureml.data.data_reference import DataReference\n", + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core import ScriptRunConfig\n", + "from azureml.widgets import RunDetails" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Azure ML Workspace" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following step is optional if you already have a workspace. If you want to use an existing workspace, then\n", + "skip this workspace creation step and move on to the next step to load the workspace.\n", + " \n", + "Important: in the code cell below, be sure to set the correct values for the subscription_id, \n", + "resource_group, workspace_name, region before executing this code cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subscription_id = os.environ.get(\"SUBSCRIPTION_ID\", \"\")\n", + "resource_group = os.environ.get(\"RESOURCE_GROUP\", \"\")\n", + "workspace_name = os.environ.get(\"WORKSPACE_NAME\", \"\")\n", + "workspace_region = os.environ.get(\"WORKSPACE_REGION\", \"\")\n", + "\n", + "ws = Workspace.create(workspace_name, subscription_id=subscription_id, resource_group=resource_group, location=workspace_region)\n", + "\n", + "# write config to a local directory for future use\n", + "ws.write_config()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load existing Workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "# if a locally-saved configuration file for the workspace is not available, use the following to load workspace\n", + "# ws = Workspace(subscription_id=subscription_id, resource_group=resource_group, workspace_name=workspace_name)\n", + "print('Workspace name: ' + ws.name, \n", + " 'Azure region: ' + ws.location, \n", + " 'Subscription id: ' + ws.subscription_id, \n", + " 'Resource group: ' + ws.resource_group, sep = '\\n')\n", + "\n", + "scripts_folder = \"scripts_folder\"\n", + "\n", + "if not os.path.isdir(scripts_folder):\n", + " os.mkdir(scripts_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create AML Compute Target" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because NVIDIA RAPIDS requires P40 or V100 GPUs, the user needs to specify compute targets from one of [NC_v3](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series), [NC_v2](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv2-series), [ND](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#nd-series) or [ND_v2](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ndv2-series-preview) virtual machine types in Azure; these are the families of virtual machines in Azure that are provisioned with these GPUs.\n", + " \n", + "Pick one of the supported VM SKUs based on the number of GPUs you want to use for ETL and training in RAPIDS.\n", + " \n", + "The script in this notebook is implemented for single-machine scenarios. An example supporting multiple nodes will be published later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "gpu_cluster_name = \"gpucluster\"\n", + "\n", + "if gpu_cluster_name in ws.compute_targets:\n", + " gpu_cluster = ws.compute_targets[gpu_cluster_name]\n", + " if gpu_cluster and type(gpu_cluster) is AmlCompute:\n", + " print('found compute target. just use it. ' + gpu_cluster_name)\n", + "else:\n", + " print(\"creating new cluster\")\n", + " # vm_size parameter below could be modified to one of the RAPIDS-supported VM types\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"Standard_NC6s_v2\", min_nodes=1, max_nodes = 1)\n", + "\n", + " # create the cluster\n", + " gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, provisioning_config)\n", + " gpu_cluster.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Script to process data and train model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The _process_data.py_ script used in the step below is a slightly modified implementation of [RAPIDS E2E example](https://github.com/rapidsai/notebooks/blob/master/mortgage/E2E.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# copy process_data.py into the script folder\n", + "import shutil\n", + "shutil.copy('./process_data.py', os.path.join(scripts_folder, 'process_data.py'))\n", + "\n", + "with open(os.path.join(scripts_folder, './process_data.py'), 'r') as process_data_script:\n", + " print(process_data_script.read())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Data required to run this sample" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sample uses [Fannie Mae's Single-Family Loan Performance Data](http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html). Once you obtain access to the data, you will need to make this data available in an [Azure Machine Learning Datastore](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-access-data), for use in this sample. The following code shows how to do that." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Downloading Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Important: Python package progressbar2 is necessary to run the following cell. If it is not available in your environment where this notebook is running, please install it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tarfile\n", + "import hashlib\n", + "from urllib.request import urlretrieve\n", + "from progressbar import ProgressBar\n", + "\n", + "def validate_downloaded_data(path):\n", + " if(os.path.isdir(path) and os.path.exists(path + '//names.csv')) :\n", + " if(os.path.isdir(path + '//acq' ) and len(os.listdir(path + '//acq')) == 8):\n", + " if(os.path.isdir(path + '//perf' ) and len(os.listdir(path + '//perf')) == 11):\n", + " print(\"Data has been downloaded and decompressed at: {0}\".format(path))\n", + " return True\n", + " print(\"Data has not been downloaded and decompressed\")\n", + " return False\n", + "\n", + "def show_progress(count, block_size, total_size):\n", + " global pbar\n", + " global processed\n", + " \n", + " if count == 0:\n", + " pbar = ProgressBar(maxval=total_size)\n", + " processed = 0\n", + " \n", + " processed += block_size\n", + " processed = min(processed,total_size)\n", + " pbar.update(processed)\n", + "\n", + " \n", + "def download_file(fileroot):\n", + " filename = fileroot + '.tgz'\n", + " if(not os.path.exists(filename) or hashlib.md5(open(filename, 'rb').read()).hexdigest() != '82dd47135053303e9526c2d5c43befd5' ):\n", + " url_format = 'http://rapidsai-data.s3-website.us-east-2.amazonaws.com/notebook-mortgage-data/{0}.tgz'\n", + " url = url_format.format(fileroot)\n", + " print(\"...Downloading file :{0}\".format(filename))\n", + " urlretrieve(url, filename,show_progress)\n", + " pbar.finish()\n", + " print(\"...File :{0} finished downloading\".format(filename))\n", + " else:\n", + " print(\"...File :{0} has been downloaded already\".format(filename))\n", + " return filename\n", + "\n", + "def decompress_file(filename,path):\n", + " tar = tarfile.open(filename)\n", + " print(\"...Getting information from {0} about files to decompress\".format(filename))\n", + " members = tar.getmembers()\n", + " numFiles = len(members)\n", + " so_far = 0\n", + " for member_info in members:\n", + " tar.extract(member_info,path=path)\n", + " show_progress(so_far, 1, numFiles)\n", + " so_far += 1\n", + " pbar.finish()\n", + " print(\"...All {0} files have been decompressed\".format(numFiles))\n", + " tar.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fileroot = 'mortgage_2000-2001'\n", + "path = '.\\\\{0}'.format(fileroot)\n", + "pbar = None\n", + "processed = 0\n", + "\n", + "if(not validate_downloaded_data(path)):\n", + " print(\"Downloading and Decompressing Input Data\")\n", + " filename = download_file(fileroot)\n", + " decompress_file(filename,path)\n", + " print(\"Input Data has been Downloaded and Decompressed\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uploading Data to Workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds = ws.get_default_datastore()\n", + "\n", + "# download and uncompress data in a local directory before uploading to data store\n", + "# directory specified in src_dir parameter below should have the acq, perf directories with data and names.csv file\n", + "ds.upload(src_dir=path, target_path=fileroot, overwrite=True, show_progress=True)\n", + "\n", + "# data already uploaded to the datastore\n", + "data_ref = DataReference(data_reference_name='data', datastore=ds, path_on_datastore=fileroot)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create AML run configuration to launch a machine learning job" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "RunConfiguration is used to submit jobs to Azure Machine Learning service. When creating RunConfiguration for a job, users can either \n", + "1. specify a Docker image with prebuilt conda environment and use it without any modifications to run the job, or \n", + "2. specify a Docker image as the base image and conda or pip packages as dependnecies to let AML build a new Docker image with a conda environment containing specified dependencies to use in the job\n", + "\n", + "The second option is the recommended option in AML. \n", + "The following steps have code for both options. You can pick the one that is more appropriate for your requirements. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Specify prebuilt conda environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following code shows how to use an existing image from [Docker Hub](https://hub.docker.com/r/rapidsai/rapidsai/) that has a prebuilt conda environment named 'rapids' when creating a RunConfiguration. Note that this conda environment does not include azureml-defaults package that is required for using AML functionality like metrics tracking, model management etc. This package is automatically installed when you use 'Specify package dependencies' option and that is why it is the recommended option to create RunConfiguraiton in AML." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "run_config = RunConfiguration()\n", + "run_config.framework = 'python'\n", + "run_config.environment.python.user_managed_dependencies = True\n", + "run_config.environment.python.interpreter_path = '/conda/envs/rapids/bin/python'\n", + "run_config.target = gpu_cluster_name\n", + "run_config.environment.docker.enabled = True\n", + "run_config.environment.docker.gpu_support = True\n", + "run_config.environment.docker.base_image = \"rapidsai/rapidsai:cuda9.2-runtime-ubuntu18.04\"\n", + "# run_config.environment.docker.base_image_registry.address = '' # not required if the base_image is in Docker hub\n", + "# run_config.environment.docker.base_image_registry.username = '' # needed only for private images\n", + "# run_config.environment.docker.base_image_registry.password = '' # needed only for private images\n", + "run_config.environment.spark.precache_packages = False\n", + "run_config.data_references={'data':data_ref.to_config()}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Specify package dependencies" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following code shows how to list package dependencies in a conda environment definition file (rapids.yml) when creating a RunConfiguration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# cd = CondaDependencies(conda_dependencies_file_path='rapids.yml')\n", + "# run_config = RunConfiguration(conda_dependencies=cd)\n", + "# run_config.framework = 'python'\n", + "# run_config.target = gpu_cluster_name\n", + "# run_config.environment.docker.enabled = True\n", + "# run_config.environment.docker.gpu_support = True\n", + "# run_config.environment.docker.base_image = \"\"\n", + "# run_config.environment.docker.base_image_registry.address = '' # not required if the base_image is in Docker hub\n", + "# run_config.environment.docker.base_image_registry.username = '' # needed only for private images\n", + "# run_config.environment.docker.base_image_registry.password = '' # needed only for private images\n", + "# run_config.environment.spark.precache_packages = False\n", + "# run_config.data_references={'data':data_ref.to_config()}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Wrapper function to submit Azure Machine Learning experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# parameter cpu_predictor indicates if training should be done on CPU. If set to true, GPUs are used *only* for ETL and *not* for training\n", + "# parameter num_gpu indicates number of GPUs to use among the GPUs available in the VM for ETL and if cpu_predictor is false, for training as well \n", + "def run_rapids_experiment(cpu_training, gpu_count, part_count):\n", + " # any value between 1-4 is allowed here depending the type of VMs available in gpu_cluster\n", + " if gpu_count not in [1, 2, 3, 4]:\n", + " raise Exception('Value specified for the number of GPUs to use {0} is invalid'.format(gpu_count))\n", + "\n", + " # following data partition mapping is empirical (specific to GPUs used and current data partitioning scheme) and may need to be tweaked\n", + " max_gpu_count_data_partition_mapping = {1: 3, 2: 4, 3: 6, 4: 8}\n", + " \n", + " if part_count > max_gpu_count_data_partition_mapping[gpu_count]:\n", + " print(\"Too many partitions for the number of GPUs, exceeding memory threshold\")\n", + " \n", + " if part_count > 11:\n", + " print(\"Warning: Maximum number of partitions available is 11\")\n", + " part_count = 11\n", + " \n", + " end_year = 2000\n", + " \n", + " if part_count > 4:\n", + " end_year = 2001 # use more data with more GPUs\n", + "\n", + " src = ScriptRunConfig(source_directory=scripts_folder, \n", + " script='process_data.py', \n", + " arguments = ['--num_gpu', gpu_count, '--data_dir', str(data_ref),\n", + " '--part_count', part_count, '--end_year', end_year,\n", + " '--cpu_predictor', cpu_training\n", + " ],\n", + " run_config=run_config\n", + " )\n", + "\n", + " exp = Experiment(ws, 'rapidstest')\n", + " run = exp.submit(config=src)\n", + " RunDetails(run).show()\n", + " return run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit experiment (ETL & training on GPU)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cpu_predictor = False\n", + "# the value for num_gpu should be less than or equal to the number of GPUs available in the VM\n", + "num_gpu = 1\n", + "data_part_count = 1\n", + "# train using CPU, use GPU for both ETL and training\n", + "run = run_rapids_experiment(cpu_predictor, num_gpu, data_part_count)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit experiment (ETL on GPU, training on CPU)\n", + "\n", + "To observe performance difference between GPU-accelerated RAPIDS based training with CPU-only training, set 'cpu_predictor' predictor to 'True' and rerun the experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cpu_predictor = True\n", + "# the value for num_gpu should be less than or equal to the number of GPUs available in the VM\n", + "num_gpu = 1\n", + "data_part_count = 1\n", + "# train using CPU, use GPU for ETL\n", + "run = run_rapids_experiment(cpu_predictor, num_gpu, data_part_count)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete cluster" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# delete the cluster\n", + "# gpu_cluster.delete()" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "ksivas" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/contrib/datadrift/azure-ml-datadrift.ipynb b/contrib/datadrift/azure-ml-datadrift.ipynb new file mode 100644 index 00000000..7922bc77 --- /dev/null +++ b/contrib/datadrift/azure-ml-datadrift.ipynb @@ -0,0 +1,709 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Track Data Drift between Training and Inference Data in Production \n", + "\n", + "With this notebook, you will learn how to enable the DataDrift service to automatically track and determine whether your inference data is drifting from the data your model was initially trained on. The DataDrift service provides metrics and visualizations to help stakeholders identify which specific features cause the concept drift to occur.\n", + "\n", + "Please email driftfeedback@microsoft.com with any issues. A member from the DataDrift team will respond shortly. \n", + "\n", + "The DataDrift Public Preview API can be found [here](https://docs.microsoft.com/en-us/python/api/azureml-contrib-datadrift/?view=azure-ml-py). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/contrib/datadrift/azureml-datadrift.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prerequisites and Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install the DataDrift package\n", + "\n", + "Install the azureml-contrib-datadrift, azureml-contrib-opendatasets and lightgbm packages before running this notebook.\n", + "```\n", + "pip install azureml-contrib-datadrift\n", + "pip install azureml-contrib-datasets\n", + "pip install lightgbm\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "import time\n", + "from datetime import datetime, timedelta\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import requests\n", + "from azureml.contrib.datadrift import DataDriftDetector, AlertConfiguration\n", + "from azureml.contrib.opendatasets import NoaaIsdWeather\n", + "from azureml.core import Dataset, Workspace, Run\n", + "from azureml.core.compute import AksCompute, ComputeTarget\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.image import ContainerImage\n", + "from azureml.core.model import Model\n", + "from azureml.core.webservice import Webservice, AksWebservice\n", + "from azureml.widgets import RunDetails\n", + "from sklearn.externals import joblib\n", + "from sklearn.model_selection import train_test_split\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up Configuraton and Create Azure ML Workspace\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration notebook](../../../configuration.ipynb) first if you haven't already to establish your connection to the AzureML Workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Please type in your initials/alias. The prefix is prepended to the names of resources created by this notebook. \n", + "prefix = \"dd\"\n", + "\n", + "# NOTE: Please do not change the model_name, as it's required by the score.py file\n", + "model_name = \"driftmodel\"\n", + "image_name = \"{}driftimage\".format(prefix)\n", + "service_name = \"{}driftservice\".format(prefix)\n", + "\n", + "# optionally, set email address to receive an email alert for DataDrift\n", + "email_address = \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate Train/Testing Data\n", + "\n", + "For this demo, we will use NOAA weather data from [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). You may replace this step with your own dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "usaf_list = ['725724', '722149', '723090', '722159', '723910', '720279',\n", + " '725513', '725254', '726430', '720381', '723074', '726682',\n", + " '725486', '727883', '723177', '722075', '723086', '724053',\n", + " '725070', '722073', '726060', '725224', '725260', '724520',\n", + " '720305', '724020', '726510', '725126', '722523', '703333',\n", + " '722249', '722728', '725483', '722972', '724975', '742079',\n", + " '727468', '722193', '725624', '722030', '726380', '720309',\n", + " '722071', '720326', '725415', '724504', '725665', '725424',\n", + " '725066']\n", + "\n", + "columns = ['usaf', 'wban', 'datetime', 'latitude', 'longitude', 'elevation', 'windAngle', 'windSpeed', 'temperature', 'stationName', 'p_k']\n", + "\n", + "\n", + "def enrich_weather_noaa_data(noaa_df):\n", + " hours_in_day = 23\n", + " week_in_year = 52\n", + " \n", + " noaa_df[\"hour\"] = noaa_df[\"datetime\"].dt.hour\n", + " noaa_df[\"weekofyear\"] = noaa_df[\"datetime\"].dt.week\n", + " \n", + " noaa_df[\"sine_weekofyear\"] = noaa_df['datetime'].transform(lambda x: np.sin((2*np.pi*x.dt.week-1)/week_in_year))\n", + " noaa_df[\"cosine_weekofyear\"] = noaa_df['datetime'].transform(lambda x: np.cos((2*np.pi*x.dt.week-1)/week_in_year))\n", + "\n", + " noaa_df[\"sine_hourofday\"] = noaa_df['datetime'].transform(lambda x: np.sin(2*np.pi*x.dt.hour/hours_in_day))\n", + " noaa_df[\"cosine_hourofday\"] = noaa_df['datetime'].transform(lambda x: np.cos(2*np.pi*x.dt.hour/hours_in_day))\n", + " \n", + " return noaa_df\n", + "\n", + "def add_window_col(input_df):\n", + " shift_interval = pd.Timedelta('-7 days') # your X days interval\n", + " df_shifted = input_df.copy()\n", + " df_shifted['datetime'] = df_shifted['datetime'] - shift_interval\n", + " df_shifted.drop(list(input_df.columns.difference(['datetime', 'usaf', 'wban', 'sine_hourofday', 'temperature'])), axis=1, inplace=True)\n", + "\n", + " # merge, keeping only observations where -1 lag is present\n", + " df2 = pd.merge(input_df,\n", + " df_shifted,\n", + " on=['datetime', 'usaf', 'wban', 'sine_hourofday'],\n", + " how='inner', # use 'left' to keep observations without lags\n", + " suffixes=['', '-7'])\n", + " return df2\n", + "\n", + "def get_noaa_data(start_time, end_time, cols, station_list):\n", + " isd = NoaaIsdWeather(start_time, end_time, cols=cols)\n", + " # Read into Pandas data frame.\n", + " noaa_df = isd.to_pandas_dataframe()\n", + " noaa_df = noaa_df.rename(columns={\"stationName\": \"station_name\"})\n", + " \n", + " df_filtered = noaa_df[noaa_df[\"usaf\"].isin(station_list)]\n", + " df_filtered.reset_index(drop=True)\n", + " \n", + " # Enrich with time features\n", + " df_enriched = enrich_weather_noaa_data(df_filtered)\n", + " \n", + " return df_enriched\n", + "\n", + "def get_featurized_noaa_df(start_time, end_time, cols, station_list):\n", + " df_1 = get_noaa_data(start_time - timedelta(days=7), start_time - timedelta(seconds=1), cols, station_list)\n", + " df_2 = get_noaa_data(start_time, end_time, cols, station_list)\n", + " noaa_df = pd.concat([df_1, df_2])\n", + " \n", + " print(\"Adding window feature\")\n", + " df_window = add_window_col(noaa_df)\n", + " \n", + " cat_columns = df_window.dtypes == object\n", + " cat_columns = cat_columns[cat_columns == True]\n", + " \n", + " print(\"Encoding categorical columns\")\n", + " df_encoded = pd.get_dummies(df_window, columns=cat_columns.keys().tolist())\n", + " \n", + " print(\"Dropping unnecessary columns\")\n", + " df_featurized = df_encoded.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna().drop_duplicates()\n", + " \n", + " return df_featurized" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Train model on Jan 1 - 14, 2009 data\n", + "df = get_featurized_noaa_df(datetime(2009, 1, 1), datetime(2009, 1, 14, 23, 59, 59), columns, usaf_list)\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "label = \"temperature\"\n", + "x_df = df.drop(label, axis=1)\n", + "y_df = df[[label]]\n", + "x_train, x_test, y_train, y_test = train_test_split(df, y_df, test_size=0.2, random_state=223)\n", + "print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)\n", + "\n", + "training_dir = 'outputs/training'\n", + "training_file = \"training.csv\"\n", + "\n", + "# Generate training dataframe to register as Training Dataset\n", + "os.makedirs(training_dir, exist_ok=True)\n", + "training_df = pd.merge(x_train.drop(label, axis=1), y_train, left_index=True, right_index=True)\n", + "training_df.to_csv(training_dir + \"/\" + training_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create/Register Training Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset_name = \"dataset\"\n", + "name_suffix = datetime.utcnow().strftime(\"%Y-%m-%d-%H-%M-%S\")\n", + "snapshot_name = \"snapshot-{}\".format(name_suffix)\n", + "\n", + "dstore = ws.get_default_datastore()\n", + "dstore.upload(training_dir, \"data/training\", show_progress=True)\n", + "dpath = dstore.path(\"data/training/training.csv\")\n", + "trainingDataset = Dataset.auto_read_files(dpath, include_path=True)\n", + "trainingDataset = trainingDataset.register(workspace=ws, name=dataset_name, description=\"dset\", exist_ok=True)\n", + "\n", + "trainingDataSnapshot = trainingDataset.create_snapshot(snapshot_name=snapshot_name, compute_target=None, create_data_snapshot=True)\n", + "datasets = [(Dataset.Scenario.TRAINING, trainingDataSnapshot)]\n", + "print(\"dataset registration done.\\n\")\n", + "datasets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train and Save Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import lightgbm as lgb\n", + "\n", + "train = lgb.Dataset(data=x_train, \n", + " label=y_train)\n", + "\n", + "test = lgb.Dataset(data=x_test, \n", + " label=y_test,\n", + " reference=train)\n", + "\n", + "params = {'learning_rate' : 0.1,\n", + " 'boosting' : 'gbdt',\n", + " 'metric' : 'rmse',\n", + " 'feature_fraction' : 1,\n", + " 'bagging_fraction' : 1,\n", + " 'max_depth': 6,\n", + " 'num_leaves' : 31,\n", + " 'objective' : 'regression',\n", + " 'bagging_freq' : 1,\n", + " \"verbose\": -1,\n", + " 'min_data_per_leaf': 100}\n", + "\n", + "model = lgb.train(params, \n", + " num_boost_round=500,\n", + " train_set=train,\n", + " valid_sets=[train, test],\n", + " verbose_eval=50,\n", + " early_stopping_rounds=25)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_file = 'outputs/{}.pkl'.format(model_name)\n", + "\n", + "os.makedirs('outputs', exist_ok=True)\n", + "joblib.dump(model, model_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Register Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = Model.register(model_path=model_file,\n", + " model_name=model_name,\n", + " workspace=ws,\n", + " datasets=datasets)\n", + "\n", + "print(model_name, image_name, service_name, model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy Model To AKS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prepare Environment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn', 'joblib', 'lightgbm', 'pandas'],\n", + " pip_packages=['azureml-monitoring', 'azureml-sdk[automl]'])\n", + "\n", + "with open(\"myenv.yml\",\"w\") as f:\n", + " f.write(myenv.serialize_to_string())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Image creation may take up to 15 minutes.\n", + "\n", + "image_name = image_name + str(model.version)\n", + "\n", + "if not image_name in ws.images:\n", + " # Use the score.py defined in this directory as the execution script\n", + " # NOTE: The Model Data Collector must be enabled in the execution script for DataDrift to run correctly\n", + " image_config = ContainerImage.image_configuration(execution_script=\"score.py\",\n", + " runtime=\"python\",\n", + " conda_file=\"myenv.yml\",\n", + " description=\"Image with weather dataset model\")\n", + " image = ContainerImage.create(name=image_name,\n", + " models=[model],\n", + " image_config=image_config,\n", + " workspace=ws)\n", + "\n", + " image.wait_for_creation(show_output=True)\n", + "else:\n", + " image = ws.images[image_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Compute Target" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_name = 'dd-demo-e2e'\n", + "prov_config = AksCompute.provisioning_configuration()\n", + "\n", + "if not aks_name in ws.compute_targets:\n", + " aks_target = ComputeTarget.create(workspace=ws,\n", + " name=aks_name,\n", + " provisioning_configuration=prov_config)\n", + "\n", + " aks_target.wait_for_completion(show_output=True)\n", + " print(aks_target.provisioning_state)\n", + " print(aks_target.provisioning_errors)\n", + "else:\n", + " aks_target=ws.compute_targets[aks_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploy Service" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_service_name = service_name\n", + "\n", + "if not aks_service_name in ws.webservices:\n", + " aks_config = AksWebservice.deploy_configuration(collect_model_data=True, enable_app_insights=True)\n", + " aks_service = Webservice.deploy_from_image(workspace=ws,\n", + " name=aks_service_name,\n", + " image=image,\n", + " deployment_config=aks_config,\n", + " deployment_target=aks_target)\n", + " aks_service.wait_for_deployment(show_output=True)\n", + " print(aks_service.state)\n", + "else:\n", + " aks_service = ws.webservices[aks_service_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run DataDrift Analysis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Send Scoring Data to Service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download Scoring Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Score Model on March 15, 2016 data\n", + "scoring_df = get_noaa_data(datetime(2016, 3, 15) - timedelta(days=7), datetime(2016, 3, 16), columns, usaf_list)\n", + "# Add the window feature column\n", + "scoring_df = add_window_col(scoring_df)\n", + "\n", + "# Drop features not used by the model\n", + "print(\"Dropping unnecessary columns\")\n", + "scoring_df = scoring_df.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna()\n", + "scoring_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# One Hot Encode the scoring dataset to match the training dataset schema\n", + "columns_dict = model.datasets[\"training\"][0].get_profile().columns\n", + "extra_cols = ('Path', 'Column1')\n", + "for k in extra_cols:\n", + " columns_dict.pop(k, None)\n", + "training_columns = list(columns_dict.keys())\n", + "\n", + "categorical_columns = scoring_df.dtypes == object\n", + "categorical_columns = categorical_columns[categorical_columns == True]\n", + "\n", + "test_df = pd.get_dummies(scoring_df[categorical_columns.keys().tolist()])\n", + "encoded_df = scoring_df.join(test_df)\n", + "\n", + "# Populate missing OHE columns with 0 values to match traning dataset schema\n", + "difference = list(set(training_columns) - set(encoded_df.columns.tolist()))\n", + "for col in difference:\n", + " encoded_df[col] = 0\n", + "encoded_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Serialize dataframe to list of row dictionaries\n", + "encoded_dict = encoded_df.to_dict('records')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit Scoring Data to Service" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "# retreive the API keys. AML generates two keys.\n", + "key1, key2 = aks_service.get_keys()\n", + "\n", + "total_count = len(scoring_df)\n", + "i = 0\n", + "load = []\n", + "for row in encoded_dict:\n", + " load.append(row)\n", + " i = i + 1\n", + " if i % 100 == 0:\n", + " payload = json.dumps({\"data\": load})\n", + " \n", + " # construct raw HTTP request and send to the service\n", + " payload_binary = bytes(payload,encoding = 'utf8')\n", + " headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n", + " resp = requests.post(aks_service.scoring_uri, payload_binary, headers=headers)\n", + " \n", + " print(\"prediction:\", resp.content, \"Progress: {}/{}\".format(i, total_count)) \n", + "\n", + " load = []\n", + " time.sleep(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure DataDrift" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "services = [service_name]\n", + "start = datetime.now() - timedelta(days=2)\n", + "end = datetime(year=2020, month=1, day=22, hour=15, minute=16)\n", + "feature_list = ['usaf', 'wban', 'latitude', 'longitude', 'station_name', 'p_k', 'sine_hourofday', 'cosine_hourofday', 'temperature-7']\n", + "alert_config = AlertConfiguration([email_address]) if email_address else None\n", + "\n", + "# there will be an exception indicating using get() method if DataDrift object already exist\n", + "try:\n", + " datadrift = DataDriftDetector.create(ws, model.name, model.version, services, frequency=\"Day\", alert_config=alert_config)\n", + "except KeyError:\n", + " datadrift = DataDriftDetector.get(ws, model.name, model.version)\n", + " \n", + "print(\"Details of DataDrift Object:\\n{}\".format(datadrift))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run an Adhoc DataDriftDetector Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "target_date = datetime.today()\n", + "run = datadrift.run(target_date, services, feature_list=feature_list, create_compute_target=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(ws, datadrift._id)\n", + "dd_run = Run(experiment=exp, run_id=run)\n", + "RunDetails(dd_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get Drift Analysis Results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(dd_run.get_children())\n", + "for child in children:\n", + " child.wait_for_completion()\n", + "\n", + "drift_metrics = datadrift.get_output(start_time=start, end_time=end)\n", + "drift_metrics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Show all drift figures, one per serivice.\n", + "# If setting with_details is False (by default), only drift will be shown; if it's True, all details will be shown.\n", + "\n", + "drift_figures = datadrift.show(with_details=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Enable DataDrift Schedule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datadrift.enable_schedule()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "rafarmah" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/contrib/datadrift/azure-ml-datadrift.yml b/contrib/datadrift/azure-ml-datadrift.yml new file mode 100644 index 00000000..85e23c7a --- /dev/null +++ b/contrib/datadrift/azure-ml-datadrift.yml @@ -0,0 +1,8 @@ +name: azure-ml-datadrift +dependencies: +- pip: + - azureml-sdk + - azureml-contrib-datadrift + - azureml-contrib-opendatasets + - lightgbm + - azureml-widgets diff --git a/contrib/datadrift/score.py b/contrib/datadrift/score.py new file mode 100644 index 00000000..cda62f8b --- /dev/null +++ b/contrib/datadrift/score.py @@ -0,0 +1,58 @@ +import pickle +import json +import numpy +import azureml.train.automl +from sklearn.externals import joblib +from sklearn.linear_model import Ridge +from azureml.core.model import Model +from azureml.core.run import Run +from azureml.monitoring import ModelDataCollector +import time +import pandas as pd + + +def init(): + global model, inputs_dc, prediction_dc, feature_names, categorical_features + + print("Model is initialized" + time.strftime("%H:%M:%S")) + model_path = Model.get_model_path(model_name="driftmodel") + model = joblib.load(model_path) + + feature_names = ["usaf", "wban", "latitude", "longitude", "station_name", "p_k", + "sine_weekofyear", "cosine_weekofyear", "sine_hourofday", "cosine_hourofday", + "temperature-7"] + + categorical_features = ["usaf", "wban", "p_k", "station_name"] + + inputs_dc = ModelDataCollector(model_name="driftmodel", + identifier="inputs", + feature_names=feature_names) + + prediction_dc = ModelDataCollector("driftmodel", + identifier="predictions", + feature_names=["temperature"]) + + +def run(raw_data): + global inputs_dc, prediction_dc + + try: + data = json.loads(raw_data)["data"] + data = pd.DataFrame(data) + + # Remove the categorical features as the model expects OHE values + input_data = data.drop(categorical_features, axis=1) + + result = model.predict(input_data) + + # Collect the non-OHE dataframe + collected_df = data[feature_names] + + inputs_dc.collect(collected_df.values) + prediction_dc.collect(result) + return result.tolist() + except Exception as e: + error = str(e) + + print(error + time.strftime("%H:%M:%S")) + return error diff --git a/how-to-use-azureml/automated-machine-learning/README.md b/how-to-use-azureml/automated-machine-learning/README.md index af7ec6af..d31a3729 100644 --- a/how-to-use-azureml/automated-machine-learning/README.md +++ b/how-to-use-azureml/automated-machine-learning/README.md @@ -179,6 +179,26 @@ jupyter notebook - Simple example of using automated ML for classification with ONNX models - Uses local compute for training +- [auto-ml-bank-marketing-subscribers-with-deployment.ipynb](bank-marketing-subscribers-with-deployment/auto-ml-bank-marketing-with-deployment.ipynb) + - Dataset: UCI's [bank marketing dataset](https://www.kaggle.com/janiobachmann/bank-marketing-dataset) + - Simple example of using automated ML for classification to predict term deposit subscriptions for a bank + - Uses azure compute for training + +- [auto-ml-creditcard-with-deployment.ipynb](credit-card-fraud-detection-with-deployment/auto-ml-creditcard-with-deployment.ipynb) + - Dataset: Kaggle's [credit card fraud detection dataset](https://www.kaggle.com/mlg-ulb/creditcardfraud) + - Simple example of using automated ML for classification to fraudulent credit card transactions + - Uses azure compute for training + +- [auto-ml-hardware-performance-with-deployment.ipynb](hardware-performance-prediction-with-deployment/auto-ml-hardware-performance-with-deployment.ipynb) + - Dataset: UCI's [computer hardware dataset](https://archive.ics.uci.edu/ml/datasets/Computer+Hardware) + - Simple example of using automated ML for regression to predict the performance of certain combinations of hardware components + - Uses azure compute for training + +- [auto-ml-concrete-strength-with-deployment.ipynb](predicting-concrete-strength-with-deployment/auto-ml-concrete-strength-with-deployment.ipynb) + - Dataset: UCI's [concrete compressive strength dataset](https://www.kaggle.com/pavanraj159/concrete-compressive-strength-data-set) + - Simple example of using automated ML for regression to predict the strength predict the compressive strength of concrete based off of different ingredient combinations and quantities of those ingredients + - Uses azure compute for training + See [Configure automated machine learning experiments](https://docs.microsoft.com/azure/machine-learning/service/how-to-configure-auto-train) to learn how more about the the settings and features available for automated machine learning experiments. diff --git a/how-to-use-azureml/automated-machine-learning/automl_setup.cmd b/how-to-use-azureml/automated-machine-learning/automl_setup.cmd index 2ef80420..510e2ed5 100644 --- a/how-to-use-azureml/automated-machine-learning/automl_setup.cmd +++ b/how-to-use-azureml/automated-machine-learning/automl_setup.cmd @@ -9,6 +9,8 @@ IF "%automl_env_file%"=="" SET automl_env_file="automl_env.yml" IF NOT EXIST %automl_env_file% GOTO YmlMissing +IF "%CONDA_EXE%"=="" GOTO CondaMissing + call conda activate %conda_env_name% 2>nul: if not errorlevel 1 ( @@ -42,6 +44,15 @@ IF NOT "%options%"=="nolaunch" ( goto End +:CondaMissing +echo Please run this script from an Anaconda Prompt window. +echo You can start an Anaconda Prompt window by +echo typing Anaconda Prompt on the Start menu. +echo If you don't see the Anaconda Prompt app, install Miniconda. +echo If you are running an older version of Miniconda or Anaconda, +echo you can upgrade using the command: conda update conda +goto End + :YmlMissing echo File %automl_env_file% not found. diff --git a/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb new file mode 100644 index 00000000..efaccf23 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb @@ -0,0 +1,729 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Classification with Deployment using a Bank Marketing Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Deploy](#Deploy)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "In this example we use the UCI Bank Marketing dataset to showcase how you can use AutoML for a classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if the client will subscribe to a term deposit with the bank.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an experiment using an existing workspace.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Register the model.\n", + "6. Create a container image.\n", + "7. Create an Azure Container Instance (ACI) service.\n", + "8. Test the ACI service." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import os\n", + "from sklearn import datasets\n", + "import azureml.dataprep as dprep\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.train.automl.run import AutoMLRun" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for experiment\n", + "experiment_name = 'automl-classification-bmarketing'\n", + "# project folder\n", + "project_folder = './sample_projects/automl-classification-bankmarketing'\n", + "\n", + "experiment=Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data() script to be utilized in azure compute. To do this first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_Run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy','py-xgboost<=0.80'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here we create the script to be run in azure comput for loading the data, we load the bank marketing dataset into X_train and y_train. Next X_train and y_train is returned for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_train.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X_train = dflow.drop_columns(columns=['y'])\n", + "y_train = dflow.keep_columns(columns=['y'], validate_column_exists=True)\n", + "dflow.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], Multi-class targets.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 2,\n", + " \"primary_metric\": 'AUC_weighted',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'classification',\n", + " debug_log = 'automl_errors.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while.\n", + "In this example, we specify `show_output = True` to print currently running iterations to the console." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Widget for Monitoring Runs\n", + "\n", + "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "\n", + "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploy\n", + "\n", + "### Retrieve the Best Model\n", + "\n", + "Below we select the best pipeline from our iterations. The `get_output` method on `automl_classifier` returns the best run and the fitted model for the last invocation. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register the Fitted Model for Deployment\n", + "If neither `metric` nor `iteration` are specified in the `register_model` call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model trained on bank marketing data to predict if a client will subscribe to a term deposit'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn','py-xgboost<=0.80'],\n", + " pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"bmData\", 'type': \"automl_classification\"},\n", + " description = \"Image for automl classification sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"bmData\", 'type': \"automl_classification\"}, \n", + " description = 'sample service for Automl Classification')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-bankmarketing'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test\n", + "\n", + "Now that the model is trained split our data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load the bank marketing datasets.\n", + "from sklearn.datasets import load_diabetes\n", + "from sklearn.model_selection import train_test_split\n", + "from numpy import array" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_validate.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X_test = dflow.drop_columns(columns=['y'])\n", + "y_test = dflow.keep_columns(columns=['y'], validate_column_exists=True)\n", + "dflow.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred = fitted_model.predict(X_test)\n", + "actual = array(y_test)\n", + "actual = actual[:,0]\n", + "print(y_pred.shape, \" \", actual.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "test_pred = plt.scatter(actual, y_pred, color='b')\n", + "test_test = plt.scatter(actual, actual, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Bank Marketing dataset is made available under the Creative Commons (CCO: Public Domain) License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: https://creativecommons.org/publicdomain/zero/1.0/ and is available at: https://www.kaggle.com/janiobachmann/bank-marketing-dataset .\n", + "\n", + "_**Acknowledgements**_\n", + "This data set is originally available within the UCI Machine Learning Database: https://archive.ics.uci.edu/ml/datasets/bank+marketing\n", + "\n", + "[Moro et al., 2014] S. Moro, P. Cortez and P. Rita. A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems, Elsevier, 62:22-31, June 2014" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml new file mode 100644 index 00000000..a46c905b --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-bank-marketing +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb new file mode 100644 index 00000000..9bd1342a --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb @@ -0,0 +1,712 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Classification with Deployment using Credit Card Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Deploy](#Deploy)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "In this example we use the associated credit card dataset to showcase how you can use AutoML for a simple classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if a creditcard transaction is or is not considered a fraudulent charge.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an experiment using an existing workspace.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Register the model.\n", + "6. Create a container image.\n", + "7. Create an Azure Container Instance (ACI) service.\n", + "8. Test the ACI service." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import pandas as pd\n", + "import os\n", + "from sklearn.model_selection import train_test_split\n", + "import azureml.dataprep as dprep\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.train.automl.run import AutoMLRun" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for experiment\n", + "experiment_name = 'automl-classification-ccard'\n", + "# project folder\n", + "project_folder = './sample_projects/automl-classification-creditcard'\n", + "\n", + "experiment=Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy','py-xgboost<=0.80'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here create the script to be run in azure compute for loading the data, load the credit card dataset into cards and store the Class column (y) in the y variable and store the remaining data in the x variable. Next split the data using train_test_split and return X_train and y_train for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/creditcard.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X = dflow.drop_columns(columns=['Class'])\n", + "y = dflow.keep_columns(columns=['Class'], validate_column_exists=True)\n", + "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", + "y_train, y_test = y.random_split(percentage=0.8, seed=223)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], Multi-class targets.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 2,\n", + " \"primary_metric\": 'average_precision_score_weighted',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'classification',\n", + " debug_log = 'automl_errors_20190417.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while.\n", + "In this example, we specify `show_output = True` to print currently running iterations to the console." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Widget for Monitoring Runs\n", + "\n", + "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "\n", + "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploy\n", + "\n", + "### Retrieve the Best Model\n", + "\n", + "Below we select the best pipeline from our iterations. The `get_output` method on `automl_classifier` returns the best run and the fitted model for the last invocation. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register the Fitted Model for Deployment\n", + "If neither `metric` nor `iteration` are specified in the `register_model` call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn','py-xgboost<=0.80'],\n", + " pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"cards\", 'type': \"automl_classification\"},\n", + " description = \"Image for automl classification sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"cards\", 'type': \"automl_classification\"}, \n", + " description = 'sample service for Automl Classification')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-creditcard'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test\n", + "\n", + "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Randomly select and test\n", + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred = fitted_model.predict(X_test)\n", + "y_pred" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Randomly select and test\n", + "# Plot outputs\n", + "%matplotlib notebook\n", + "test_pred = plt.scatter(y_test, y_pred, color='b')\n", + "test_test = plt.scatter(y_test, y_test, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Credit Card fraud Detection dataset is made available under the Open Database License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: http://opendatacommons.org/licenses/dbcl/1.0/ and is available at: https://www.kaggle.com/mlg-ulb/creditcardfraud\n", + "\n", + "\n", + "The dataset has been collected and analysed during a research collaboration of Worldline and the Machine Learning Group (http://mlg.ulb.ac.be) of ULB (Universit\u00c3\u00a9 Libre de Bruxelles) on big data mining and fraud detection. More details on current and past projects on related topics are available on https://www.researchgate.net/project/Fraud-detection-5 and the page of the DefeatFraud project\n", + "Please cite the following works: \n", + "\u00e2\u20ac\u00a2\tAndrea Dal Pozzolo, Olivier Caelen, Reid A. Johnson and Gianluca Bontempi. Calibrating Probability with Undersampling for Unbalanced Classification. In Symposium on Computational Intelligence and Data Mining (CIDM), IEEE, 2015\n", + "\u00e2\u20ac\u00a2\tDal Pozzolo, Andrea; Caelen, Olivier; Le Borgne, Yann-Ael; Waterschoot, Serge; Bontempi, Gianluca. Learned lessons in credit card fraud detection from a practitioner perspective, Expert systems with applications,41,10,4915-4928,2014, Pergamon\n", + "\u00e2\u20ac\u00a2\tDal Pozzolo, Andrea; Boracchi, Giacomo; Caelen, Olivier; Alippi, Cesare; Bontempi, Gianluca. Credit card fraud detection: a realistic modeling and a novel learning strategy, IEEE transactions on neural networks and learning systems,29,8,3784-3797,2018,IEEE\n", + "o\tDal Pozzolo, Andrea Adaptive Machine learning for credit card fraud detection ULB MLG PhD thesis (supervised by G. Bontempi)\n", + "\u00e2\u20ac\u00a2\tCarcillo, Fabrizio; Dal Pozzolo, Andrea; Le Borgne, Yann-A\u00c3\u00abl; Caelen, Olivier; Mazzer, Yannis; Bontempi, Gianluca. Scarff: a scalable framework for streaming credit card fraud detection with Spark, Information fusion,41, 182-194,2018,Elsevier\n", + "\u00e2\u20ac\u00a2\tCarcillo, Fabrizio; Le Borgne, Yann-A\u00c3\u00abl; Caelen, Olivier; Bontempi, Gianluca. Streaming active learning strategies for real-life credit card fraud detection: assessment and visualization, International Journal of Data Science and Analytics, 5,4,285-300,2018,Springer International Publishing" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml new file mode 100644 index 00000000..14c8fe46 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-credit-card-fraud +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml b/how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml new file mode 100644 index 00000000..e0d810ab --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-with-deployment +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.ipynb b/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.ipynb index a555a3a5..6a0032af 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.ipynb +++ b/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.ipynb @@ -129,6 +129,22 @@ " test_size=0.2, \n", " random_state=0)\n", "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ensure the x_train and x_test are pandas DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Convert the X_train and X_test to pandas DataFrame and set column names,\n", "# This is needed for initializing the input variable names of ONNX model, \n", "# and the prediction with the ONNX model using the inference helper.\n", @@ -158,6 +174,13 @@ "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set the preprocess=True, currently the InferenceHelper only supports this mode." + ] + }, { "cell_type": "code", "execution_count": null, @@ -299,7 +322,7 @@ " onnxrt_present = False\n", "\n", "def get_onnx_res(run):\n", - " res_path = '_debug_y_trans_converter.json'\n", + " res_path = 'onnx_resource.json'\n", " run.download_file(name=constants.MODEL_RESOURCE_PATH_ONNX, output_file_path=res_path)\n", " with open(res_path) as f:\n", " onnx_res = json.load(f)\n", @@ -316,7 +339,7 @@ " print(pred_prob_onnx)\n", "else:\n", " if not python_version_compatible:\n", - " print('Please use Python version 3.6 to run the inference helper.') \n", + " print('Please use Python version 3.6 or 3.7 to run the inference helper.') \n", " if not onnxrt_present:\n", " print('Please install the onnxruntime package to do the prediction with ONNX model.')" ] diff --git a/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml b/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml new file mode 100644 index 00000000..04a552c8 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml @@ -0,0 +1,9 @@ +name: auto-ml-classification-with-onnx +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - onnxruntime diff --git a/how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml b/how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml new file mode 100644 index 00000000..32631c0a --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-with-whitelisting +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml b/how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml new file mode 100644 index 00000000..c099a8b9 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.ipynb b/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.ipynb index e4b50a4e..abc53d82 100644 --- a/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.ipynb +++ b/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.ipynb @@ -21,7 +21,7 @@ "metadata": {}, "source": [ "# Automated Machine Learning\n", - "_**Prepare Data using `azureml.dataprep` for Remote Execution (DSVM)**_\n", + "_**Prepare Data using `azureml.dataprep` for Remote Execution (AmlCompute)**_\n", "\n", "## Contents\n", "1. [Introduction](#Introduction)\n", diff --git a/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml b/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml new file mode 100644 index 00000000..c9e18056 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml @@ -0,0 +1,8 @@ +name: auto-ml-dataprep-remote-execution +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml b/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml new file mode 100644 index 00000000..4f7748d9 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml @@ -0,0 +1,8 @@ +name: auto-ml-dataprep +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml b/how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml new file mode 100644 index 00000000..e6e638f1 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml @@ -0,0 +1,8 @@ +name: auto-ml-exploring-previous-runs +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb index 6a063f5e..dabce312 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb @@ -36,19 +36,17 @@ "metadata": {}, "source": [ "## Introduction\n", - "In this example, we show how AutoML can be used for bike share forecasting.\n", + "This notebook demonstrates demand forecasting for a bike-sharing service using AutoML.\n", "\n", - "The purpose is to demonstrate how to take advantage of the built-in holiday featurization, access the feature names, and further demonstrate how to work with the `forecast` function. Please also look at the additional forecasting notebooks, which document lagging, rolling windows, forecast quantiles, other ways to use the forecast function, and forecaster deployment.\n", + "AutoML highlights here include built-in holiday featurization, accessing engineered feature names, and working with the `forecast` function. Please also look at the additional forecasting notebooks, which document lagging, rolling windows, forecast quantiles, other ways to use the forecast function, and forecaster deployment.\n", "\n", "Make sure you have executed the [configuration](../../../configuration.ipynb) before running this notebook.\n", "\n", - "In this notebook you would see\n", + "Notebook synopsis:\n", "1. Creating an Experiment in an existing Workspace\n", - "2. Instantiating AutoMLConfig with new task type \"forecasting\" for timeseries data training, and other timeseries related settings: for this dataset we use the basic one: \"time_column_name\" \n", - "3. Training the Model using local compute\n", - "4. Exploring the results\n", - "5. Viewing the engineered names for featurized data and featurization summary for all raw features\n", - "6. Testing the fitted model" + "2. Configuration and local run of AutoML for a time-series model with lag and holiday features \n", + "3. Viewing the engineered names for featurized data and featurization summary for all raw features\n", + "4. Evaluating the fitted model using a rolling test " ] }, { @@ -69,10 +67,12 @@ "import numpy as np\n", "import logging\n", "import warnings\n", + "\n", + "from pandas.tseries.frequencies import to_offset\n", + "\n", "# Squash warning messages for cleaner output in the notebook\n", "warnings.showwarning = lambda *args, **kwargs: None\n", "\n", - "\n", "from azureml.core.workspace import Workspace\n", "from azureml.core.experiment import Experiment\n", "from azureml.train.automl import AutoMLConfig\n", @@ -84,7 +84,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As part of the setup you have already created a Workspace. For AutoML you would need to create an Experiment. An Experiment is a named object in a Workspace, which is used to run experiments." + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." ] }, { @@ -129,14 +129,15 @@ "metadata": {}, "outputs": [], "source": [ - "data = pd.read_csv('bike-no.csv', parse_dates=['date'])" + "data = pd.read_csv('bike-no.csv', parse_dates=['date'])\n", + "data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's set up what we know abou the dataset. \n", + "Let's set up what we know about the dataset. \n", "\n", "**Target column** is what we want to forecast.\n", "\n", @@ -194,8 +195,7 @@ "source": [ "### Setting forecaster maximum horizon \n", "\n", - "Assuming your test data forms a full and regular time series(regular time intervals and no holes), \n", - "the maximum horizon you will need to forecast is the length of the longest grain in your test set." + "The forecast horizon is the number of periods into the future that the model should predict. Here, we set the horizon to 14 periods (i.e. 14 days). Notice that this is much shorter than the number of days in the test set; we will need to use a rolling test to evaluate the performance on the whole test set. For more discussion of forecast horizons and guiding principles for setting them, please see the [energy demand notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand). " ] }, { @@ -204,10 +204,7 @@ "metadata": {}, "outputs": [], "source": [ - "if len(grain_column_names) == 0:\n", - " max_horizon = len(X_test)\n", - "else:\n", - " max_horizon = X_test.groupby(grain_column_names)[time_column_name].count().max()" + "max_horizon = 14" ] }, { @@ -237,26 +234,25 @@ "metadata": {}, "outputs": [], "source": [ - "time_column_name = 'date'\n", "automl_settings = {\n", - " \"time_column_name\": time_column_name,\n", - " # these columns are a breakdown of the total and therefore a leak\n", - " \"drop_column_names\": ['casual', 'registered'],\n", + " 'time_column_name': time_column_name,\n", + " 'max_horizon': max_horizon,\n", " # knowing the country/region allows Automated ML to bring in holidays\n", - " \"country_or_region\" : 'US',\n", - " \"max_horizon\" : max_horizon,\n", - " \"target_lags\": 1 \n", + " 'country_or_region': 'US',\n", + " 'target_lags': 1,\n", + " # these columns are a breakdown of the total and therefore a leak\n", + " 'drop_column_names': ['casual', 'registered']\n", "}\n", "\n", - "automl_config = AutoMLConfig(task = 'forecasting', \n", + "automl_config = AutoMLConfig(task='forecasting', \n", " primary_metric='normalized_root_mean_squared_error',\n", - " iterations = 10,\n", - " iteration_timeout_minutes = 5,\n", - " X = X_train,\n", - " y = y_train,\n", - " n_cross_validations = 3, \n", + " iterations=10,\n", + " iteration_timeout_minutes=5,\n", + " X=X_train,\n", + " y=y_train,\n", + " n_cross_validations=3, \n", " path=project_folder,\n", - " verbosity = logging.INFO,\n", + " verbosity=logging.INFO,\n", " **automl_settings)" ] }, @@ -264,7 +260,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will now run the experiment, starting with 10 iterations of model search. Experiment can be continued for more iterations if the results are not yet good. You will see the currently running iterations printing to the console." + "We will now run the experiment, starting with 10 iterations of model search. The experiment can be continued for more iterations if more accurate results are required. You will see the currently running iterations printing to the console." ] }, { @@ -356,11 +352,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Test the Best Fitted Model\n", + "## Evaluate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now use the best fitted model from the AutoML Run to make forecasts for the test set. \n", "\n", - "Predict on training and test set, and calculate residual values.\n", - "\n", - "We always score on the original dataset whose schema matches the scheme of the training dataset." + "We always score on the original dataset whose schema matches the training set schema." ] }, { @@ -372,21 +373,12 @@ "X_test.head()" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_query = y_test.copy().astype(np.float)\n", - "y_query.fill(np.NaN)\n", - "y_fcst, X_trans = fitted_model.forecast(X_test, y_query)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ + "We now define some functions for aligning output to input and for producing rolling forecasts over the full test set. As previously stated, the forecast horizon of 14 days is shorter than the length of the test set - which is about 120 days. To get predictions over the full test set, we iterate over the test set, making forecasts 14 days at a time and combining the results. We also make sure that each 14-day forecast uses up-to-date actuals - the current context - to construct lag features. \n", + "\n", "It is a good practice to always align the output explicitly to the input, as the count and order of the rows may have changed during transformations that span multiple rows." ] }, @@ -396,7 +388,8 @@ "metadata": {}, "outputs": [], "source": [ - "def align_outputs(y_predicted, X_trans, X_test, y_test, predicted_column_name = 'predicted'):\n", + "def align_outputs(y_predicted, X_trans, X_test, y_test, predicted_column_name='predicted',\n", + " horizon_colname='horizon_origin'):\n", " \"\"\"\n", " Demonstrates how to get the output aligned to the inputs\n", " using pandas indexes. Helps understand what happened if\n", @@ -408,7 +401,8 @@ " * model was asked to predict past max_horizon -> increase max horizon\n", " * data at start of X_test was needed for lags -> provide previous periods\n", " \"\"\"\n", - " df_fcst = pd.DataFrame({predicted_column_name : y_predicted})\n", + " df_fcst = pd.DataFrame({predicted_column_name : y_predicted,\n", + " horizon_colname: X_trans[horizon_colname]})\n", " # y and X outputs are aligned by forecast() function contract\n", " df_fcst.index = X_trans.index\n", " \n", @@ -427,7 +421,49 @@ " clean = together[together[[target_column_name, predicted_column_name]].notnull().all(axis=1)]\n", " return(clean)\n", "\n", - "df_all = align_outputs(y_fcst, X_trans, X_test, y_test)\n" + "def do_rolling_forecast(fitted_model, X_test, y_test, max_horizon, freq='D'):\n", + " \"\"\"\n", + " Produce forecasts on a rolling origin over the given test set.\n", + " \n", + " Each iteration makes a forecast for the next 'max_horizon' periods \n", + " with respect to the current origin, then advances the origin by the horizon time duration. \n", + " The prediction context for each forecast is set so that the forecaster uses \n", + " the actual target values prior to the current origin time for constructing lag features.\n", + " \n", + " This function returns a concatenated DataFrame of rolling forecasts.\n", + " \"\"\"\n", + " df_list = []\n", + " origin_time = X_test[time_column_name].min()\n", + " while origin_time <= X_test[time_column_name].max():\n", + " # Set the horizon time - end date of the forecast\n", + " horizon_time = origin_time + max_horizon * to_offset(freq)\n", + " \n", + " # Extract test data from an expanding window up-to the horizon \n", + " expand_wind = (X_test[time_column_name] < horizon_time)\n", + " X_test_expand = X_test[expand_wind]\n", + " y_query_expand = np.zeros(len(X_test_expand)).astype(np.float)\n", + " y_query_expand.fill(np.NaN)\n", + " \n", + " if origin_time != X_test[time_column_name].min():\n", + " # Set the context by including actuals up-to the origin time\n", + " test_context_expand_wind = (X_test[time_column_name] < origin_time)\n", + " context_expand_wind = (X_test_expand[time_column_name] < origin_time)\n", + " y_query_expand[context_expand_wind] = y_test[test_context_expand_wind]\n", + " \n", + " # Make a forecast out to the maximum horizon\n", + " y_fcst, X_trans = fitted_model.forecast(X_test_expand, y_query_expand)\n", + " \n", + " # Align forecast with test set for dates within the current rolling window \n", + " trans_tindex = X_trans.index.get_level_values(time_column_name)\n", + " trans_roll_wind = (trans_tindex >= origin_time) & (trans_tindex < horizon_time)\n", + " test_roll_wind = expand_wind & (X_test[time_column_name] >= origin_time)\n", + " df_list.append(align_outputs(y_fcst[trans_roll_wind], X_trans[trans_roll_wind],\n", + " X_test[test_roll_wind], y_test[test_roll_wind]))\n", + " \n", + " # Advance the origin time\n", + " origin_time = horizon_time\n", + " \n", + " return pd.concat(df_list, ignore_index=True)" ] }, { @@ -436,6 +472,30 @@ "metadata": {}, "outputs": [], "source": [ + "df_all = do_rolling_forecast(fitted_model, X_test, y_test, max_horizon)\n", + "df_all" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now calculate some error metrics for the forecasts and vizualize the predictions vs. the actuals." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def APE(actual, pred):\n", + " \"\"\"\n", + " Calculate absolute percentage error.\n", + " Returns a vector of APE values with same length as actual/pred.\n", + " \"\"\"\n", + " return 100*np.abs((actual - pred)/actual)\n", + "\n", "def MAPE(actual, pred):\n", " \"\"\"\n", " Calculate mean absolute percentage error.\n", @@ -445,8 +505,7 @@ " not_zero = ~np.isclose(actual, 0.0)\n", " actual_safe = actual[not_na & not_zero]\n", " pred_safe = pred[not_na & not_zero]\n", - " APE = 100*np.abs((actual_safe - pred_safe)/actual_safe)\n", - " return np.mean(APE)" + " return np.mean(APE(actual_safe, pred_safe))" ] }, { @@ -469,12 +528,57 @@ "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", "plt.show()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The MAPE seems high; it is being skewed by an actual with a small absolute value. For a more informative evaluation, we can calculate the metrics by forecast horizon:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_all.groupby('horizon_origin').apply(\n", + " lambda df: pd.Series({'MAPE': MAPE(df[target_column_name], df['predicted']),\n", + " 'RMSE': np.sqrt(mean_squared_error(df[target_column_name], df['predicted'])),\n", + " 'MAE': mean_absolute_error(df[target_column_name], df['predicted'])}))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's also interesting to see the distributions of APE (absolute percentage error) by horizon. On a log scale, the outlying APE in the horizon-3 group is clear." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_all_APE = df_all.assign(APE=APE(df_all[target_column_name], df_all['predicted']))\n", + "APEs = [df_all_APE[df_all['horizon_origin'] == h].APE.values for h in range(1, max_horizon + 1)]\n", + "\n", + "%matplotlib notebook\n", + "plt.boxplot(APEs)\n", + "plt.yscale('log')\n", + "plt.xlabel('horizon')\n", + "plt.ylabel('APE (%)')\n", + "plt.title('Absolute Percentage Errors by Forecast Horizon')\n", + "\n", + "plt.show()" + ] } ], "metadata": { "authors": [ { - "name": "xiaga@microsoft.com, tosingli@microsoft.com" + "name": "xiaga@microsoft.com, tosingli@microsoft.com, erwright@microsoft.com" } ], "kernelspec": { @@ -492,7 +596,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml new file mode 100644 index 00000000..ad74b802 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml @@ -0,0 +1,9 @@ +name: auto-ml-forecasting-bike-share +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - statsmodels diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb index 9e29e167..88f42473 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb @@ -35,17 +35,16 @@ "metadata": {}, "source": [ "## Introduction\n", - "In this example, we show how AutoML can be used for energy demand forecasting.\n", + "In this example, we show how AutoML can be used to forecast a single time-series in the energy demand application area. \n", "\n", "Make sure you have executed the [configuration](../../../configuration.ipynb) before running this notebook.\n", "\n", - "In this notebook you would see\n", + "Notebook synopsis:\n", "1. Creating an Experiment in an existing Workspace\n", - "2. Instantiating AutoMLConfig with new task type \"forecasting\" for timeseries data training, and other timeseries related settings: for this dataset we use the basic one: \"time_column_name\" \n", - "3. Training the Model using local compute\n", - "4. Exploring the results\n", - "5. Viewing the engineered names for featurized data and featurization summary for all raw features\n", - "6. Testing the fitted model" + "2. Configuration and local run of AutoML for a simple time-series model\n", + "3. View engineered features and prediction results\n", + "4. Configuration and local run of AutoML for a time-series model with lag and rolling window features\n", + "5. Estimate feature importance" ] }, { @@ -66,10 +65,10 @@ "import numpy as np\n", "import logging\n", "import warnings\n", + "\n", "# Squash warning messages for cleaner output in the notebook\n", "warnings.showwarning = lambda *args, **kwargs: None\n", "\n", - "\n", "from azureml.core.workspace import Workspace\n", "from azureml.core.experiment import Experiment\n", "from azureml.train.automl import AutoMLConfig\n", @@ -81,7 +80,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As part of the setup you have already created a Workspace. For AutoML you would need to create an Experiment. An Experiment is a named object in a Workspace, which is used to run experiments." + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." ] }, { @@ -117,7 +116,7 @@ "metadata": {}, "source": [ "## Data\n", - "Read energy demanding data from file, and preview data." + "We will use energy consumption data from New York City for model training. The data is stored in a tabular format and includes energy demand and basic weather data at an hourly frequency. Pandas CSV reader is used to read the file into memory. Special attention is given to the \"timeStamp\" column in the data since it contains text which should be parsed as datetime-type objects. " ] }, { @@ -130,13 +129,20 @@ "data.head()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We must now define the schema of this dataset. Every time-series must have a time column and a target. The target quantity is what will be eventually forecasted by a trained model. In this case, the target is the \"demand\" column. The other columns, \"temp\" and \"precip,\" are implicitly designated as features." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# let's take note of what columns means what in the data\n", + "# Dataset schema\n", "time_column_name = 'timeStamp'\n", "target_column_name = 'demand'" ] @@ -145,7 +151,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Split the data into train and test sets\n" + "### Forecast Horizon\n", + "\n", + "In addition to the data schema, we must also specify the forecast horizon. A forecast horizon is a time span into the future (or just beyond the latest date in the training data) where forecasts of the target quantity are needed. Choosing a forecast horizon is application specific, but a rule-of-thumb is that **the horizon should be the time-frame where you need actionable decisions based on the forecast.** The horizon usually has a strong relationship with the frequency of the time-series data, that is, the sampling interval of the target quantity and the features. For instance, the NYC energy demand data has an hourly frequency. A decision that requires a demand forecast to the hour is unlikely to be made weeks or months in advance, particularly if we expect weather to be a strong determinant of demand. We may have fairly accurate meteorological forecasts of the hourly temperature and precipitation on a the time-scale of a day or two, however.\n", + "\n", + "Given the above discussion, we generally recommend that users set forecast horizons to less than 100 time periods (i.e. less than 100 hours in the NYC energy example). Furthermore, **AutoML's memory use and computation time increase in proportion to the length of the horizon**, so the user should consider carefully how they set this value. If a long horizon forecast really is necessary, it may be good practice to aggregate the series to a coarser time scale. \n", + "\n", + "\n", + "Forecast horizons in AutoML are given as integer multiples of the time-series frequency. In this example, we set the horizon to 48 hours." ] }, { @@ -154,8 +167,32 @@ "metadata": {}, "outputs": [], "source": [ - "X_train = data[data[time_column_name] < '2017-02-01']\n", - "X_test = data[data[time_column_name] >= '2017-02-01']\n", + "max_horizon = 48" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Split the data into train and test sets\n", + "We now split the data into a train and a test set so that we may evaluate model performance. We note that the tail of the dataset contains a large number of NA values in the target column, so we designate the test set as the 48 hour window ending on the latest date of known energy demand. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Find time point to split on\n", + "latest_known_time = data[~pd.isnull(data[target_column_name])][time_column_name].max()\n", + "split_time = latest_known_time - pd.Timedelta(hours=max_horizon)\n", + "\n", + "# Split into train/test sets\n", + "X_train = data[data[time_column_name] <= split_time]\n", + "X_test = data[(data[time_column_name] > split_time) & (data[time_column_name] <= latest_known_time)]\n", + "\n", + "# Move the target values into their own arrays \n", "y_train = X_train.pop(target_column_name).values\n", "y_test = X_test.pop(target_column_name).values" ] @@ -166,7 +203,7 @@ "source": [ "## Train\n", "\n", - "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "We now instantiate an AutoMLConfig object. This config defines the settings and data used to run the experiment. For forecasting tasks, we must provide extra configuration related to the time-series data schema and forecasting context. Here, only the name of the time column and the maximum forecast horizon are needed. Other settings are described below:\n", "\n", "|Property|Description|\n", "|-|-|\n", @@ -176,7 +213,7 @@ "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**n_cross_validations**|Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way.|\n", "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder. " ] }, @@ -186,22 +223,22 @@ "metadata": {}, "outputs": [], "source": [ - "automl_settings = {\n", - " \"time_column_name\": time_column_name \n", + "time_series_settings = {\n", + " 'time_column_name': time_column_name,\n", + " 'max_horizon': max_horizon\n", "}\n", "\n", - "\n", - "automl_config = AutoMLConfig(task = 'forecasting',\n", - " debug_log = 'automl_nyc_energy_errors.log',\n", + "automl_config = AutoMLConfig(task='forecasting',\n", + " debug_log='automl_nyc_energy_errors.log',\n", " primary_metric='normalized_root_mean_squared_error',\n", - " iterations = 10,\n", - " iteration_timeout_minutes = 5,\n", - " X = X_train,\n", - " y = y_train,\n", - " n_cross_validations = 3,\n", + " iterations=10,\n", + " iteration_timeout_minutes=5,\n", + " X=X_train,\n", + " y=y_train,\n", + " n_cross_validations=3,\n", " path=project_folder,\n", " verbosity = logging.INFO,\n", - " **automl_settings)" + " **time_series_settings)" ] }, { @@ -358,7 +395,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Calculate accuracy metrics\n" + "### Calculate accuracy metrics\n", + "Finally, we calculate some accuracy metrics for the forecast and plot the predictions vs. the actuals over the time range in the test set." ] }, { @@ -395,9 +433,12 @@ "\n", "# Plot outputs\n", "%matplotlib notebook\n", - "test_pred = plt.scatter(df_all[target_column_name], df_all['predicted'], color='b')\n", - "test_test = plt.scatter(y_test, y_test, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "pred, = plt.plot(df_all[time_column_name], df_all['predicted'], color='b')\n", + "actual, = plt.plot(df_all[time_column_name], df_all[target_column_name], color='g')\n", + "plt.xticks(fontsize=8)\n", + "plt.legend((pred, actual), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.title('Prediction vs. Actual Time-Series')\n", + "\n", "plt.show()" ] }, @@ -412,16 +453,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Using lags and rolling window features to improve the forecast" + "### Using lags and rolling window features" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, grain and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data.\n", + "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, grain and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data. In the previous example, the horizon was only used to split the data for cross-validation.\n", "\n", - "Now that we configured target lags, that is the previous values of the target variables, and the prediction is no longer horizon-less. We therefore must specify the `max_horizon` that the model will learn to forecast. The `target_lags` keyword specifies how far back we will construct the lags of the target variable, and the `target_rolling_window_size` specifies the size of the rolling window over which we will generate the `max`, `min` and `sum` features." + "Now that we configured target lags, that is the previous values of the target variables, and the prediction is no longer horizon-less. We therefore must still specify the `max_horizon` that the model will learn to forecast. The `target_lags` keyword specifies how far back we will construct the lags of the target variable, and the `target_rolling_window_size` specifies the size of the rolling window over which we will generate the `max`, `min` and `sum` features." ] }, { @@ -430,27 +471,32 @@ "metadata": {}, "outputs": [], "source": [ - "automl_settings_lags = {\n", + "time_series_settings_with_lags = {\n", " 'time_column_name': time_column_name,\n", - " 'target_lags': 1,\n", - " 'target_rolling_window_size': 5,\n", - " # you MUST set the max_horizon when using lags and rolling windows\n", - " # it is optional when looking-back features are not used \n", - " 'max_horizon': len(y_test), # only one grain\n", + " 'max_horizon': max_horizon,\n", + " 'target_lags': 12,\n", + " 'target_rolling_window_size': 4\n", "}\n", "\n", - "\n", - "automl_config_lags = AutoMLConfig(task = 'forecasting',\n", - " debug_log = 'automl_nyc_energy_errors.log',\n", - " primary_metric='normalized_root_mean_squared_error',\n", - " iterations = 10,\n", - " iteration_timeout_minutes = 5,\n", - " X = X_train,\n", - " y = y_train,\n", - " n_cross_validations = 3,\n", - " path=project_folder,\n", - " verbosity = logging.INFO,\n", - " **automl_settings_lags)" + "automl_config_lags = AutoMLConfig(task='forecasting',\n", + " debug_log='automl_nyc_energy_errors.log',\n", + " primary_metric='normalized_root_mean_squared_error',\n", + " blacklist_models=['ElasticNet'],\n", + " iterations=10,\n", + " iteration_timeout_minutes=10,\n", + " X=X_train,\n", + " y=y_train,\n", + " n_cross_validations=3,\n", + " path=project_folder,\n", + " verbosity=logging.INFO,\n", + " **time_series_settings_with_lags)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now start a new local run, this time with lag and rolling window featurization. AutoML applies featurizations in the setup stage, prior to iterating over ML models. The full training set is featurized first, followed by featurization of each of the CV splits. Lag and rolling window features introduce additional complexity, so the run will take longer than in the previous example that lacked these featurizations." ] }, { @@ -498,9 +544,10 @@ "\n", "# Plot outputs\n", "%matplotlib notebook\n", - "test_pred = plt.scatter(df_lags[target_column_name], df_lags['predicted'], color='b')\n", - "test_test = plt.scatter(y_test, y_test, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "pred, = plt.plot(df_lags[time_column_name], df_lags['predicted'], color='b')\n", + "actual, = plt.plot(df_lags[time_column_name], df_lags[target_column_name], color='g')\n", + "plt.xticks(fontsize=8)\n", + "plt.legend((pred, actual), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", "plt.show()" ] }, @@ -520,8 +567,8 @@ "from azureml.train.automl.automlexplainer import explain_model\n", "\n", "# feature names are everything in the transformed data except the target\n", - "features = X_trans.columns[:-1]\n", - "expl = explain_model(fitted_model, X_train, X_test, features = features, best_run=best_run_lags, y_train = y_train)\n", + "features = X_trans_lags.columns[:-1]\n", + "expl = explain_model(fitted_model_lags, X_train.copy(), X_test.copy(), features=features, best_run=best_run_lags, y_train=y_train)\n", "# unpack the tuple\n", "shap_values, expected_values, feat_overall_imp, feat_names, per_class_summary, per_class_imp = expl\n", "best_run_lags" @@ -540,7 +587,7 @@ "metadata": { "authors": [ { - "name": "xiaga, tosingli" + "name": "xiaga, tosingli, erwright" } ], "kernelspec": { @@ -558,7 +605,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml new file mode 100644 index 00000000..5a2fda3d --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml @@ -0,0 +1,10 @@ +name: auto-ml-forecasting-energy-demand +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - statsmodels + - azureml-explain-model diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb index d157c9a7..e129c731 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb @@ -37,16 +37,10 @@ "metadata": {}, "source": [ "## Introduction\n", - "In this example, we use AutoML to find and tune a time-series forecasting model.\n", + "In this example, we use AutoML to train, select, and operationalize a time-series forecasting model for multiple time-series.\n", "\n", "Make sure you have executed the [configuration notebook](../../../configuration.ipynb) before running this notebook.\n", "\n", - "In this notebook, you will:\n", - "1. Create an Experiment in an existing Workspace\n", - "2. Instantiate an AutoMLConfig \n", - "3. Find and train a forecasting model using local compute\n", - "4. Evaluate the performance of the model\n", - "\n", "The examples in the follow code samples use the University of Chicago's Dominick's Finer Foods dataset to forecast orange juice sales. Dominick's was a grocery chain in the Chicago metropolitan area." ] }, @@ -68,10 +62,10 @@ "import numpy as np\n", "import logging\n", "import warnings\n", + "\n", "# Squash warning messages for cleaner output in the notebook\n", "warnings.showwarning = lambda *args, **kwargs: None\n", "\n", - "\n", "from azureml.core.workspace import Workspace\n", "from azureml.core.experiment import Experiment\n", "from azureml.train.automl import AutoMLConfig\n", @@ -82,7 +76,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment is a named object in a Workspace which represents a predictive task, the output of which is a trained model and a set of evaluation metrics for the model. " + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem. " ] }, { @@ -236,7 +230,7 @@ "\n", "For forecasting tasks, there are some additional parameters that can be set: the name of the column holding the date/time, the grain column names, and the maximum forecast horizon. A time column is required for forecasting, while the grain is optional. If a grain is not given, AutoML assumes that the whole dataset is a single time-series. We also pass a list of columns to drop prior to modeling. The _logQuantity_ column is completely correlated with the target quantity, so it must be removed to prevent a target leak.\n", "\n", - "The forecast horizon is given in units of the time-series frequency; for instance, the OJ series frequency is weekly, so a horizon of 20 means that a trained model will estimate sales up-to 20 weeks beyond the latest date in the training data for each series. In this example, we set the maximum horizon to the number of samples per series in the test set (n_test_periods). Generally, the value of this parameter will be dictated by business needs. For example, a demand planning organizaion that needs to estimate the next month of sales would set the horizon accordingly. \n", + "The forecast horizon is given in units of the time-series frequency; for instance, the OJ series frequency is weekly, so a horizon of 20 means that a trained model will estimate sales up-to 20 weeks beyond the latest date in the training data for each series. In this example, we set the maximum horizon to the number of samples per series in the test set (n_test_periods). Generally, the value of this parameter will be dictated by business needs. For example, a demand planning organizaion that needs to estimate the next month of sales would set the horizon accordingly. Please see the [energy_demand notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand) for more discussion of forecast horizon.\n", "\n", "Finally, a note about the cross-validation (CV) procedure for time-series data. AutoML uses out-of-sample error estimates to select a best pipeline/model, so it is important that the CV fold splitting is done correctly. Time-series can violate the basic statistical assumptions of the canonical K-Fold CV strategy, so AutoML implements a [rolling origin validation](https://robjhyndman.com/hyndsight/tscv/) procedure to create CV folds for time-series data. To use this procedure, you just need to specify the desired number of CV folds in the AutoMLConfig object. It is also possible to bypass CV and use your own validation set by setting the *X_valid* and *y_valid* parameters of AutoMLConfig.\n", "\n", @@ -269,7 +263,7 @@ " 'time_column_name': time_column_name,\n", " 'grain_column_names': grain_column_names,\n", " 'drop_column_names': ['logQuantity'],\n", - " 'max_horizon': n_test_periods # optional\n", + " 'max_horizon': n_test_periods\n", "}\n", "\n", "automl_config = AutoMLConfig(task='forecasting',\n", @@ -278,7 +272,7 @@ " iterations=10,\n", " X=X_train,\n", " y=y_train,\n", - " n_cross_validations=5,\n", + " n_cross_validations=3,\n", " enable_ensembling=False,\n", " path=project_folder,\n", " verbosity=logging.INFO,\n", @@ -324,7 +318,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Predict\n", + "# Forecasting\n", + "\n", "Now that we have retrieved the best pipeline/model, it can be used to make predictions on test data. First, we remove the target values from the test set:" ] }, @@ -852,7 +847,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml new file mode 100644 index 00000000..a9ab52bd --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml @@ -0,0 +1,9 @@ +name: auto-ml-forecasting-orange-juice-sales +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - statsmodels diff --git a/how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml b/how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml new file mode 100644 index 00000000..b3d14116 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml @@ -0,0 +1,8 @@ +name: auto-ml-missing-data-blacklist-early-termination +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml b/how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml new file mode 100644 index 00000000..1c4e89af --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml @@ -0,0 +1,9 @@ +name: auto-ml-model-explanation +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - azureml-explain-model diff --git a/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb new file mode 100644 index 00000000..6b1fc201 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb @@ -0,0 +1,800 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Regression with Deployment using Hardware Performance Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Data](#Data)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "In this example we use the Predicting Compressive Strength of Concrete Dataset to showcase how you can use AutoML for a regression problem. The regression goal is to predict the compressive strength of concrete based off of different ingredient combinations and the quantities of those ingredients.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an `Experiment` in an existing `Workspace`.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Test the best fitted model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "As part of the setup you have already created an Azure ML Workspace object. For AutoML you will need to create an Experiment object, which is a named object in a Workspace used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import os\n", + "from sklearn.model_selection import train_test_split\n", + "import azureml.dataprep as dprep\n", + " \n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# Choose a name for the experiment and specify the project folder.\n", + "experiment_name = 'automl-regression-concrete'\n", + "project_folder = './sample_projects/automl-regression-concrete'\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace Name'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here create the script to be run in azure compute for loading the data, load the concrete strength dataset into the X and y variables. Next, split the data using train_test_split and return X_train and y_train for training the model. Finally, return X_train and y_train for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/compresive_strength_concrete.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X = dflow.drop_columns(columns=['CONCRETE'])\n", + "y = dflow.keep_columns(columns=['CONCRETE'], validate_column_exists=True)\n", + "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", + "y_train, y_test = y.random_split(percentage=0.8, seed=223) \n", + "dflow.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 5,\n", + " \"primary_metric\": 'spearman_correlation',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'regression',\n", + " debug_log = 'automl.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results\n", + "Widget for Monitoring Runs\n", + "The widget will first report a \u00e2\u20ac\u0153loading status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "Note: The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Retrieve All Child Runs\n", + "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(remote_run.get_children())\n", + "metricslist = {}\n", + "for run in children:\n", + " properties = run.get_properties()\n", + " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)}\n", + " metricslist[int(properties['iteration'])] = metrics\n", + "\n", + "rundata = pd.DataFrame(metricslist).sort_index(1)\n", + "rundata" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve the Best Model\n", + "Below we select the best pipeline from our iterations. The get_output method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on get_output allow you to retrieve the best run and fitted model for any logged metric or for a particular iteration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Best Model Based on Any Other Metric\n", + "Show the run and the model that has the smallest root_mean_squared_error value (which turned out to be the same as the one with largest spearman_correlation value):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lookup_metric = \"root_mean_squared_error\"\n", + "best_run, fitted_model = remote_run.get_output(metric = lookup_metric)\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "iteration = 3\n", + "third_run, third_model = remote_run.get_output(iteration = iteration)\n", + "print(third_run)\n", + "print(third_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Register the Fitted Model for Deployment\n", + "If neither metric nor iteration are specified in the register_model call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn'], pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"},\n", + " description = \"Image for automl regression sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"}, \n", + " description = 'sample service for Automl Regression')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-concrete'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test\n", + "\n", + "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()\n", + "y_test = np.array(y_test)\n", + "y_test = y_test[:,0]\n", + "X_train = X_train.to_pandas_dataframe()\n", + "y_train = y_train.to_pandas_dataframe()\n", + "y_train = np.array(y_train)\n", + "y_train = y_train[:,0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Predict on training and test set, and calculate residual values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred_train = fitted_model.predict(X_train)\n", + "y_residual_train = y_train - y_pred_train\n", + "\n", + "y_pred_test = fitted_model.predict(X_test)\n", + "y_residual_test = y_test - y_pred_test\n", + "\n", + "y_residual_train.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "from sklearn.metrics import mean_squared_error, r2_score\n", + "\n", + "# Set up a multi-plot chart.\n", + "f, (a0, a1) = plt.subplots(1, 2, gridspec_kw = {'width_ratios':[1, 1], 'wspace':0, 'hspace': 0})\n", + "f.suptitle('Regression Residual Values', fontsize = 18)\n", + "f.set_figheight(6)\n", + "f.set_figwidth(16)\n", + "\n", + "# Plot residual values of training set.\n", + "a0.axis([0, 360, -200, 200])\n", + "a0.plot(y_residual_train, 'bo', alpha = 0.5)\n", + "a0.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a0.text(16,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_train, y_pred_train))), fontsize = 12)\n", + "a0.text(16,140,'R2 score = {0:.2f}'.format(r2_score(y_train, y_pred_train)), fontsize = 12)\n", + "a0.set_xlabel('Training samples', fontsize = 12)\n", + "a0.set_ylabel('Residual Values', fontsize = 12)\n", + "\n", + "# Plot a histogram.\n", + "#a0.hist(y_residual_train, orientation = 'horizontal', color = ['b']*len(y_residual_train), bins = 10, histtype = 'step')\n", + "#a0.hist(y_residual_train, orientation = 'horizontal', color = ['b']*len(y_residual_train), alpha = 0.2, bins = 10)\n", + "\n", + "# Plot residual values of test set.\n", + "a1.axis([0, 90, -200, 200])\n", + "a1.plot(y_residual_test, 'bo', alpha = 0.5)\n", + "a1.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a1.text(5,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_test, y_pred_test))), fontsize = 12)\n", + "a1.text(5,140,'R2 score = {0:.2f}'.format(r2_score(y_test, y_pred_test)), fontsize = 12)\n", + "a1.set_xlabel('Test samples', fontsize = 12)\n", + "a1.set_yticklabels([])\n", + "\n", + "# Plot a histogram.\n", + "#a1.hist(y_residual_test, orientation = 'horizontal', color = ['b']*len(y_residual_test), bins = 10, histtype = 'step')\n", + "#a1.hist(y_residual_test, orientation = 'horizontal', color = ['b']*len(y_residual_test), alpha = 0.2, bins = 10)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot outputs\n", + "%matplotlib notebook\n", + "test_pred = plt.scatter(y_test, y_pred_test, color='b')\n", + "test_test = plt.scatter(y_test, y_test, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements\n", + "\n", + "This Predicting Compressive Strength of Concrete Dataset is made available under the CC0 1.0 Universal (CC0 1.0)\n", + "Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the CC0 1.0 Universal (CC0 1.0)\n", + "Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/ . The dataset itself can be found here: https://www.kaggle.com/pavanraj159/concrete-compressive-strength-data-set and http://archive.ics.uci.edu/ml/datasets/concrete+compressive+strength\n", + "\n", + "I-Cheng Yeh, \"Modeling of strength of high performance concrete using artificial neural networks,\" Cement and Concrete Research, Vol. 28, No. 12, pp. 1797-1808 (1998). \n", + "\n", + "Dua, D. and Graff, C. (2019). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml new file mode 100644 index 00000000..eb39aa20 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml @@ -0,0 +1,8 @@ +name: auto-ml-regression-concrete-strength +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb new file mode 100644 index 00000000..5376ac3d --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb @@ -0,0 +1,800 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Regression with Deployment using Hardware Performance Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Data](#Data)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "In this example we use the Hardware Performance Dataset to showcase how you can use AutoML for a simple regression problem. The Regression goal is to predict the performance of certain combinations of hardware parts.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an `Experiment` in an existing `Workspace`.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Test the best fitted model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "As part of the setup you have already created an Azure ML Workspace object. For AutoML you will need to create an Experiment object, which is a named object in a Workspace used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import os\n", + "from sklearn.model_selection import train_test_split\n", + "import azureml.dataprep as dprep\n", + " \n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# Choose a name for the experiment and specify the project folder.\n", + "experiment_name = 'automl-regression-hardware'\n", + "project_folder = './sample_projects/automl-remote-regression'\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace Name'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here create the script to be run in azure compute for loading the data, load the hardware dataset into the X and y variables. Next split the data using train_test_split and return X_train and y_train for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/machineData.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X = dflow.drop_columns(columns=['ERP'])\n", + "y = dflow.keep_columns(columns=['ERP'], validate_column_exists=True)\n", + "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", + "y_train, y_test = y.random_split(percentage=0.8, seed=223) \n", + "dflow.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Train\n", + "\n", + "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 5,\n", + " \"primary_metric\": 'spearman_correlation',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'regression',\n", + " debug_log = 'automl_errors_20190417.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Widget for Monitoring Runs\n", + "\n", + "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "\n", + "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Wait until the run finishes.\n", + "remote_run.wait_for_completion(show_output = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve All Child Runs\n", + "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(remote_run.get_children())\n", + "metricslist = {}\n", + "for run in children:\n", + " properties = run.get_properties()\n", + " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)}\n", + " metricslist[int(properties['iteration'])] = metrics\n", + "\n", + "rundata = pd.DataFrame(metricslist).sort_index(1)\n", + "rundata" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve the Best Model\n", + "Below we select the best pipeline from our iterations. The get_output method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on get_output allow you to retrieve the best run and fitted model for any logged metric or for a particular iteration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Best Model Based on Any Other Metric\n", + "Show the run and the model that has the smallest `root_mean_squared_error` value (which turned out to be the same as the one with largest `spearman_correlation` value):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lookup_metric = \"root_mean_squared_error\"\n", + "best_run, fitted_model = remote_run.get_output(metric = lookup_metric)\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "iteration = 3\n", + "third_run, third_model = remote_run.get_output(iteration = iteration)\n", + "print(third_run)\n", + "print(third_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Register the Fitted Model for Deployment\n", + "If neither metric nor iteration are specified in the register_model call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn'], pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"},\n", + " description = \"Image for automl regression sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"}, \n", + " description = 'sample service for Automl Regression')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-hardware'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test\n", + "\n", + "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()\n", + "y_test = np.array(y_test)\n", + "y_test = y_test[:,0]\n", + "X_train = X_train.to_pandas_dataframe()\n", + "y_train = y_train.to_pandas_dataframe()\n", + "y_train = np.array(y_train)\n", + "y_train = y_train[:,0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Predict on training and test set, and calculate residual values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred_train = fitted_model.predict(X_train)\n", + "y_residual_train = y_train - y_pred_train\n", + "\n", + "y_pred_test = fitted_model.predict(X_test)\n", + "y_residual_test = y_test - y_pred_test" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "from sklearn.metrics import mean_squared_error, r2_score\n", + "\n", + "# Set up a multi-plot chart.\n", + "f, (a0, a1) = plt.subplots(1, 2, gridspec_kw = {'width_ratios':[1, 1], 'wspace':0, 'hspace': 0})\n", + "f.suptitle('Regression Residual Values', fontsize = 18)\n", + "f.set_figheight(6)\n", + "f.set_figwidth(16)\n", + "\n", + "# Plot residual values of training set.\n", + "a0.axis([0, 360, -200, 200])\n", + "a0.plot(y_residual_train, 'bo', alpha = 0.5)\n", + "a0.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a0.text(16,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_train, y_pred_train))), fontsize = 12)\n", + "a0.text(16,140,'R2 score = {0:.2f}'.format(r2_score(y_train, y_pred_train)),fontsize = 12)\n", + "a0.set_xlabel('Training samples', fontsize = 12)\n", + "a0.set_ylabel('Residual Values', fontsize = 12)\n", + "\n", + "# Plot residual values of test set.\n", + "a1.axis([0, 90, -200, 200])\n", + "a1.plot(y_residual_test, 'bo', alpha = 0.5)\n", + "a1.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a1.text(5,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_test, y_pred_test))), fontsize = 12)\n", + "a1.text(5,140,'R2 score = {0:.2f}'.format(r2_score(y_test, y_pred_test)),fontsize = 12)\n", + "a1.set_xlabel('Test samples', fontsize = 12)\n", + "a1.set_yticklabels([])\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "test_pred = plt.scatter(y_test, y_pred_test, color='')\n", + "test_test = plt.scatter(y_test, y_test, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements\n", + "This Predicting Hardware Performance Dataset is made available under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/ . The dataset itself can be found here: https://www.kaggle.com/faizunnabi/comp-hardware-performance and https://archive.ics.uci.edu/ml/datasets/Computer+Hardware\n", + "\n", + "_**Citation Found Here**_\n" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml new file mode 100644 index 00000000..ddc29fa8 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml @@ -0,0 +1,8 @@ +name: auto-ml-regression-hardware-performance +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml b/how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml new file mode 100644 index 00000000..18789ff3 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml @@ -0,0 +1,9 @@ +name: auto-ml-regression +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - paramiko<2.5.0 diff --git a/how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml b/how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml new file mode 100644 index 00000000..41b4f214 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml @@ -0,0 +1,8 @@ +name: auto-ml-remote-amlcompute +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml b/how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml new file mode 100644 index 00000000..954b57f0 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml @@ -0,0 +1,8 @@ +name: auto-ml-sample-weight +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml b/how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml new file mode 100644 index 00000000..4039b384 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml @@ -0,0 +1,8 @@ +name: auto-ml-sparse-data-train-test-split +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml b/how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml new file mode 100644 index 00000000..695cd466 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml @@ -0,0 +1,8 @@ +name: auto-ml-subsampling-local +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/azure-hdi/automl_hdi_local_classification.ipynb b/how-to-use-azureml/azure-hdi/automl_hdi_local_classification.ipynb index 9d223949..8f3fa251 100644 --- a/how-to-use-azureml/azure-hdi/automl_hdi_local_classification.ipynb +++ b/how-to-use-azureml/azure-hdi/automl_hdi_local_classification.ipynb @@ -1,631 +1,612 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { + "cells": [ + { "cell_type": "markdown", "metadata": {}, "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/azure-hdi/automl_hdi_local_classification.png)" - ] + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated ML on Azure HDInsight\n", - "\n", - "In this example we use the scikit-learn's digit dataset to showcase how you can use AutoML for a simple classification problem.\n", - "\n", - "In this notebook you will learn how to:\n", - "1. Create Azure Machine Learning Workspace object and initialize your notebook directory to easily reload this object from a configuration file.\n", - "2. Create an `Experiment` in an existing `Workspace`.\n", - "3. Configure Automated ML using `AutoMLConfig`.\n", - "4. Train the model using Azure HDInsight.\n", - "5. Explore the results.\n", - "6. Test the best fitted model.\n", - "\n", - "Before running this notebook, please follow the readme for using Automated ML on Azure HDI for installing necessary libraries to your cluster." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Check the Azure ML Core SDK Version to Validate Your Installation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import azureml.core\n", - "import pandas as pd\n", - "from azureml.core.authentication import ServicePrincipalAuthentication\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.train.automl import AutoMLConfig\n", - "from azureml.train.automl.run import AutoMLRun\n", - "import logging\n", - "\n", - "print(\"SDK Version:\", azureml.core.VERSION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialize an Azure ML Workspace\n", - "### What is an Azure ML Workspace and Why Do I Need One?\n", - "\n", - "An Azure ML workspace is an Azure resource that organizes and coordinates the actions of many other Azure resources to assist in executing and sharing machine learning workflows. In particular, an Azure ML workspace coordinates storage, databases, and compute resources providing added functionality for machine learning experimentation, operationalization, and the monitoring of operationalized models.\n", - "\n", - "\n", - "### What do I Need?\n", - "\n", - "To create or access an Azure ML workspace, you will need to import the Azure ML library and specify following information:\n", - "* A name for your workspace. You can choose one.\n", - "* Your subscription id. Use the `id` value from the `az account show` command output above.\n", - "* The resource group name. The resource group organizes Azure resources and provides a default region for the resources in the group. The resource group will be created if it doesn't exist. Resource groups can be created and viewed in the [Azure portal](https://portal.azure.com)\n", - "* Supported regions include `eastus2`, `eastus`,`westcentralus`, `southeastasia`, `westeurope`, `australiaeast`, `westus2`, `southcentralus`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import azureml.core\n", - "import pandas as pd\n", - "from azureml.core.authentication import ServicePrincipalAuthentication\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.train.automl import AutoMLConfig\n", - "from azureml.train.automl.run import AutoMLRun\n", - "import logging\n", - "\n", - "subscription_id = \"\" #you should be owner or contributor\n", - "resource_group = \"\" #you should be owner or contributor\n", - "workspace_name = \"\" #your workspace name\n", - "workspace_region = \"\" #your region\n", - "\n", - "\n", - "tenant_id = \"\"\n", - "app_id = \"\"\n", - "app_key = \"\"\n", - "\n", - "auth_sp = ServicePrincipalAuthentication(tenant_id = tenant_id,\n", - " service_principal_id = app_id,\n", - " service_principal_password = app_key)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating a Workspace\n", - "If you already have access to an Azure ML workspace you want to use, you can skip this cell. Otherwise, this cell will create an Azure ML workspace for you in the specified subscription, provided you have the correct permissions for the given `subscription_id`.\n", - "\n", - "This will fail when:\n", - "1. The workspace already exists.\n", - "2. You do not have permission to create a workspace in the resource group.\n", - "3. You are not a subscription owner or contributor and no Azure ML workspaces have ever been created in this subscription.\n", - "\n", - "If workspace creation fails for any reason other than already existing, please work with your IT administrator to provide you with the appropriate permissions or to provision the required resources.\n", - "\n", - "**Note:** Creation of a new workspace can take several minutes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "##TESTONLY\n", - "# Import the Workspace class and check the Azure ML SDK version.\n", - "from azureml.core import Workspace\n", - "\n", - "ws = Workspace.create(name = workspace_name,\n", - " subscription_id = subscription_id,\n", - " resource_group = resource_group, \n", - " location = workspace_region,\n", - " auth = auth_sp,\n", - " exist_ok=True)\n", - "ws.get_details()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuring Your Local Environment\n", - "You can validate that you have access to the specified workspace and write a configuration file to the default configuration location, `./aml_config/config.json`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "\n", - "ws = Workspace(workspace_name = workspace_name,\n", - " subscription_id = subscription_id,\n", - " resource_group = resource_group,\n", - " auth = auth_sp)\n", - "\n", - "# Persist the subscription id, resource group name, and workspace name in aml_config/config.json.\n", - "ws.write_config()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a Folder to Host Sample Projects\n", - "Finally, create a folder where all the sample projects will be hosted." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "sample_projects_folder = './sample_projects'\n", - "\n", - "if not os.path.isdir(sample_projects_folder):\n", - " os.mkdir(sample_projects_folder)\n", - " \n", - "print('Sample projects will be created in {}.'.format(sample_projects_folder))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create an Experiment\n", - "\n", - "As part of the setup you have already created an Azure ML `Workspace` object. For Automated ML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "import os\n", - "import random\n", - "import time\n", - "\n", - "from matplotlib import pyplot as plt\n", - "from matplotlib.pyplot import imshow\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.train.automl import AutoMLConfig\n", - "from azureml.train.automl.run import AutoMLRun" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Choose a name for the experiment and specify the project folder.\n", - "experiment_name = 'automl-local-classification-hdi'\n", - "project_folder = './sample_projects/automl-local-classification-hdi'\n", - "\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['SDK version'] = azureml.core.VERSION\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace Name'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Project Directory'] = project_folder\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "pd.DataFrame(data = output, index = ['']).T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Diagnostics\n", - "\n", - "Opt-in diagnostics for better experience, quality, and security of future releases." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.telemetry import set_diagnostics_collection\n", - "set_diagnostics_collection(send_diagnostics = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Registering Datastore" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Datastore is the way to save connection information to a storage service (e.g. Azure Blob, Azure Data Lake, Azure SQL) information to your workspace so you can access them without exposing credentials in your code. The first thing you will need to do is register a datastore, you can refer to our [python SDK documentation](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore.datastore?view=azure-ml-py) on how to register datastores. __Note: for best security practices, please do not check in code that contains registering datastores with secrets into your source control__\n", - "\n", - "The code below registers a datastore pointing to a publicly readable blob container." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Datastore\n", - "\n", - "datastore_name = 'demo_training'\n", - "container_name = 'digits' \n", - "account_name = 'automlpublicdatasets'\n", - "Datastore.register_azure_blob_container(\n", - " workspace = ws, \n", - " datastore_name = datastore_name, \n", - " container_name = container_name, \n", - " account_name = account_name,\n", - " overwrite = True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Below is an example on how to register a private blob container\n", - "```python\n", - "datastore = Datastore.register_azure_blob_container(\n", - " workspace = ws, \n", - " datastore_name = 'example_datastore', \n", - " container_name = 'example-container', \n", - " account_name = 'storageaccount',\n", - " account_key = 'accountkey'\n", - ")\n", - "```\n", - "The example below shows how to register an Azure Data Lake store. Please make sure you have granted the necessary permissions for the service principal to access the data lake.\n", - "```python\n", - "datastore = Datastore.register_azure_data_lake(\n", - " workspace = ws,\n", - " datastore_name = 'example_datastore',\n", - " store_name = 'adlsstore',\n", - " tenant_id = 'tenant-id-of-service-principal',\n", - " client_id = 'client-id-of-service-principal',\n", - " client_secret = 'client-secret-of-service-principal'\n", - ")\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load Training Data Using DataPrep" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Automated ML takes a Dataflow as input.\n", - "\n", - "If you are familiar with Pandas and have done your data preparation work in Pandas already, you can use the `read_pandas_dataframe` method in dprep to convert the DataFrame to a Dataflow.\n", - "```python\n", - "df = pd.read_csv(...)\n", - "# apply some transforms\n", - "dprep.read_pandas_dataframe(df, temp_folder='/path/accessible/by/both/driver/and/worker')\n", - "```\n", - "\n", - "If you just need to ingest data without doing any preparation, you can directly use AzureML Data Prep (Data Prep) to do so. The code below demonstrates this scenario. Data Prep also has data preparation capabilities, we have many [sample notebooks](https://github.com/Microsoft/AMLDataPrepDocs) demonstrating the capabilities.\n", - "\n", - "You will get the datastore you registered previously and pass it to Data Prep for reading. The data comes from the digits dataset: `sklearn.datasets.load_digits()`. `DataPath` points to a specific location within a datastore. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import azureml.dataprep as dprep\n", - "from azureml.data.datapath import DataPath\n", - "\n", - "datastore = Datastore.get(workspace = ws, datastore_name = datastore_name)\n", - "\n", - "X_train = dprep.read_csv(datastore.path('X.csv'))\n", - "y_train = dprep.read_csv(datastore.path('y.csv')).to_long(dprep.ColumnSelector(term='.*', use_regex = True))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Review the Data Preparation Result\n", - "You can peek the result of a Dataflow at any range using `skip(i)` and `head(j)`. Doing so evaluates only j records for all the steps in the Dataflow, which makes it fast even against large datasets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_train.get_profile()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_train.get_profile()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configure AutoML\n", - "\n", - "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|classification or regression|\n", - "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", - "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", - "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", - "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**spark_context**|Spark Context object. for HDInsight, use spark_context=sc|\n", - "|**max_concurrent_iterations**|Maximum number of iterations to execute in parallel. This should be <= number of worker nodes in your Azure HDInsight cluster.|\n", - "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", - "|**y**|(sparse) array-like, shape = [n_samples, ], [n_samples, n_classes]
Multi-class targets. An indicator matrix turns on multilabel classification. This should be an array of integers.|\n", - "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", - "|**preprocess**|set this to True to enable pre-processing of data eg. string to numeric using one-hot encoding|\n", - "|**exit_score**|Target score for experiment. It is associated with the metric. eg. exit_score=0.995 will exit experiment after that|" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_config = AutoMLConfig(task = 'classification',\n", - " debug_log = 'automl_errors.log',\n", - " primary_metric = 'AUC_weighted',\n", - " iteration_timeout_minutes = 10,\n", - " iterations = 3,\n", - " preprocess = True,\n", - " n_cross_validations = 10,\n", - " max_concurrent_iterations = 2, #change it based on number of worker nodes\n", - " verbosity = logging.INFO,\n", - " spark_context=sc, #HDI /spark related\n", - " X = X_train, \n", - " y = y_train,\n", - " path = project_folder)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train the Models\n", - "\n", - "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "local_run = experiment.submit(automl_config, show_output = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Explore the Results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following will show the child runs and waits for the parent run to complete." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Retrieve All Child Runs after the experiment is completed (in portal)\n", - "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "children = list(local_run.get_children())\n", - "metricslist = {}\n", - "for run in children:\n", - " properties = run.get_properties()\n", - " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)} \n", - " metricslist[int(properties['iteration'])] = metrics\n", - "\n", - "rundata = pd.DataFrame(metricslist).sort_index(1)\n", - "rundata" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrieve the Best Model after the above run is complete \n", - "\n", - "Below we select the best pipeline from our iterations. The `get_output` method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run, fitted_model = local_run.get_output()\n", - "print(best_run)\n", - "print(fitted_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Best Model Based on Any Other Metric after the above run is complete based on the child run\n", - "Show the run and the model that has the smallest `log_loss` value:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lookup_metric = \"log_loss\"\n", - "best_run, fitted_model = local_run.get_output(metric = lookup_metric)\n", - "print(best_run)\n", - "print(fitted_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Test the Best Fitted Model\n", - "\n", - "#### Load Test Data - you can split the dataset beforehand & pass Train dataset to AutoML and use Test dataset to evaluate the best model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "blob_location = \"https://{}.blob.core.windows.net/{}\".format(account_name, container_name)\n", - "X_test = pd.read_csv(\"{}./X_valid.csv\".format(blob_location), header=0)\n", - "y_test = pd.read_csv(\"{}/y_valid.csv\".format(blob_location), header=0)\n", - "images = pd.read_csv(\"{}/images.csv\".format(blob_location), header=None)\n", - "images = np.reshape(images.values, (100,8,8))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Testing Our Best Fitted Model\n", - "We will try to predict digits and see how our model works. This is just an example to show you." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Randomly select digits and test.\n", - "for index in np.random.choice(len(y_test), 2, replace = False):\n", - " print(index)\n", - " predicted = fitted_model.predict(X_test[index:index + 1])[0]\n", - " label = y_test.values[index]\n", - " title = \"Label value = %d Predicted value = %d \" % (label, predicted)\n", - " fig = plt.figure(3, figsize = (5,5))\n", - " ax1 = fig.add_axes((0,0,.8,.8))\n", - " ax1.set_title(title)\n", - " plt.imshow(images[index], cmap = plt.cm.gray_r, interpolation = 'nearest')\n", - " display(fig)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When deploying an automated ML trained model, please specify _pippackages=['azureml-sdk[automl]']_ in your CondaDependencies.\n", - "\n", - "Please refer to only the **Deploy** section in this notebook - Deployment of Automated ML trained model" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "savitam" - }, - { - "name": "sasum" - } + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/azure-hdi/automl_hdi_local_classification.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated ML on Azure HDInsight\n", + "\n", + "In this example we use the scikit-learn's digit dataset to showcase how you can use AutoML for a simple classification problem.\n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create Azure Machine Learning Workspace object and initialize your notebook directory to easily reload this object from a configuration file.\n", + "2. Create an `Experiment` in an existing `Workspace`.\n", + "3. Configure Automated ML using `AutoMLConfig`.\n", + "4. Train the model using Azure HDInsight.\n", + "5. Explore the results.\n", + "6. Test the best fitted model.\n", + "\n", + "Before running this notebook, please follow the readme for using Automated ML on Azure HDI for installing necessary libraries to your cluster." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check the Azure ML Core SDK Version to Validate Your Installation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.core\n", + "import pandas as pd\n", + "from azureml.core.authentication import ServicePrincipalAuthentication\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.train.automl.run import AutoMLRun\n", + "import logging\n", + "\n", + "print(\"SDK Version:\", azureml.core.VERSION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize an Azure ML Workspace\n", + "### What is an Azure ML Workspace and Why Do I Need One?\n", + "\n", + "An Azure ML workspace is an Azure resource that organizes and coordinates the actions of many other Azure resources to assist in executing and sharing machine learning workflows. In particular, an Azure ML workspace coordinates storage, databases, and compute resources providing added functionality for machine learning experimentation, operationalization, and the monitoring of operationalized models.\n", + "\n", + "\n", + "### What do I Need?\n", + "\n", + "To create or access an Azure ML workspace, you will need to import the Azure ML library and specify following information:\n", + "* A name for your workspace. You can choose one.\n", + "* Your subscription id. Use the `id` value from the `az account show` command output above.\n", + "* The resource group name. The resource group organizes Azure resources and provides a default region for the resources in the group. The resource group will be created if it doesn't exist. Resource groups can be created and viewed in the [Azure portal](https://portal.azure.com)\n", + "* Supported regions include `eastus2`, `eastus`,`westcentralus`, `southeastasia`, `westeurope`, `australiaeast`, `westus2`, `southcentralus`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.core\n", + "import pandas as pd\n", + "from azureml.core.authentication import ServicePrincipalAuthentication\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.train.automl.run import AutoMLRun\n", + "import logging\n", + "\n", + "subscription_id = \"\" #you should be owner or contributor\n", + "resource_group = \"\" #you should be owner or contributor\n", + "workspace_name = \"\" #your workspace name\n", + "workspace_region = \"\" #your region\n", + "\n", + "\n", + "tenant_id = \"\"\n", + "app_id = \"\"\n", + "app_key = \"\"\n", + "\n", + "auth_sp = ServicePrincipalAuthentication(tenant_id = tenant_id,\n", + " service_principal_id = app_id,\n", + " service_principal_password = app_key)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating a Workspace\n", + "If you already have access to an Azure ML workspace you want to use, you can skip this cell. Otherwise, this cell will create an Azure ML workspace for you in the specified subscription, provided you have the correct permissions for the given `subscription_id`.\n", + "\n", + "This will fail when:\n", + "1. The workspace already exists.\n", + "2. You do not have permission to create a workspace in the resource group.\n", + "3. You are not a subscription owner or contributor and no Azure ML workspaces have ever been created in this subscription.\n", + "\n", + "If workspace creation fails for any reason other than already existing, please work with your IT administrator to provide you with the appropriate permissions or to provision the required resources.\n", + "\n", + "**Note:** Creation of a new workspace can take several minutes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configuring Your Local Environment\n", + "You can validate that you have access to the specified workspace and write a configuration file to the default configuration location, `./aml_config/config.json`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "\n", + "ws = Workspace(workspace_name = workspace_name,\n", + " subscription_id = subscription_id,\n", + " resource_group = resource_group,\n", + " auth = auth_sp)\n", + "\n", + "# Persist the subscription id, resource group name, and workspace name in aml_config/config.json.\n", + "ws.write_config()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Folder to Host Sample Projects\n", + "Finally, create a folder where all the sample projects will be hosted." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "sample_projects_folder = './sample_projects'\n", + "\n", + "if not os.path.isdir(sample_projects_folder):\n", + " os.mkdir(sample_projects_folder)\n", + " \n", + "print('Sample projects will be created in {}.'.format(sample_projects_folder))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create an Experiment\n", + "\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For Automated ML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "import os\n", + "import random\n", + "import time\n", + "\n", + "from matplotlib import pyplot as plt\n", + "from matplotlib.pyplot import imshow\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.train.automl.run import AutoMLRun" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Choose a name for the experiment and specify the project folder.\n", + "experiment_name = 'automl-local-classification-hdi'\n", + "project_folder = './sample_projects/automl-local-classification-hdi'\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace Name'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "pd.DataFrame(data = output, index = ['']).T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Diagnostics\n", + "\n", + "Opt-in diagnostics for better experience, quality, and security of future releases." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.telemetry import set_diagnostics_collection\n", + "set_diagnostics_collection(send_diagnostics = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Registering Datastore" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Datastore is the way to save connection information to a storage service (e.g. Azure Blob, Azure Data Lake, Azure SQL) information to your workspace so you can access them without exposing credentials in your code. The first thing you will need to do is register a datastore, you can refer to our [python SDK documentation](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore.datastore?view=azure-ml-py) on how to register datastores. __Note: for best security practices, please do not check in code that contains registering datastores with secrets into your source control__\n", + "\n", + "The code below registers a datastore pointing to a publicly readable blob container." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Datastore\n", + "\n", + "datastore_name = 'demo_training'\n", + "container_name = 'digits' \n", + "account_name = 'automlpublicdatasets'\n", + "Datastore.register_azure_blob_container(\n", + " workspace = ws, \n", + " datastore_name = datastore_name, \n", + " container_name = container_name, \n", + " account_name = account_name,\n", + " overwrite = True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below is an example on how to register a private blob container\n", + "```python\n", + "datastore = Datastore.register_azure_blob_container(\n", + " workspace = ws, \n", + " datastore_name = 'example_datastore', \n", + " container_name = 'example-container', \n", + " account_name = 'storageaccount',\n", + " account_key = 'accountkey'\n", + ")\n", + "```\n", + "The example below shows how to register an Azure Data Lake store. Please make sure you have granted the necessary permissions for the service principal to access the data lake.\n", + "```python\n", + "datastore = Datastore.register_azure_data_lake(\n", + " workspace = ws,\n", + " datastore_name = 'example_datastore',\n", + " store_name = 'adlsstore',\n", + " tenant_id = 'tenant-id-of-service-principal',\n", + " client_id = 'client-id-of-service-principal',\n", + " client_secret = 'client-secret-of-service-principal'\n", + ")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Training Data Using DataPrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Automated ML takes a Dataflow as input.\n", + "\n", + "If you are familiar with Pandas and have done your data preparation work in Pandas already, you can use the `read_pandas_dataframe` method in dprep to convert the DataFrame to a Dataflow.\n", + "```python\n", + "df = pd.read_csv(...)\n", + "# apply some transforms\n", + "dprep.read_pandas_dataframe(df, temp_folder='/path/accessible/by/both/driver/and/worker')\n", + "```\n", + "\n", + "If you just need to ingest data without doing any preparation, you can directly use AzureML Data Prep (Data Prep) to do so. The code below demonstrates this scenario. Data Prep also has data preparation capabilities, we have many [sample notebooks](https://github.com/Microsoft/AMLDataPrepDocs) demonstrating the capabilities.\n", + "\n", + "You will get the datastore you registered previously and pass it to Data Prep for reading. The data comes from the digits dataset: `sklearn.datasets.load_digits()`. `DataPath` points to a specific location within a datastore. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "from azureml.data.datapath import DataPath\n", + "\n", + "datastore = Datastore.get(workspace = ws, datastore_name = datastore_name)\n", + "\n", + "X_train = dprep.read_csv(datastore.path('X.csv'))\n", + "y_train = dprep.read_csv(datastore.path('y.csv')).to_long(dprep.ColumnSelector(term='.*', use_regex = True))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Review the Data Preparation Result\n", + "You can peek the result of a Dataflow at any range using `skip(i)` and `head(j)`. Doing so evaluates only j records for all the steps in the Dataflow, which makes it fast even against large datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_train.get_profile()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_train.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure AutoML\n", + "\n", + "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", + "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**spark_context**|Spark Context object. for HDInsight, use spark_context=sc|\n", + "|**max_concurrent_iterations**|Maximum number of iterations to execute in parallel. This should be <= number of worker nodes in your Azure HDInsight cluster.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], [n_samples, n_classes]
Multi-class targets. An indicator matrix turns on multilabel classification. This should be an array of integers.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "|**preprocess**|set this to True to enable pre-processing of data eg. string to numeric using one-hot encoding|\n", + "|**exit_score**|Target score for experiment. It is associated with the metric. eg. exit_score=0.995 will exit experiment after that|" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_config = AutoMLConfig(task = 'classification',\n", + " debug_log = 'automl_errors.log',\n", + " primary_metric = 'AUC_weighted',\n", + " iteration_timeout_minutes = 10,\n", + " iterations = 3,\n", + " preprocess = True,\n", + " n_cross_validations = 10,\n", + " max_concurrent_iterations = 2, #change it based on number of worker nodes\n", + " verbosity = logging.INFO,\n", + " spark_context=sc, #HDI /spark related\n", + " X = X_train, \n", + " y = y_train,\n", + " path = project_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train the Models\n", + "\n", + "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "local_run = experiment.submit(automl_config, show_output = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Explore the Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following will show the child runs and waits for the parent run to complete." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Retrieve All Child Runs after the experiment is completed (in portal)\n", + "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(local_run.get_children())\n", + "metricslist = {}\n", + "for run in children:\n", + " properties = run.get_properties()\n", + " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)} \n", + " metricslist[int(properties['iteration'])] = metrics\n", + "\n", + "rundata = pd.DataFrame(metricslist).sort_index(1)\n", + "rundata" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieve the Best Model after the above run is complete \n", + "\n", + "Below we select the best pipeline from our iterations. The `get_output` method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = local_run.get_output()\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Best Model Based on Any Other Metric after the above run is complete based on the child run\n", + "Show the run and the model that has the smallest `log_loss` value:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lookup_metric = \"log_loss\"\n", + "best_run, fitted_model = local_run.get_output(metric = lookup_metric)\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test the Best Fitted Model\n", + "\n", + "#### Load Test Data - you can split the dataset beforehand & pass Train dataset to AutoML and use Test dataset to evaluate the best model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "blob_location = \"https://{}.blob.core.windows.net/{}\".format(account_name, container_name)\n", + "X_test = pd.read_csv(\"{}./X_valid.csv\".format(blob_location), header=0)\n", + "y_test = pd.read_csv(\"{}/y_valid.csv\".format(blob_location), header=0)\n", + "images = pd.read_csv(\"{}/images.csv\".format(blob_location), header=None)\n", + "images = np.reshape(images.values, (100,8,8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Testing Our Best Fitted Model\n", + "We will try to predict digits and see how our model works. This is just an example to show you." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Randomly select digits and test.\n", + "for index in np.random.choice(len(y_test), 2, replace = False):\n", + " print(index)\n", + " predicted = fitted_model.predict(X_test[index:index + 1])[0]\n", + " label = y_test.values[index]\n", + " title = \"Label value = %d Predicted value = %d \" % (label, predicted)\n", + " fig = plt.figure(3, figsize = (5,5))\n", + " ax1 = fig.add_axes((0,0,.8,.8))\n", + " ax1.set_title(title)\n", + " plt.imshow(images[index], cmap = plt.cm.gray_r, interpolation = 'nearest')\n", + " display(fig)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When deploying an automated ML trained model, please specify _pippackages=['azureml-sdk[automl]']_ in your CondaDependencies.\n", + "\n", + "Please refer to only the **Deploy** section in this notebook - Deployment of Automated ML trained model" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "Python", - "name": "Python36" + "metadata": { + "authors": [ + { + "name": "savitam" + }, + { + "name": "sasum" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "Python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "python", + "version": 3 + }, + "mimetype": "text/x-python", + "name": "pyspark3", + "pygments_lexer": "python3" + }, + "name": "auto-ml-classification-local-adb", + "notebookId": 587284549713154 }, - "language_info": { - "codemirror_mode": { - "name": "python", - "version": 3 - }, - "mimetype": "text/x-python", - "name": "pyspark3", - "pygments_lexer": "python3" - }, - "name": "auto-ml-classification-local-adb", - "notebookId": 587284549713154 - }, - "nbformat": 4, - "nbformat_minor": 1 -} + "nbformat": 4, + "nbformat_minor": 1 +} \ No newline at end of file diff --git a/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb b/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb index 05750864..7922bc77 100644 --- a/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb +++ b/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb @@ -1,709 +1,709 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Track Data Drift between Training and Inference Data in Production \n", - "\n", - "With this notebook, you will learn how to enable the DataDrift service to automatically track and determine whether your inference data is drifting from the data your model was initially trained on. The DataDrift service provides metrics and visualizations to help stakeholders identify which specific features cause the concept drift to occur.\n", - "\n", - "Please email driftfeedback@microsoft.com with any issues. A member from the DataDrift team will respond shortly. \n", - "\n", - "The DataDrift Public Preview API can be found [here](https://docs.microsoft.com/en-us/python/api/azureml-contrib-datadrift/?view=azure-ml-py). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/contrib/datadrift/azureml-datadrift.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Prerequisites and Setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install the DataDrift package\n", - "\n", - "Install the azureml-contrib-datadrift, azureml-contrib-opendatasets and lightgbm packages before running this notebook.\n", - "```\n", - "pip install azureml-contrib-datadrift\n", - "pip install azureml-contrib-datasets\n", - "pip install lightgbm\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Import Dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import os\n", - "import time\n", - "from datetime import datetime, timedelta\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "import requests\n", - "from azureml.contrib.datadrift import DataDriftDetector, AlertConfiguration\n", - "from azureml.contrib.opendatasets import NoaaIsdWeather\n", - "from azureml.core import Dataset, Workspace, Run\n", - "from azureml.core.compute import AksCompute, ComputeTarget\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.image import ContainerImage\n", - "from azureml.core.model import Model\n", - "from azureml.core.webservice import Webservice, AksWebservice\n", - "from azureml.widgets import RunDetails\n", - "from sklearn.externals import joblib\n", - "from sklearn.model_selection import train_test_split\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up Configuraton and Create Azure ML Workspace\n", - "\n", - "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration notebook](../../../configuration.ipynb) first if you haven't already to establish your connection to the AzureML Workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Please type in your initials/alias. The prefix is prepended to the names of resources created by this notebook. \n", - "prefix = \"dd\"\n", - "\n", - "# NOTE: Please do not change the model_name, as it's required by the score.py file\n", - "model_name = \"driftmodel\"\n", - "image_name = \"{}driftimage\".format(prefix)\n", - "service_name = \"{}driftservice\".format(prefix)\n", - "\n", - "# optionally, set email address to receive an email alert for DataDrift\n", - "email_address = \"\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate Train/Testing Data\n", - "\n", - "For this demo, we will use NOAA weather data from [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). You may replace this step with your own dataset. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "usaf_list = ['725724', '722149', '723090', '722159', '723910', '720279',\n", - " '725513', '725254', '726430', '720381', '723074', '726682',\n", - " '725486', '727883', '723177', '722075', '723086', '724053',\n", - " '725070', '722073', '726060', '725224', '725260', '724520',\n", - " '720305', '724020', '726510', '725126', '722523', '703333',\n", - " '722249', '722728', '725483', '722972', '724975', '742079',\n", - " '727468', '722193', '725624', '722030', '726380', '720309',\n", - " '722071', '720326', '725415', '724504', '725665', '725424',\n", - " '725066']\n", - "\n", - "columns = ['usaf', 'wban', 'datetime', 'latitude', 'longitude', 'elevation', 'windAngle', 'windSpeed', 'temperature', 'stationName', 'p_k']\n", - "\n", - "\n", - "def enrich_weather_noaa_data(noaa_df):\n", - " hours_in_day = 23\n", - " week_in_year = 52\n", - " \n", - " noaa_df[\"hour\"] = noaa_df[\"datetime\"].dt.hour\n", - " noaa_df[\"weekofyear\"] = noaa_df[\"datetime\"].dt.week\n", - " \n", - " noaa_df[\"sine_weekofyear\"] = noaa_df['datetime'].transform(lambda x: np.sin((2*np.pi*x.dt.week-1)/week_in_year))\n", - " noaa_df[\"cosine_weekofyear\"] = noaa_df['datetime'].transform(lambda x: np.cos((2*np.pi*x.dt.week-1)/week_in_year))\n", - "\n", - " noaa_df[\"sine_hourofday\"] = noaa_df['datetime'].transform(lambda x: np.sin(2*np.pi*x.dt.hour/hours_in_day))\n", - " noaa_df[\"cosine_hourofday\"] = noaa_df['datetime'].transform(lambda x: np.cos(2*np.pi*x.dt.hour/hours_in_day))\n", - " \n", - " return noaa_df\n", - "\n", - "def add_window_col(input_df):\n", - " shift_interval = pd.Timedelta('-7 days') # your X days interval\n", - " df_shifted = input_df.copy()\n", - " df_shifted['datetime'] = df_shifted['datetime'] - shift_interval\n", - " df_shifted.drop(list(input_df.columns.difference(['datetime', 'usaf', 'wban', 'sine_hourofday', 'temperature'])), axis=1, inplace=True)\n", - "\n", - " # merge, keeping only observations where -1 lag is present\n", - " df2 = pd.merge(input_df,\n", - " df_shifted,\n", - " on=['datetime', 'usaf', 'wban', 'sine_hourofday'],\n", - " how='inner', # use 'left' to keep observations without lags\n", - " suffixes=['', '-7'])\n", - " return df2\n", - "\n", - "def get_noaa_data(start_time, end_time, cols, station_list):\n", - " isd = NoaaIsdWeather(start_time, end_time, cols=cols)\n", - " # Read into Pandas data frame.\n", - " noaa_df = isd.to_pandas_dataframe()\n", - " noaa_df = noaa_df.rename(columns={\"stationName\": \"station_name\"})\n", - " \n", - " df_filtered = noaa_df[noaa_df[\"usaf\"].isin(station_list)]\n", - " df_filtered.reset_index(drop=True)\n", - " \n", - " # Enrich with time features\n", - " df_enriched = enrich_weather_noaa_data(df_filtered)\n", - " \n", - " return df_enriched\n", - "\n", - "def get_featurized_noaa_df(start_time, end_time, cols, station_list):\n", - " df_1 = get_noaa_data(start_time - timedelta(days=7), start_time - timedelta(seconds=1), cols, station_list)\n", - " df_2 = get_noaa_data(start_time, end_time, cols, station_list)\n", - " noaa_df = pd.concat([df_1, df_2])\n", - " \n", - " print(\"Adding window feature\")\n", - " df_window = add_window_col(noaa_df)\n", - " \n", - " cat_columns = df_window.dtypes == object\n", - " cat_columns = cat_columns[cat_columns == True]\n", - " \n", - " print(\"Encoding categorical columns\")\n", - " df_encoded = pd.get_dummies(df_window, columns=cat_columns.keys().tolist())\n", - " \n", - " print(\"Dropping unnecessary columns\")\n", - " df_featurized = df_encoded.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna().drop_duplicates()\n", - " \n", - " return df_featurized" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Train model on Jan 1 - 14, 2009 data\n", - "df = get_featurized_noaa_df(datetime(2009, 1, 1), datetime(2009, 1, 14, 23, 59, 59), columns, usaf_list)\n", - "df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "label = \"temperature\"\n", - "x_df = df.drop(label, axis=1)\n", - "y_df = df[[label]]\n", - "x_train, x_test, y_train, y_test = train_test_split(df, y_df, test_size=0.2, random_state=223)\n", - "print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)\n", - "\n", - "training_dir = 'outputs/training'\n", - "training_file = \"training.csv\"\n", - "\n", - "# Generate training dataframe to register as Training Dataset\n", - "os.makedirs(training_dir, exist_ok=True)\n", - "training_df = pd.merge(x_train.drop(label, axis=1), y_train, left_index=True, right_index=True)\n", - "training_df.to_csv(training_dir + \"/\" + training_file)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create/Register Training Dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dataset_name = \"dataset\"\n", - "name_suffix = datetime.utcnow().strftime(\"%Y-%m-%d-%H-%M-%S\")\n", - "snapshot_name = \"snapshot-{}\".format(name_suffix)\n", - "\n", - "dstore = ws.get_default_datastore()\n", - "dstore.upload(training_dir, \"data/training\", show_progress=True)\n", - "dpath = dstore.path(\"data/training/training.csv\")\n", - "trainingDataset = Dataset.auto_read_files(dpath, include_path=True)\n", - "trainingDataset = trainingDataset.register(workspace=ws, name=dataset_name, description=\"dset\", exist_ok=True)\n", - "\n", - "trainingDataSnapshot = trainingDataset.create_snapshot(snapshot_name=snapshot_name, compute_target=None, create_data_snapshot=True)\n", - "datasets = [(Dataset.Scenario.TRAINING, trainingDataSnapshot)]\n", - "print(\"dataset registration done.\\n\")\n", - "datasets" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train and Save Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import lightgbm as lgb\n", - "\n", - "train = lgb.Dataset(data=x_train, \n", - " label=y_train)\n", - "\n", - "test = lgb.Dataset(data=x_test, \n", - " label=y_test,\n", - " reference=train)\n", - "\n", - "params = {'learning_rate' : 0.1,\n", - " 'boosting' : 'gbdt',\n", - " 'metric' : 'rmse',\n", - " 'feature_fraction' : 1,\n", - " 'bagging_fraction' : 1,\n", - " 'max_depth': 6,\n", - " 'num_leaves' : 31,\n", - " 'objective' : 'regression',\n", - " 'bagging_freq' : 1,\n", - " \"verbose\": -1,\n", - " 'min_data_per_leaf': 100}\n", - "\n", - "model = lgb.train(params, \n", - " num_boost_round=500,\n", - " train_set=train,\n", - " valid_sets=[train, test],\n", - " verbose_eval=50,\n", - " early_stopping_rounds=25)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_file = 'outputs/{}.pkl'.format(model_name)\n", - "\n", - "os.makedirs('outputs', exist_ok=True)\n", - "joblib.dump(model, model_file)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Register Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model = Model.register(model_path=model_file,\n", - " model_name=model_name,\n", - " workspace=ws,\n", - " datasets=datasets)\n", - "\n", - "print(model_name, image_name, service_name, model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy Model To AKS" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prepare Environment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn', 'joblib', 'lightgbm', 'pandas'],\n", - " pip_packages=['azureml-monitoring', 'azureml-sdk[automl]'])\n", - "\n", - "with open(\"myenv.yml\",\"w\") as f:\n", - " f.write(myenv.serialize_to_string())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create Image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Image creation may take up to 15 minutes.\n", - "\n", - "image_name = image_name + str(model.version)\n", - "\n", - "if not image_name in ws.images:\n", - " # Use the score.py defined in this directory as the execution script\n", - " # NOTE: The Model Data Collector must be enabled in the execution script for DataDrift to run correctly\n", - " image_config = ContainerImage.image_configuration(execution_script=\"score.py\",\n", - " runtime=\"python\",\n", - " conda_file=\"myenv.yml\",\n", - " description=\"Image with weather dataset model\")\n", - " image = ContainerImage.create(name=image_name,\n", - " models=[model],\n", - " image_config=image_config,\n", - " workspace=ws)\n", - "\n", - " image.wait_for_creation(show_output=True)\n", - "else:\n", - " image = ws.images[image_name]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create Compute Target" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_name = 'dd-demo-e2e'\n", - "prov_config = AksCompute.provisioning_configuration()\n", - "\n", - "if not aks_name in ws.compute_targets:\n", - " aks_target = ComputeTarget.create(workspace=ws,\n", - " name=aks_name,\n", - " provisioning_configuration=prov_config)\n", - "\n", - " aks_target.wait_for_completion(show_output=True)\n", - " print(aks_target.provisioning_state)\n", - " print(aks_target.provisioning_errors)\n", - "else:\n", - " aks_target=ws.compute_targets[aks_name]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Deploy Service" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_service_name = service_name\n", - "\n", - "if not aks_service_name in ws.webservices:\n", - " aks_config = AksWebservice.deploy_configuration(collect_model_data=True, enable_app_insights=True)\n", - " aks_service = Webservice.deploy_from_image(workspace=ws,\n", - " name=aks_service_name,\n", - " image=image,\n", - " deployment_config=aks_config,\n", - " deployment_target=aks_target)\n", - " aks_service.wait_for_deployment(show_output=True)\n", - " print(aks_service.state)\n", - "else:\n", - " aks_service = ws.webservices[aks_service_name]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Run DataDrift Analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Send Scoring Data to Service" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Download Scoring Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Score Model on March 15, 2016 data\n", - "scoring_df = get_noaa_data(datetime(2016, 3, 15) - timedelta(days=7), datetime(2016, 3, 16), columns, usaf_list)\n", - "# Add the window feature column\n", - "scoring_df = add_window_col(scoring_df)\n", - "\n", - "# Drop features not used by the model\n", - "print(\"Dropping unnecessary columns\")\n", - "scoring_df = scoring_df.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna()\n", - "scoring_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# One Hot Encode the scoring dataset to match the training dataset schema\n", - "columns_dict = model.datasets[\"training\"][0].get_profile().columns\n", - "extra_cols = ('Path', 'Column1')\n", - "for k in extra_cols:\n", - " columns_dict.pop(k, None)\n", - "training_columns = list(columns_dict.keys())\n", - "\n", - "categorical_columns = scoring_df.dtypes == object\n", - "categorical_columns = categorical_columns[categorical_columns == True]\n", - "\n", - "test_df = pd.get_dummies(scoring_df[categorical_columns.keys().tolist()])\n", - "encoded_df = scoring_df.join(test_df)\n", - "\n", - "# Populate missing OHE columns with 0 values to match traning dataset schema\n", - "difference = list(set(training_columns) - set(encoded_df.columns.tolist()))\n", - "for col in difference:\n", - " encoded_df[col] = 0\n", - "encoded_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Serialize dataframe to list of row dictionaries\n", - "encoded_dict = encoded_df.to_dict('records')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit Scoring Data to Service" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "\n", - "# retreive the API keys. AML generates two keys.\n", - "key1, key2 = aks_service.get_keys()\n", - "\n", - "total_count = len(scoring_df)\n", - "i = 0\n", - "load = []\n", - "for row in encoded_dict:\n", - " load.append(row)\n", - " i = i + 1\n", - " if i % 100 == 0:\n", - " payload = json.dumps({\"data\": load})\n", - " \n", - " # construct raw HTTP request and send to the service\n", - " payload_binary = bytes(payload,encoding = 'utf8')\n", - " headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n", - " resp = requests.post(aks_service.scoring_uri, payload_binary, headers=headers)\n", - " \n", - " print(\"prediction:\", resp.content, \"Progress: {}/{}\".format(i, total_count)) \n", - "\n", - " load = []\n", - " time.sleep(3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configure DataDrift" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "services = [service_name]\n", - "start = datetime.now() - timedelta(days=2)\n", - "end = datetime(year=2020, month=1, day=22, hour=15, minute=16)\n", - "feature_list = ['usaf', 'wban', 'latitude', 'longitude', 'station_name', 'p_k', 'sine_hourofday', 'cosine_hourofday', 'temperature-7']\n", - "alert_config = AlertConfiguration([email_address]) if email_address else None\n", - "\n", - "# there will be an exception indicating using get() method if DataDrift object already exist\n", - "try:\n", - " datadrift = DataDriftDetector.create(ws, model.name, model.version, services, frequency=\"Day\", alert_config=alert_config)\n", - "except KeyError:\n", - " datadrift = DataDriftDetector.get(ws, model.name, model.version)\n", - " \n", - "print(\"Details of DataDrift Object:\\n{}\".format(datadrift))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run an Adhoc DataDriftDetector Run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "target_date = datetime.today()\n", - "run = datadrift.run(target_date, services, feature_list=feature_list, create_compute_target=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "exp = Experiment(ws, datadrift._id)\n", - "dd_run = Run(experiment=exp, run_id=run)\n", - "RunDetails(dd_run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Get Drift Analysis Results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "children = list(dd_run.get_children())\n", - "for child in children:\n", - " child.wait_for_completion()\n", - "\n", - "drift_metrics = datadrift.get_output(start_time=start, end_time=end)\n", - "drift_metrics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Show all drift figures, one per serivice.\n", - "# If setting with_details is False (by default), only drift will be shown; if it's True, all details will be shown.\n", - "\n", - "drift_figures = datadrift.show(with_details=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Enable DataDrift Schedule" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "datadrift.enable_schedule()" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "rafarmah" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Track Data Drift between Training and Inference Data in Production \n", + "\n", + "With this notebook, you will learn how to enable the DataDrift service to automatically track and determine whether your inference data is drifting from the data your model was initially trained on. The DataDrift service provides metrics and visualizations to help stakeholders identify which specific features cause the concept drift to occur.\n", + "\n", + "Please email driftfeedback@microsoft.com with any issues. A member from the DataDrift team will respond shortly. \n", + "\n", + "The DataDrift Public Preview API can be found [here](https://docs.microsoft.com/en-us/python/api/azureml-contrib-datadrift/?view=azure-ml-py). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/contrib/datadrift/azureml-datadrift.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prerequisites and Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install the DataDrift package\n", + "\n", + "Install the azureml-contrib-datadrift, azureml-contrib-opendatasets and lightgbm packages before running this notebook.\n", + "```\n", + "pip install azureml-contrib-datadrift\n", + "pip install azureml-contrib-datasets\n", + "pip install lightgbm\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "import time\n", + "from datetime import datetime, timedelta\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import requests\n", + "from azureml.contrib.datadrift import DataDriftDetector, AlertConfiguration\n", + "from azureml.contrib.opendatasets import NoaaIsdWeather\n", + "from azureml.core import Dataset, Workspace, Run\n", + "from azureml.core.compute import AksCompute, ComputeTarget\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.image import ContainerImage\n", + "from azureml.core.model import Model\n", + "from azureml.core.webservice import Webservice, AksWebservice\n", + "from azureml.widgets import RunDetails\n", + "from sklearn.externals import joblib\n", + "from sklearn.model_selection import train_test_split\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up Configuraton and Create Azure ML Workspace\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration notebook](../../../configuration.ipynb) first if you haven't already to establish your connection to the AzureML Workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Please type in your initials/alias. The prefix is prepended to the names of resources created by this notebook. \n", + "prefix = \"dd\"\n", + "\n", + "# NOTE: Please do not change the model_name, as it's required by the score.py file\n", + "model_name = \"driftmodel\"\n", + "image_name = \"{}driftimage\".format(prefix)\n", + "service_name = \"{}driftservice\".format(prefix)\n", + "\n", + "# optionally, set email address to receive an email alert for DataDrift\n", + "email_address = \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate Train/Testing Data\n", + "\n", + "For this demo, we will use NOAA weather data from [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). You may replace this step with your own dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "usaf_list = ['725724', '722149', '723090', '722159', '723910', '720279',\n", + " '725513', '725254', '726430', '720381', '723074', '726682',\n", + " '725486', '727883', '723177', '722075', '723086', '724053',\n", + " '725070', '722073', '726060', '725224', '725260', '724520',\n", + " '720305', '724020', '726510', '725126', '722523', '703333',\n", + " '722249', '722728', '725483', '722972', '724975', '742079',\n", + " '727468', '722193', '725624', '722030', '726380', '720309',\n", + " '722071', '720326', '725415', '724504', '725665', '725424',\n", + " '725066']\n", + "\n", + "columns = ['usaf', 'wban', 'datetime', 'latitude', 'longitude', 'elevation', 'windAngle', 'windSpeed', 'temperature', 'stationName', 'p_k']\n", + "\n", + "\n", + "def enrich_weather_noaa_data(noaa_df):\n", + " hours_in_day = 23\n", + " week_in_year = 52\n", + " \n", + " noaa_df[\"hour\"] = noaa_df[\"datetime\"].dt.hour\n", + " noaa_df[\"weekofyear\"] = noaa_df[\"datetime\"].dt.week\n", + " \n", + " noaa_df[\"sine_weekofyear\"] = noaa_df['datetime'].transform(lambda x: np.sin((2*np.pi*x.dt.week-1)/week_in_year))\n", + " noaa_df[\"cosine_weekofyear\"] = noaa_df['datetime'].transform(lambda x: np.cos((2*np.pi*x.dt.week-1)/week_in_year))\n", + "\n", + " noaa_df[\"sine_hourofday\"] = noaa_df['datetime'].transform(lambda x: np.sin(2*np.pi*x.dt.hour/hours_in_day))\n", + " noaa_df[\"cosine_hourofday\"] = noaa_df['datetime'].transform(lambda x: np.cos(2*np.pi*x.dt.hour/hours_in_day))\n", + " \n", + " return noaa_df\n", + "\n", + "def add_window_col(input_df):\n", + " shift_interval = pd.Timedelta('-7 days') # your X days interval\n", + " df_shifted = input_df.copy()\n", + " df_shifted['datetime'] = df_shifted['datetime'] - shift_interval\n", + " df_shifted.drop(list(input_df.columns.difference(['datetime', 'usaf', 'wban', 'sine_hourofday', 'temperature'])), axis=1, inplace=True)\n", + "\n", + " # merge, keeping only observations where -1 lag is present\n", + " df2 = pd.merge(input_df,\n", + " df_shifted,\n", + " on=['datetime', 'usaf', 'wban', 'sine_hourofday'],\n", + " how='inner', # use 'left' to keep observations without lags\n", + " suffixes=['', '-7'])\n", + " return df2\n", + "\n", + "def get_noaa_data(start_time, end_time, cols, station_list):\n", + " isd = NoaaIsdWeather(start_time, end_time, cols=cols)\n", + " # Read into Pandas data frame.\n", + " noaa_df = isd.to_pandas_dataframe()\n", + " noaa_df = noaa_df.rename(columns={\"stationName\": \"station_name\"})\n", + " \n", + " df_filtered = noaa_df[noaa_df[\"usaf\"].isin(station_list)]\n", + " df_filtered.reset_index(drop=True)\n", + " \n", + " # Enrich with time features\n", + " df_enriched = enrich_weather_noaa_data(df_filtered)\n", + " \n", + " return df_enriched\n", + "\n", + "def get_featurized_noaa_df(start_time, end_time, cols, station_list):\n", + " df_1 = get_noaa_data(start_time - timedelta(days=7), start_time - timedelta(seconds=1), cols, station_list)\n", + " df_2 = get_noaa_data(start_time, end_time, cols, station_list)\n", + " noaa_df = pd.concat([df_1, df_2])\n", + " \n", + " print(\"Adding window feature\")\n", + " df_window = add_window_col(noaa_df)\n", + " \n", + " cat_columns = df_window.dtypes == object\n", + " cat_columns = cat_columns[cat_columns == True]\n", + " \n", + " print(\"Encoding categorical columns\")\n", + " df_encoded = pd.get_dummies(df_window, columns=cat_columns.keys().tolist())\n", + " \n", + " print(\"Dropping unnecessary columns\")\n", + " df_featurized = df_encoded.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna().drop_duplicates()\n", + " \n", + " return df_featurized" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Train model on Jan 1 - 14, 2009 data\n", + "df = get_featurized_noaa_df(datetime(2009, 1, 1), datetime(2009, 1, 14, 23, 59, 59), columns, usaf_list)\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "label = \"temperature\"\n", + "x_df = df.drop(label, axis=1)\n", + "y_df = df[[label]]\n", + "x_train, x_test, y_train, y_test = train_test_split(df, y_df, test_size=0.2, random_state=223)\n", + "print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)\n", + "\n", + "training_dir = 'outputs/training'\n", + "training_file = \"training.csv\"\n", + "\n", + "# Generate training dataframe to register as Training Dataset\n", + "os.makedirs(training_dir, exist_ok=True)\n", + "training_df = pd.merge(x_train.drop(label, axis=1), y_train, left_index=True, right_index=True)\n", + "training_df.to_csv(training_dir + \"/\" + training_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create/Register Training Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset_name = \"dataset\"\n", + "name_suffix = datetime.utcnow().strftime(\"%Y-%m-%d-%H-%M-%S\")\n", + "snapshot_name = \"snapshot-{}\".format(name_suffix)\n", + "\n", + "dstore = ws.get_default_datastore()\n", + "dstore.upload(training_dir, \"data/training\", show_progress=True)\n", + "dpath = dstore.path(\"data/training/training.csv\")\n", + "trainingDataset = Dataset.auto_read_files(dpath, include_path=True)\n", + "trainingDataset = trainingDataset.register(workspace=ws, name=dataset_name, description=\"dset\", exist_ok=True)\n", + "\n", + "trainingDataSnapshot = trainingDataset.create_snapshot(snapshot_name=snapshot_name, compute_target=None, create_data_snapshot=True)\n", + "datasets = [(Dataset.Scenario.TRAINING, trainingDataSnapshot)]\n", + "print(\"dataset registration done.\\n\")\n", + "datasets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train and Save Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import lightgbm as lgb\n", + "\n", + "train = lgb.Dataset(data=x_train, \n", + " label=y_train)\n", + "\n", + "test = lgb.Dataset(data=x_test, \n", + " label=y_test,\n", + " reference=train)\n", + "\n", + "params = {'learning_rate' : 0.1,\n", + " 'boosting' : 'gbdt',\n", + " 'metric' : 'rmse',\n", + " 'feature_fraction' : 1,\n", + " 'bagging_fraction' : 1,\n", + " 'max_depth': 6,\n", + " 'num_leaves' : 31,\n", + " 'objective' : 'regression',\n", + " 'bagging_freq' : 1,\n", + " \"verbose\": -1,\n", + " 'min_data_per_leaf': 100}\n", + "\n", + "model = lgb.train(params, \n", + " num_boost_round=500,\n", + " train_set=train,\n", + " valid_sets=[train, test],\n", + " verbose_eval=50,\n", + " early_stopping_rounds=25)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_file = 'outputs/{}.pkl'.format(model_name)\n", + "\n", + "os.makedirs('outputs', exist_ok=True)\n", + "joblib.dump(model, model_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Register Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = Model.register(model_path=model_file,\n", + " model_name=model_name,\n", + " workspace=ws,\n", + " datasets=datasets)\n", + "\n", + "print(model_name, image_name, service_name, model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy Model To AKS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prepare Environment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn', 'joblib', 'lightgbm', 'pandas'],\n", + " pip_packages=['azureml-monitoring', 'azureml-sdk[automl]'])\n", + "\n", + "with open(\"myenv.yml\",\"w\") as f:\n", + " f.write(myenv.serialize_to_string())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Image creation may take up to 15 minutes.\n", + "\n", + "image_name = image_name + str(model.version)\n", + "\n", + "if not image_name in ws.images:\n", + " # Use the score.py defined in this directory as the execution script\n", + " # NOTE: The Model Data Collector must be enabled in the execution script for DataDrift to run correctly\n", + " image_config = ContainerImage.image_configuration(execution_script=\"score.py\",\n", + " runtime=\"python\",\n", + " conda_file=\"myenv.yml\",\n", + " description=\"Image with weather dataset model\")\n", + " image = ContainerImage.create(name=image_name,\n", + " models=[model],\n", + " image_config=image_config,\n", + " workspace=ws)\n", + "\n", + " image.wait_for_creation(show_output=True)\n", + "else:\n", + " image = ws.images[image_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Compute Target" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_name = 'dd-demo-e2e'\n", + "prov_config = AksCompute.provisioning_configuration()\n", + "\n", + "if not aks_name in ws.compute_targets:\n", + " aks_target = ComputeTarget.create(workspace=ws,\n", + " name=aks_name,\n", + " provisioning_configuration=prov_config)\n", + "\n", + " aks_target.wait_for_completion(show_output=True)\n", + " print(aks_target.provisioning_state)\n", + " print(aks_target.provisioning_errors)\n", + "else:\n", + " aks_target=ws.compute_targets[aks_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploy Service" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_service_name = service_name\n", + "\n", + "if not aks_service_name in ws.webservices:\n", + " aks_config = AksWebservice.deploy_configuration(collect_model_data=True, enable_app_insights=True)\n", + " aks_service = Webservice.deploy_from_image(workspace=ws,\n", + " name=aks_service_name,\n", + " image=image,\n", + " deployment_config=aks_config,\n", + " deployment_target=aks_target)\n", + " aks_service.wait_for_deployment(show_output=True)\n", + " print(aks_service.state)\n", + "else:\n", + " aks_service = ws.webservices[aks_service_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run DataDrift Analysis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Send Scoring Data to Service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download Scoring Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Score Model on March 15, 2016 data\n", + "scoring_df = get_noaa_data(datetime(2016, 3, 15) - timedelta(days=7), datetime(2016, 3, 16), columns, usaf_list)\n", + "# Add the window feature column\n", + "scoring_df = add_window_col(scoring_df)\n", + "\n", + "# Drop features not used by the model\n", + "print(\"Dropping unnecessary columns\")\n", + "scoring_df = scoring_df.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna()\n", + "scoring_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# One Hot Encode the scoring dataset to match the training dataset schema\n", + "columns_dict = model.datasets[\"training\"][0].get_profile().columns\n", + "extra_cols = ('Path', 'Column1')\n", + "for k in extra_cols:\n", + " columns_dict.pop(k, None)\n", + "training_columns = list(columns_dict.keys())\n", + "\n", + "categorical_columns = scoring_df.dtypes == object\n", + "categorical_columns = categorical_columns[categorical_columns == True]\n", + "\n", + "test_df = pd.get_dummies(scoring_df[categorical_columns.keys().tolist()])\n", + "encoded_df = scoring_df.join(test_df)\n", + "\n", + "# Populate missing OHE columns with 0 values to match traning dataset schema\n", + "difference = list(set(training_columns) - set(encoded_df.columns.tolist()))\n", + "for col in difference:\n", + " encoded_df[col] = 0\n", + "encoded_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Serialize dataframe to list of row dictionaries\n", + "encoded_dict = encoded_df.to_dict('records')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit Scoring Data to Service" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "# retreive the API keys. AML generates two keys.\n", + "key1, key2 = aks_service.get_keys()\n", + "\n", + "total_count = len(scoring_df)\n", + "i = 0\n", + "load = []\n", + "for row in encoded_dict:\n", + " load.append(row)\n", + " i = i + 1\n", + " if i % 100 == 0:\n", + " payload = json.dumps({\"data\": load})\n", + " \n", + " # construct raw HTTP request and send to the service\n", + " payload_binary = bytes(payload,encoding = 'utf8')\n", + " headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n", + " resp = requests.post(aks_service.scoring_uri, payload_binary, headers=headers)\n", + " \n", + " print(\"prediction:\", resp.content, \"Progress: {}/{}\".format(i, total_count)) \n", + "\n", + " load = []\n", + " time.sleep(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure DataDrift" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "services = [service_name]\n", + "start = datetime.now() - timedelta(days=2)\n", + "end = datetime(year=2020, month=1, day=22, hour=15, minute=16)\n", + "feature_list = ['usaf', 'wban', 'latitude', 'longitude', 'station_name', 'p_k', 'sine_hourofday', 'cosine_hourofday', 'temperature-7']\n", + "alert_config = AlertConfiguration([email_address]) if email_address else None\n", + "\n", + "# there will be an exception indicating using get() method if DataDrift object already exist\n", + "try:\n", + " datadrift = DataDriftDetector.create(ws, model.name, model.version, services, frequency=\"Day\", alert_config=alert_config)\n", + "except KeyError:\n", + " datadrift = DataDriftDetector.get(ws, model.name, model.version)\n", + " \n", + "print(\"Details of DataDrift Object:\\n{}\".format(datadrift))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run an Adhoc DataDriftDetector Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "target_date = datetime.today()\n", + "run = datadrift.run(target_date, services, feature_list=feature_list, create_compute_target=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(ws, datadrift._id)\n", + "dd_run = Run(experiment=exp, run_id=run)\n", + "RunDetails(dd_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get Drift Analysis Results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(dd_run.get_children())\n", + "for child in children:\n", + " child.wait_for_completion()\n", + "\n", + "drift_metrics = datadrift.get_output(start_time=start, end_time=end)\n", + "drift_metrics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Show all drift figures, one per serivice.\n", + "# If setting with_details is False (by default), only drift will be shown; if it's True, all details will be shown.\n", + "\n", + "drift_figures = datadrift.show(with_details=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Enable DataDrift Schedule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datadrift.enable_schedule()" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "rafarmah" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml b/how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml new file mode 100644 index 00000000..ca6bae19 --- /dev/null +++ b/how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml @@ -0,0 +1,4 @@ +name: model-register-and-deploy +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/accelerated-models/accelerated-models-object-detection.ipynb b/how-to-use-azureml/deployment/accelerated-models/accelerated-models-object-detection.ipynb index b06edc92..5191da87 100644 --- a/how-to-use-azureml/deployment/accelerated-models/accelerated-models-object-detection.ipynb +++ b/how-to-use-azureml/deployment/accelerated-models/accelerated-models-object-detection.ipynb @@ -1,494 +1,494 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Azure ML Hardware Accelerated Object Detection" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial will show you how to deploy an object detection service based on the SSD-VGG model in just a few minutes using the Azure Machine Learning Accelerated AI service.\n", - "\n", - "We will use the SSD-VGG model accelerated on an FPGA. Our Accelerated Models Service handles translating deep neural networks (DNN) into an FPGA program.\n", - "\n", - "The steps in this notebook are: \n", - "1. [Setup Environment](#set-up-environment)\n", - "* [Construct Model](#construct-model)\n", - " * Image Preprocessing\n", - " * Featurizer\n", - " * Save Model\n", - " * Save input and output tensor names\n", - "* [Create Image](#create-image)\n", - "* [Deploy Image](#deploy-image)\n", - "* [Test the Service](#test-service)\n", - " * Create Client\n", - " * Serve the model\n", - "* [Cleanup](#cleanup)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 1. Set up Environment\n", - "### 1.a. Imports" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import tensorflow as tf" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.b. Retrieve Workspace\n", - "If you haven't created a Workspace, please follow [this notebook](\"../../../configuration.ipynb\") to do so. If you have, run the codeblock below to retrieve it. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "\n", - "ws = Workspace.from_config()\n", - "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 2. Construct model\n", - "### 2.a. Image preprocessing\n", - "We'd like our service to accept JPEG images as input. However the input to SSD-VGG is a float tensor of shape \\[1, 300, 300, 3\\]. The first dimension is batch, then height, width, and channels (i.e. NHWC). To bridge this gap, we need code that decodes JPEG images and resizes them appropriately for input to SSD-VGG. The Accelerated AI service can execute TensorFlow graphs as part of the service and we'll use that ability to do the image preprocessing. This code defines a TensorFlow graph that preprocesses an array of JPEG images (as TensorFlow strings) and produces a tensor that is ready to be featurized by SSD-VGG.\n", - "\n", - "**Note:** Expect to see TF deprecation warnings until we port our SDK over to use Tensorflow 2.0." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Input images as a two-dimensional tensor containing an arbitrary number of images represented a strings\n", - "import azureml.accel.models.utils as utils\n", - "tf.reset_default_graph()\n", - "\n", - "in_images = tf.placeholder(tf.string)\n", - "image_tensors = utils.preprocess_array(in_images, output_width=300, output_height=300, preserve_aspect_ratio=False)\n", - "print(image_tensors.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.b. Featurizer\n", - "The SSD-VGG model is different from our other models in that it generates 12 tensor outputs. These corresponds to x,y displacements of the anchor boxes and the detection confidence (for 21 classes). Because these outputs are not convenient to work with, we will later use a pre-defined post-processing utility to transform the outputs into a simplified list of bounding boxes with their respective class and confidence.\n", - "\n", - "For more information about the output tensors, take this example: the output tensor 'ssd_300_vgg/block4_box/Reshape_1:0' has a shape of [None, 37, 37, 4, 21]. This gives the pre-softmax confidence for 4 anchor boxes situated at each site of a 37 x 37 grid imposed on the image, one confidence score for each of the 21 classes. The first dimension is the batch dimension. Likewise, 'ssd_300_vgg/block4_box/Reshape:0' has shape [None, 37, 37, 4, 4] and encodes the (cx, cy) center shift and rescaling (sw, sh) relative to each anchor box. Refer to the [SSD-VGG paper](https://arxiv.org/abs/1512.02325) to understand how these are computed. The other 10 tensors are defined similarly." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.accel.models import SsdVgg\n", - "\n", - "saved_model_dir = os.path.join(os.path.expanduser('~'), 'models')\n", - "model_graph = SsdVgg(saved_model_dir, is_frozen = True)\n", - "\n", - "print('SSD-VGG Input Tensors:')\n", - "for idx, input_name in enumerate(model_graph.input_tensor_list):\n", - " print('{}, {}'.format(input_name, model_graph.get_input_dims(idx)))\n", - " \n", - "print('SSD-VGG Output Tensors:')\n", - "for idx, output_name in enumerate(model_graph.output_tensor_list):\n", - " print('{}, {}'.format(output_name, model_graph.get_output_dims(idx)))\n", - "\n", - "ssd_outputs = model_graph.import_graph_def(image_tensors, is_training=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.c. Save Model\n", - "Now that we loaded both parts of the tensorflow graph (preprocessor and SSD-VGG featurizer), we can save the graph and associated variables to a directory which we can register as an Azure ML Model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_name = \"ssdvgg\"\n", - "model_save_path = os.path.join(saved_model_dir, model_name, \"saved_model\")\n", - "print(\"Saving model in {}\".format(model_save_path))\n", - "\n", - "output_map = {}\n", - "for i, output in enumerate(ssd_outputs):\n", - " output_map['out_{}'.format(i)] = output\n", - "\n", - "with tf.Session() as sess:\n", - " model_graph.restore_weights(sess)\n", - " tf.saved_model.simple_save(sess, \n", - " model_save_path, \n", - " inputs={'images': in_images}, \n", - " outputs=output_map)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.d. Important! Save names of input and output tensors\n", - "\n", - "These input and output tensors that were created during the preprocessing and classifier steps are also going to be used when **converting the model** to an Accelerated Model that can run on FPGA's and for **making an inferencing request**. It is very important to save this information!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "register model from file" - ] - }, - "outputs": [], - "source": [ - "input_tensors = in_images.name\n", - "# We will use the list of output tensors during inferencing\n", - "output_tensors = [output.name for output in ssd_outputs]\n", - "# However, for multiple output tensors, our AccelOnnxConverter will \n", - "# accept comma-delimited strings (lists will cause error)\n", - "output_tensors_str = \",\".join(output_tensors)\n", - "\n", - "print(input_tensors)\n", - "print(output_tensors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 3. Create AccelContainerImage\n", - "Below we will execute all the same steps as in the [Quickstart](./accelerated-models-quickstart.ipynb#create-image) to package the model we have saved locally into an accelerated Docker image saved in our workspace. To complete all the steps, it may take a few minutes. For more details on each step, check out the [Quickstart section on model registration](./accelerated-models-quickstart.ipynb#register-model)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "from azureml.core.model import Model\n", - "from azureml.core.image import Image\n", - "from azureml.accel import AccelOnnxConverter\n", - "from azureml.accel import AccelContainerImage\n", - "\n", - "# Retrieve workspace\n", - "ws = Workspace.from_config()\n", - "print(\"Successfully retrieved workspace:\", ws.name, ws.resource_group, ws.location, ws.subscription_id, '\\n')\n", - "\n", - "# Register model\n", - "registered_model = Model.register(workspace = ws,\n", - " model_path = model_save_path,\n", - " model_name = model_name)\n", - "print(\"Successfully registered: \", registered_model.name, registered_model.description, registered_model.version, '\\n', sep = '\\t')\n", - "\n", - "# Convert model\n", - "convert_request = AccelOnnxConverter.convert_tf_model(ws, registered_model, input_tensors, output_tensors_str)\n", - "# If it fails, you can run wait_for_completion again with show_output=True.\n", - "convert_request.wait_for_completion(show_output=False)\n", - "converted_model = convert_request.result\n", - "print(\"\\nSuccessfully converted: \", converted_model.name, converted_model.url, converted_model.version, \n", - " converted_model.id, converted_model.created_time, '\\n')\n", - "\n", - "# Package into AccelContainerImage\n", - "image_config = AccelContainerImage.image_configuration()\n", - "# Image name must be lowercase\n", - "image_name = \"{}-image\".format(model_name)\n", - "image = Image.create(name = image_name,\n", - " models = [converted_model],\n", - " image_config = image_config, \n", - " workspace = ws)\n", - "image.wait_for_creation()\n", - "print(\"Created AccelContainerImage: {} {} {}\\n\".format(image.name, image.creation_state, image.image_location))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 4. Deploy image\n", - "Once you have an Azure ML Accelerated Image in your Workspace, you can deploy it to two destinations, to a Databox Edge machine or to an AKS cluster. \n", - "\n", - "### 4.a. Deploy to Databox Edge Machine using IoT Hub\n", - "See the sample [here](https://github.com/Azure-Samples/aml-real-time-ai/) for using the Azure IoT CLI extension for deploying your Docker image to your Databox Edge Machine.\n", - "\n", - "### 4.b. Deploy to AKS Cluster\n", - "Same as in the [Quickstart section on image deployment](./accelerated-models-quickstart.ipynb#deploy-image), we are going to create an AKS cluster with FPGA-enabled machines, then deploy our service to it.\n", - "#### Create AKS ComputeTarget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import AksCompute, ComputeTarget\n", - "\n", - "# Uses the specific FPGA enabled VM (sku: Standard_PB6s)\n", - "# Standard_PB6s are available in: eastus, westus2, westeurope, southeastasia\n", - "prov_config = AksCompute.provisioning_configuration(vm_size = \"Standard_PB6s\",\n", - " agent_count = 1, \n", - " location = \"eastus\")\n", - "\n", - "aks_name = 'aks-pb6-obj'\n", - "# Create the cluster\n", - "aks_target = ComputeTarget.create(workspace = ws, \n", - " name = aks_name, \n", - " provisioning_configuration = prov_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Provisioning an AKS cluster might take awhile (15 or so minutes), and we want to wait until it's successfully provisioned before we can deploy a service to it. If you interrupt this cell, provisioning of the cluster will continue. You can re-run it or check the status in your Workspace under Compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_target.wait_for_completion(show_output = True)\n", - "print(aks_target.provisioning_state)\n", - "print(aks_target.provisioning_errors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Deploy AccelContainerImage to AKS ComputeTarget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import Webservice, AksWebservice\n", - "\n", - "# Set the web service configuration (for creating a test service, we don't want autoscale enabled)\n", - "# Authentication is enabled by default, but for testing we specify False\n", - "aks_config = AksWebservice.deploy_configuration(autoscale_enabled=False,\n", - " num_replicas=1,\n", - " auth_enabled = False)\n", - "\n", - "aks_service_name ='my-aks-service'\n", - "\n", - "aks_service = Webservice.deploy_from_image(workspace = ws,\n", - " name = aks_service_name,\n", - " image = image,\n", - " deployment_config = aks_config,\n", - " deployment_target = aks_target)\n", - "aks_service.wait_for_deployment(show_output = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 5. Test the service\n", - "\n", - "### 5.a. Create Client\n", - "The image supports gRPC and the TensorFlow Serving \"predict\" API. We have a client that can call into the docker image to get predictions. \n", - "\n", - "**Note:** If you chose to use auth_enabled=True when creating your AksWebservice.deploy_configuration(), see documentation [here](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.webservice(class)?view=azure-ml-py#get-keys--) on how to retrieve your keys and use either key as an argument to PredictionClient(...,access_token=key).", - "\n", - "**WARNING:** If you are running on Azure Notebooks free compute, you will not be able to make outgoing calls to your service. Try locating your client on a different machine to consume it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Using the grpc client in AzureML Accelerated Models SDK\n", - "from azureml.accel.client import PredictionClient\n", - "\n", - "address = aks_service.scoring_uri\n", - "ssl_enabled = address.startswith(\"https\")\n", - "address = address[address.find('/')+2:].strip('/')\n", - "port = 443 if ssl_enabled else 80\n", - "\n", - "# Initialize AzureML Accelerated Models client\n", - "client = PredictionClient(address=address,\n", - " port=port,\n", - " use_ssl=ssl_enabled,\n", - " service_name=aks_service.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can adapt the client [code](https://github.com/Azure/aml-real-time-ai/blob/master/pythonlib/amlrealtimeai/client.py) to meet your needs. There is also an example C# [client](https://github.com/Azure/aml-real-time-ai/blob/master/sample-clients/csharp).\n", - "\n", - "The service provides an API that is compatible with TensorFlow Serving. There are instructions to download a sample client [here](https://www.tensorflow.org/serving/setup)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### 5.b. Serve the model\n", - "The SSD-VGG model returns the confidence and bounding boxes for all possible anchor boxes. As mentioned earlier, we will use a post-processing routine to transform this into a list of bounding boxes (y1, x1, y2, x2) where x, y are fractional coordinates measured from left and top respectively. A respective list of classes and scores is also returned to tag each bounding box. Below we make use of this information to draw the bounding boxes on top the original image. Note that in the post-processing routine we select a confidence threshold of 0.5." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import cv2\n", - "from matplotlib import pyplot as plt\n", - "\n", - "colors_tableau = [(255, 255, 255), (31, 119, 180), (174, 199, 232), (255, 127, 14), (255, 187, 120),\n", - " (44, 160, 44), (152, 223, 138), (214, 39, 40), (255, 152, 150),\n", - " (148, 103, 189), (197, 176, 213), (140, 86, 75), (196, 156, 148),\n", - " (227, 119, 194), (247, 182, 210), (127, 127, 127), (199, 199, 199),\n", - " (188, 189, 34), (219, 219, 141), (23, 190, 207), (158, 218, 229)]\n", - "\n", - "\n", - "def draw_boxes_on_img(img, classes, scores, bboxes, thickness=2):\n", - " shape = img.shape\n", - " for i in range(bboxes.shape[0]):\n", - " bbox = bboxes[i]\n", - " color = colors_tableau[classes[i]]\n", - " # Draw bounding box...\n", - " p1 = (int(bbox[0] * shape[0]), int(bbox[1] * shape[1]))\n", - " p2 = (int(bbox[2] * shape[0]), int(bbox[3] * shape[1]))\n", - " cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)\n", - " # Draw text...\n", - " s = '%s/%.3f' % (classes[i], scores[i])\n", - " p1 = (p1[0]-5, p1[1])\n", - " cv2.putText(img, s, p1[::-1], cv2.FONT_HERSHEY_DUPLEX, 0.4, color, 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import azureml.accel._external.ssdvgg_utils as ssdvgg_utils\n", - "\n", - "result = client.score_file(path=\"meeting.jpg\", input_name=input_tensors, outputs=output_tensors)\n", - "classes, scores, bboxes = ssdvgg_utils.postprocess(result, select_threshold=0.5)\n", - "\n", - "img = cv2.imread('meeting.jpg', 1)\n", - "img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n", - "draw_boxes_on_img(img, classes, scores, bboxes)\n", - "plt.imshow(img)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 6. Cleanup\n", - "It's important to clean up your resources, so that you won't incur unnecessary costs. In the [next notebook](./accelerated-models-training.ipynb) you will learn how to train a classfier on a new dataset using transfer learning." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_service.delete()\n", - "aks_target.delete()\n", - "image.delete()\n", - "registered_model.delete()\n", - "converted_model.delete()" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "coverste" - }, - { - "name": "paledger" - }, - { - "name": "sukha" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Azure ML Hardware Accelerated Object Detection" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial will show you how to deploy an object detection service based on the SSD-VGG model in just a few minutes using the Azure Machine Learning Accelerated AI service.\n", + "\n", + "We will use the SSD-VGG model accelerated on an FPGA. Our Accelerated Models Service handles translating deep neural networks (DNN) into an FPGA program.\n", + "\n", + "The steps in this notebook are: \n", + "1. [Setup Environment](#set-up-environment)\n", + "* [Construct Model](#construct-model)\n", + " * Image Preprocessing\n", + " * Featurizer\n", + " * Save Model\n", + " * Save input and output tensor names\n", + "* [Create Image](#create-image)\n", + "* [Deploy Image](#deploy-image)\n", + "* [Test the Service](#test-service)\n", + " * Create Client\n", + " * Serve the model\n", + "* [Cleanup](#cleanup)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 1. Set up Environment\n", + "### 1.a. Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import tensorflow as tf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.b. Retrieve Workspace\n", + "If you haven't created a Workspace, please follow [this notebook](\"../../../configuration.ipynb\") to do so. If you have, run the codeblock below to retrieve it. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "\n", + "ws = Workspace.from_config()\n", + "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 2. Construct model\n", + "### 2.a. Image preprocessing\n", + "We'd like our service to accept JPEG images as input. However the input to SSD-VGG is a float tensor of shape \\[1, 300, 300, 3\\]. The first dimension is batch, then height, width, and channels (i.e. NHWC). To bridge this gap, we need code that decodes JPEG images and resizes them appropriately for input to SSD-VGG. The Accelerated AI service can execute TensorFlow graphs as part of the service and we'll use that ability to do the image preprocessing. This code defines a TensorFlow graph that preprocesses an array of JPEG images (as TensorFlow strings) and produces a tensor that is ready to be featurized by SSD-VGG.\n", + "\n", + "**Note:** Expect to see TF deprecation warnings until we port our SDK over to use Tensorflow 2.0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Input images as a two-dimensional tensor containing an arbitrary number of images represented a strings\n", + "import azureml.accel.models.utils as utils\n", + "tf.reset_default_graph()\n", + "\n", + "in_images = tf.placeholder(tf.string)\n", + "image_tensors = utils.preprocess_array(in_images, output_width=300, output_height=300, preserve_aspect_ratio=False)\n", + "print(image_tensors.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.b. Featurizer\n", + "The SSD-VGG model is different from our other models in that it generates 12 tensor outputs. These corresponds to x,y displacements of the anchor boxes and the detection confidence (for 21 classes). Because these outputs are not convenient to work with, we will later use a pre-defined post-processing utility to transform the outputs into a simplified list of bounding boxes with their respective class and confidence.\n", + "\n", + "For more information about the output tensors, take this example: the output tensor 'ssd_300_vgg/block4_box/Reshape_1:0' has a shape of [None, 37, 37, 4, 21]. This gives the pre-softmax confidence for 4 anchor boxes situated at each site of a 37 x 37 grid imposed on the image, one confidence score for each of the 21 classes. The first dimension is the batch dimension. Likewise, 'ssd_300_vgg/block4_box/Reshape:0' has shape [None, 37, 37, 4, 4] and encodes the (cx, cy) center shift and rescaling (sw, sh) relative to each anchor box. Refer to the [SSD-VGG paper](https://arxiv.org/abs/1512.02325) to understand how these are computed. The other 10 tensors are defined similarly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.accel.models import SsdVgg\n", + "\n", + "saved_model_dir = os.path.join(os.path.expanduser('~'), 'models')\n", + "model_graph = SsdVgg(saved_model_dir, is_frozen = True)\n", + "\n", + "print('SSD-VGG Input Tensors:')\n", + "for idx, input_name in enumerate(model_graph.input_tensor_list):\n", + " print('{}, {}'.format(input_name, model_graph.get_input_dims(idx)))\n", + " \n", + "print('SSD-VGG Output Tensors:')\n", + "for idx, output_name in enumerate(model_graph.output_tensor_list):\n", + " print('{}, {}'.format(output_name, model_graph.get_output_dims(idx)))\n", + "\n", + "ssd_outputs = model_graph.import_graph_def(image_tensors, is_training=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.c. Save Model\n", + "Now that we loaded both parts of the tensorflow graph (preprocessor and SSD-VGG featurizer), we can save the graph and associated variables to a directory which we can register as an Azure ML Model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_name = \"ssdvgg\"\n", + "model_save_path = os.path.join(saved_model_dir, model_name, \"saved_model\")\n", + "print(\"Saving model in {}\".format(model_save_path))\n", + "\n", + "output_map = {}\n", + "for i, output in enumerate(ssd_outputs):\n", + " output_map['out_{}'.format(i)] = output\n", + "\n", + "with tf.Session() as sess:\n", + " model_graph.restore_weights(sess)\n", + " tf.saved_model.simple_save(sess, \n", + " model_save_path, \n", + " inputs={'images': in_images}, \n", + " outputs=output_map)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.d. Important! Save names of input and output tensors\n", + "\n", + "These input and output tensors that were created during the preprocessing and classifier steps are also going to be used when **converting the model** to an Accelerated Model that can run on FPGA's and for **making an inferencing request**. It is very important to save this information!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "register model from file" + ] + }, + "outputs": [], + "source": [ + "input_tensors = in_images.name\n", + "# We will use the list of output tensors during inferencing\n", + "output_tensors = [output.name for output in ssd_outputs]\n", + "# However, for multiple output tensors, our AccelOnnxConverter will \n", + "# accept comma-delimited strings (lists will cause error)\n", + "output_tensors_str = \",\".join(output_tensors)\n", + "\n", + "print(input_tensors)\n", + "print(output_tensors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 3. Create AccelContainerImage\n", + "Below we will execute all the same steps as in the [Quickstart](./accelerated-models-quickstart.ipynb#create-image) to package the model we have saved locally into an accelerated Docker image saved in our workspace. To complete all the steps, it may take a few minutes. For more details on each step, check out the [Quickstart section on model registration](./accelerated-models-quickstart.ipynb#register-model)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "from azureml.core.model import Model\n", + "from azureml.core.image import Image\n", + "from azureml.accel import AccelOnnxConverter\n", + "from azureml.accel import AccelContainerImage\n", + "\n", + "# Retrieve workspace\n", + "ws = Workspace.from_config()\n", + "print(\"Successfully retrieved workspace:\", ws.name, ws.resource_group, ws.location, ws.subscription_id, '\\n')\n", + "\n", + "# Register model\n", + "registered_model = Model.register(workspace = ws,\n", + " model_path = model_save_path,\n", + " model_name = model_name)\n", + "print(\"Successfully registered: \", registered_model.name, registered_model.description, registered_model.version, '\\n', sep = '\\t')\n", + "\n", + "# Convert model\n", + "convert_request = AccelOnnxConverter.convert_tf_model(ws, registered_model, input_tensors, output_tensors_str)\n", + "# If it fails, you can run wait_for_completion again with show_output=True.\n", + "convert_request.wait_for_completion(show_output=False)\n", + "converted_model = convert_request.result\n", + "print(\"\\nSuccessfully converted: \", converted_model.name, converted_model.url, converted_model.version, \n", + " converted_model.id, converted_model.created_time, '\\n')\n", + "\n", + "# Package into AccelContainerImage\n", + "image_config = AccelContainerImage.image_configuration()\n", + "# Image name must be lowercase\n", + "image_name = \"{}-image\".format(model_name)\n", + "image = Image.create(name = image_name,\n", + " models = [converted_model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "image.wait_for_creation()\n", + "print(\"Created AccelContainerImage: {} {} {}\\n\".format(image.name, image.creation_state, image.image_location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 4. Deploy image\n", + "Once you have an Azure ML Accelerated Image in your Workspace, you can deploy it to two destinations, to a Databox Edge machine or to an AKS cluster. \n", + "\n", + "### 4.a. Deploy to Databox Edge Machine using IoT Hub\n", + "See the sample [here](https://github.com/Azure-Samples/aml-real-time-ai/) for using the Azure IoT CLI extension for deploying your Docker image to your Databox Edge Machine.\n", + "\n", + "### 4.b. Deploy to AKS Cluster\n", + "Same as in the [Quickstart section on image deployment](./accelerated-models-quickstart.ipynb#deploy-image), we are going to create an AKS cluster with FPGA-enabled machines, then deploy our service to it.\n", + "#### Create AKS ComputeTarget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AksCompute, ComputeTarget\n", + "\n", + "# Uses the specific FPGA enabled VM (sku: Standard_PB6s)\n", + "# Standard_PB6s are available in: eastus, westus2, westeurope, southeastasia\n", + "prov_config = AksCompute.provisioning_configuration(vm_size = \"Standard_PB6s\",\n", + " agent_count = 1, \n", + " location = \"eastus\")\n", + "\n", + "aks_name = 'aks-pb6-obj'\n", + "# Create the cluster\n", + "aks_target = ComputeTarget.create(workspace = ws, \n", + " name = aks_name, \n", + " provisioning_configuration = prov_config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Provisioning an AKS cluster might take awhile (15 or so minutes), and we want to wait until it's successfully provisioned before we can deploy a service to it. If you interrupt this cell, provisioning of the cluster will continue. You can re-run it or check the status in your Workspace under Compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_target.wait_for_completion(show_output = True)\n", + "print(aks_target.provisioning_state)\n", + "print(aks_target.provisioning_errors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Deploy AccelContainerImage to AKS ComputeTarget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice, AksWebservice\n", + "\n", + "# Set the web service configuration (for creating a test service, we don't want autoscale enabled)\n", + "# Authentication is enabled by default, but for testing we specify False\n", + "aks_config = AksWebservice.deploy_configuration(autoscale_enabled=False,\n", + " num_replicas=1,\n", + " auth_enabled = False)\n", + "\n", + "aks_service_name ='my-aks-service'\n", + "\n", + "aks_service = Webservice.deploy_from_image(workspace = ws,\n", + " name = aks_service_name,\n", + " image = image,\n", + " deployment_config = aks_config,\n", + " deployment_target = aks_target)\n", + "aks_service.wait_for_deployment(show_output = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 5. Test the service\n", + "\n", + "### 5.a. Create Client\n", + "The image supports gRPC and the TensorFlow Serving \"predict\" API. We have a client that can call into the docker image to get predictions. \n", + "\n", + "**Note:** If you chose to use auth_enabled=True when creating your AksWebservice.deploy_configuration(), see documentation [here](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.webservice(class)?view=azure-ml-py#get-keys--) on how to retrieve your keys and use either key as an argument to PredictionClient(...,access_token=key).", + "\n", + "**WARNING:** If you are running on Azure Notebooks free compute, you will not be able to make outgoing calls to your service. Try locating your client on a different machine to consume it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Using the grpc client in AzureML Accelerated Models SDK\n", + "from azureml.accel.client import PredictionClient\n", + "\n", + "address = aks_service.scoring_uri\n", + "ssl_enabled = address.startswith(\"https\")\n", + "address = address[address.find('/')+2:].strip('/')\n", + "port = 443 if ssl_enabled else 80\n", + "\n", + "# Initialize AzureML Accelerated Models client\n", + "client = PredictionClient(address=address,\n", + " port=port,\n", + " use_ssl=ssl_enabled,\n", + " service_name=aks_service.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can adapt the client [code](https://github.com/Azure/aml-real-time-ai/blob/master/pythonlib/amlrealtimeai/client.py) to meet your needs. There is also an example C# [client](https://github.com/Azure/aml-real-time-ai/blob/master/sample-clients/csharp).\n", + "\n", + "The service provides an API that is compatible with TensorFlow Serving. There are instructions to download a sample client [here](https://www.tensorflow.org/serving/setup)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 5.b. Serve the model\n", + "The SSD-VGG model returns the confidence and bounding boxes for all possible anchor boxes. As mentioned earlier, we will use a post-processing routine to transform this into a list of bounding boxes (y1, x1, y2, x2) where x, y are fractional coordinates measured from left and top respectively. A respective list of classes and scores is also returned to tag each bounding box. Below we make use of this information to draw the bounding boxes on top the original image. Note that in the post-processing routine we select a confidence threshold of 0.5." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import cv2\n", + "from matplotlib import pyplot as plt\n", + "\n", + "colors_tableau = [(255, 255, 255), (31, 119, 180), (174, 199, 232), (255, 127, 14), (255, 187, 120),\n", + " (44, 160, 44), (152, 223, 138), (214, 39, 40), (255, 152, 150),\n", + " (148, 103, 189), (197, 176, 213), (140, 86, 75), (196, 156, 148),\n", + " (227, 119, 194), (247, 182, 210), (127, 127, 127), (199, 199, 199),\n", + " (188, 189, 34), (219, 219, 141), (23, 190, 207), (158, 218, 229)]\n", + "\n", + "\n", + "def draw_boxes_on_img(img, classes, scores, bboxes, thickness=2):\n", + " shape = img.shape\n", + " for i in range(bboxes.shape[0]):\n", + " bbox = bboxes[i]\n", + " color = colors_tableau[classes[i]]\n", + " # Draw bounding box...\n", + " p1 = (int(bbox[0] * shape[0]), int(bbox[1] * shape[1]))\n", + " p2 = (int(bbox[2] * shape[0]), int(bbox[3] * shape[1]))\n", + " cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)\n", + " # Draw text...\n", + " s = '%s/%.3f' % (classes[i], scores[i])\n", + " p1 = (p1[0]-5, p1[1])\n", + " cv2.putText(img, s, p1[::-1], cv2.FONT_HERSHEY_DUPLEX, 0.4, color, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.accel._external.ssdvgg_utils as ssdvgg_utils\n", + "\n", + "result = client.score_file(path=\"meeting.jpg\", input_name=input_tensors, outputs=output_tensors)\n", + "classes, scores, bboxes = ssdvgg_utils.postprocess(result, select_threshold=0.5)\n", + "\n", + "img = cv2.imread('meeting.jpg', 1)\n", + "img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n", + "draw_boxes_on_img(img, classes, scores, bboxes)\n", + "plt.imshow(img)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 6. Cleanup\n", + "It's important to clean up your resources, so that you won't incur unnecessary costs. In the [next notebook](./accelerated-models-training.ipynb) you will learn how to train a classfier on a new dataset using transfer learning." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_service.delete()\n", + "aks_target.delete()\n", + "image.delete()\n", + "registered_model.delete()\n", + "converted_model.delete()" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "coverste" + }, + { + "name": "paledger" + }, + { + "name": "sukha" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/deployment/accelerated-models/accelerated-models-quickstart.ipynb b/how-to-use-azureml/deployment/accelerated-models/accelerated-models-quickstart.ipynb index 76c69c92..8589b1dd 100644 --- a/how-to-use-azureml/deployment/accelerated-models/accelerated-models-quickstart.ipynb +++ b/how-to-use-azureml/deployment/accelerated-models/accelerated-models-quickstart.ipynb @@ -1,548 +1,548 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Azure ML Hardware Accelerated Models Quickstart" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial will show you how to deploy an image recognition service based on the ResNet 50 classifier using the Azure Machine Learning Accelerated Models service. Get more information about our service from our [documentation](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-accelerate-with-fpgas), [API reference](https://docs.microsoft.com/en-us/python/api/azureml-accel-models/azureml.accel?view=azure-ml-py), or [forum](https://aka.ms/aml-forum).\n", - "\n", - "We will use an accelerated ResNet50 featurizer running on an FPGA. Our Accelerated Models Service handles translating deep neural networks (DNN) into an FPGA program.\n", - "\n", - "For more information about using other models besides Resnet50, see the [README](./README.md).\n", - "\n", - "The steps covered in this notebook are: \n", - "1. [Set up environment](#set-up-environment)\n", - "* [Construct model](#construct-model)\n", - " * Image Preprocessing\n", - " * Featurizer (Resnet50)\n", - " * Classifier\n", - " * Save Model\n", - "* [Register Model](#register-model)\n", - "* [Convert into Accelerated Model](#convert-model)\n", - "* [Create Image](#create-image)\n", - "* [Deploy](#deploy-image)\n", - "* [Test service](#test-service)\n", - "* [Clean-up](#clean-up)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 1. Set up environment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import tensorflow as tf" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrieve Workspace\n", - "If you haven't created a Workspace, please follow [this notebook](https://github.com/Azure/MachineLearningNotebooks/blob/master/configuration.ipynb) to do so. If you have, run the codeblock below to retrieve it. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "\n", - "ws = Workspace.from_config()\n", - "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 2. Construct model\n", - "\n", - "There are three parts to the model we are deploying: pre-processing, featurizer with ResNet50, and classifier with ImageNet dataset. Then we will save this complete Tensorflow model graph locally before registering it to your Azure ML Workspace.\n", - "\n", - "### 2.a. Image preprocessing\n", - "We'd like our service to accept JPEG images as input. However the input to ResNet50 is a tensor. So we need code that decodes JPEG images and does the preprocessing required by ResNet50. The Accelerated AI service can execute TensorFlow graphs as part of the service and we'll use that ability to do the image preprocessing. This code defines a TensorFlow graph that preprocesses an array of JPEG images (as strings) and produces a tensor that is ready to be featurized by ResNet50.\n", - "\n", - "**Note:** Expect to see TF deprecation warnings until we port our SDK over to use Tensorflow 2.0." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Input images as a two-dimensional tensor containing an arbitrary number of images represented a strings\n", - "import azureml.accel.models.utils as utils\n", - "tf.reset_default_graph()\n", - "\n", - "in_images = tf.placeholder(tf.string)\n", - "image_tensors = utils.preprocess_array(in_images)\n", - "print(image_tensors.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.b. Featurizer\n", - "We use ResNet50 as a featurizer. In this step we initialize the model. This downloads a TensorFlow checkpoint of the quantized ResNet50." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.accel.models import QuantizedResnet50\n", - "save_path = os.path.expanduser('~/models')\n", - "model_graph = QuantizedResnet50(save_path, is_frozen = True)\n", - "feature_tensor = model_graph.import_graph_def(image_tensors)\n", - "print(model_graph.version)\n", - "print(feature_tensor.name)\n", - "print(feature_tensor.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.c. Classifier\n", - "The model we downloaded includes a classifier which takes the output of the ResNet50 and identifies an image. This classifier is trained on the ImageNet dataset. We are going to use this classifier for our service. The next [notebook](./accelerated-models-training.ipynb) shows how to train a classifier for a different data set. The input to the classifier is a tensor matching the output of our ResNet50 featurizer." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "classifier_output = model_graph.get_default_classifier(feature_tensor)\n", - "print(classifier_output)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.d. Save Model\n", - "Now that we loaded all three parts of the tensorflow graph (preprocessor, resnet50 featurizer, and the classifier), we can save the graph and associated variables to a directory which we can register as an Azure ML Model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# model_name must be lowercase\n", - "model_name = \"resnet50\"\n", - "model_save_path = os.path.join(save_path, model_name)\n", - "print(\"Saving model in {}\".format(model_save_path))\n", - "\n", - "with tf.Session() as sess:\n", - " model_graph.restore_weights(sess)\n", - " tf.saved_model.simple_save(sess, model_save_path,\n", - " inputs={'images': in_images},\n", - " outputs={'output_alias': classifier_output})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.e. Important! Save names of input and output tensors\n", - "\n", - "These input and output tensors that were created during the preprocessing and classifier steps are also going to be used when **converting the model** to an Accelerated Model that can run on FPGA's and for **making an inferencing request**. It is very important to save this information! You can see our defaults for all the models in the [README](./README.md).\n", - "\n", - "By default for Resnet50, these are the values you should see when running the cell below: \n", - "* input_tensors = \"Placeholder:0\"\n", - "* output_tensors = \"classifier/resnet_v1_50/predictions/Softmax:0\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "register model from file" - ] - }, - "outputs": [], - "source": [ - "input_tensors = in_images.name\n", - "output_tensors = classifier_output.name\n", - "\n", - "print(input_tensors)\n", - "print(output_tensors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 3. Register Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can add tags and descriptions to your models. Using tags, you can track useful information such as the name and version of the machine learning library used to train the model. Note that tags must be alphanumeric." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "register model from file" - ] - }, - "outputs": [], - "source": [ - "from azureml.core.model import Model\n", - "\n", - "registered_model = Model.register(workspace = ws,\n", - " model_path = model_save_path,\n", - " model_name = model_name)\n", - "\n", - "print(\"Successfully registered: \", registered_model.name, registered_model.description, registered_model.version, sep = '\\t')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 4. Convert Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For conversion you need to provide names of input and output tensors. This information can be found from the model_graph you saved in step 2.e. above.\n", - "\n", - "**Note**: Conversion may take a while and on average for FPGA model it is about 1-3 minutes and it depends on model type." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "register model from file" - ] - }, - "outputs": [], - "source": [ - "from azureml.accel import AccelOnnxConverter\n", - "\n", - "convert_request = AccelOnnxConverter.convert_tf_model(ws, registered_model, input_tensors, output_tensors)\n", - "# If it fails, you can run wait_for_completion again with show_output=True.\n", - "convert_request.wait_for_completion(show_output = False)\n", - "# If the above call succeeded, get the converted model\n", - "converted_model = convert_request.result\n", - "print(\"\\nSuccessfully converted: \", converted_model.name, converted_model.url, converted_model.version, \n", - " converted_model.id, converted_model.created_time, '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 5. Package the model into an Image" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can add tags and descriptions to image. Also, for FPGA model an image can only contain **single** model.\n", - "\n", - "**Note**: The following command can take few minutes. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.image import Image\n", - "from azureml.accel import AccelContainerImage\n", - "\n", - "image_config = AccelContainerImage.image_configuration()\n", - "# Image name must be lowercase\n", - "image_name = \"{}-image\".format(model_name)\n", - "\n", - "image = Image.create(name = image_name,\n", - " models = [converted_model],\n", - " image_config = image_config, \n", - " workspace = ws)\n", - "image.wait_for_creation(show_output = False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 6. Deploy\n", - "Once you have an Azure ML Accelerated Image in your Workspace, you can deploy it to two destinations, to a Databox Edge machine or to an AKS cluster. \n", - "\n", - "### 6.a. Databox Edge Machine using IoT Hub\n", - "See the sample [here](https://github.com/Azure-Samples/aml-real-time-ai/) for using the Azure IoT CLI extension for deploying your Docker image to your Databox Edge Machine.\n", - "\n", - "### 6.b. Azure Kubernetes Service (AKS) using Azure ML Service\n", - "We are going to create an AKS cluster with FPGA-enabled machines, then deploy our service to it. For more information, see [AKS official docs](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-deploy-and-where#aks).\n", - "\n", - "#### Create AKS ComputeTarget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import AksCompute, ComputeTarget\n", - "\n", - "# Uses the specific FPGA enabled VM (sku: Standard_PB6s)\n", - "# Standard_PB6s are available in: eastus, westus2, westeurope, southeastasia\n", - "prov_config = AksCompute.provisioning_configuration(vm_size = \"Standard_PB6s\",\n", - " agent_count = 1, \n", - " location = \"eastus\")\n", - "\n", - "aks_name = 'my-aks-pb6'\n", - "# Create the cluster\n", - "aks_target = ComputeTarget.create(workspace = ws, \n", - " name = aks_name, \n", - " provisioning_configuration = prov_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Provisioning an AKS cluster might take awhile (15 or so minutes), and we want to wait until it's successfully provisioned before we can deploy a service to it. If you interrupt this cell, provisioning of the cluster will continue. You can also check the status in your Workspace under Compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_target.wait_for_completion(show_output = True)\n", - "print(aks_target.provisioning_state)\n", - "print(aks_target.provisioning_errors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Deploy AccelContainerImage to AKS ComputeTarget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import Webservice, AksWebservice\n", - "\n", - "#Set the web service configuration (for creating a test service, we don't want autoscale enabled)\n", - "# Authentication is enabled by default, but for testing we specify False\n", - "aks_config = AksWebservice.deploy_configuration(autoscale_enabled=False,\n", - " num_replicas=1,\n", - " auth_enabled = False)\n", - "\n", - "aks_service_name ='my-aks-service'\n", - "\n", - "aks_service = Webservice.deploy_from_image(workspace = ws,\n", - " name = aks_service_name,\n", - " image = image,\n", - " deployment_config = aks_config,\n", - " deployment_target = aks_target)\n", - "aks_service.wait_for_deployment(show_output = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 7. Test the service" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 7.a. Create Client\n", - "The image supports gRPC and the TensorFlow Serving \"predict\" API. We have a client that can call into the docker image to get predictions.\n", - "\n", - "**Note:** If you chose to use auth_enabled=True when creating your AksWebservice, see documentation [here](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.webservice(class)?view=azure-ml-py#get-keys--) on how to retrieve your keys and use either key as an argument to PredictionClient(...,access_token=key).", - "\n", - "**WARNING:** If you are running on Azure Notebooks free compute, you will not be able to make outgoing calls to your service. Try locating your client on a different machine to consume it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Using the grpc client in AzureML Accelerated Models SDK\n", - "from azureml.accel.client import PredictionClient\n", - "\n", - "address = aks_service.scoring_uri\n", - "ssl_enabled = address.startswith(\"https\")\n", - "address = address[address.find('/')+2:].strip('/')\n", - "port = 443 if ssl_enabled else 80\n", - "\n", - "# Initialize AzureML Accelerated Models client\n", - "client = PredictionClient(address=address,\n", - " port=port,\n", - " use_ssl=ssl_enabled,\n", - " service_name=aks_service.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can adapt the client [code](https://github.com/Azure/aml-real-time-ai/blob/master/pythonlib/amlrealtimeai/client.py) to meet your needs. There is also an example C# [client](https://github.com/Azure/aml-real-time-ai/blob/master/sample-clients/csharp).\n", - "\n", - "The service provides an API that is compatible with TensorFlow Serving. There are instructions to download a sample client [here](https://www.tensorflow.org/serving/setup)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 7.b. Serve the model\n", - "To understand the results we need a mapping to the human readable imagenet classes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import requests\n", - "classes_entries = requests.get(\"https://raw.githubusercontent.com/Lasagne/Recipes/master/examples/resnet50/imagenet_classes.txt\").text.splitlines()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Score image with input and output tensor names\n", - "results = client.score_file(path=\"./snowleopardgaze.jpg\", \n", - " input_name=input_tensors, \n", - " outputs=output_tensors)\n", - "\n", - "# map results [class_id] => [confidence]\n", - "results = enumerate(results)\n", - "# sort results by confidence\n", - "sorted_results = sorted(results, key=lambda x: x[1], reverse=True)\n", - "# print top 5 results\n", - "for top in sorted_results[:5]:\n", - " print(classes_entries[top[0]], 'confidence:', top[1])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 8. Clean-up\n", - "Run the cell below to delete your webservice, image, and model (must be done in that order). In the [next notebook](./accelerated-models-training.ipynb) you will learn how to train a classfier on a new dataset using transfer learning and finetune the weights." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_service.delete()\n", - "aks_target.delete()\n", - "image.delete()\n", - "registered_model.delete()\n", - "converted_model.delete()" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "coverste" - }, - { - "name": "paledger" - }, - { - "name": "aibhalla" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Azure ML Hardware Accelerated Models Quickstart" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial will show you how to deploy an image recognition service based on the ResNet 50 classifier using the Azure Machine Learning Accelerated Models service. Get more information about our service from our [documentation](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-accelerate-with-fpgas), [API reference](https://docs.microsoft.com/en-us/python/api/azureml-accel-models/azureml.accel?view=azure-ml-py), or [forum](https://aka.ms/aml-forum).\n", + "\n", + "We will use an accelerated ResNet50 featurizer running on an FPGA. Our Accelerated Models Service handles translating deep neural networks (DNN) into an FPGA program.\n", + "\n", + "For more information about using other models besides Resnet50, see the [README](./README.md).\n", + "\n", + "The steps covered in this notebook are: \n", + "1. [Set up environment](#set-up-environment)\n", + "* [Construct model](#construct-model)\n", + " * Image Preprocessing\n", + " * Featurizer (Resnet50)\n", + " * Classifier\n", + " * Save Model\n", + "* [Register Model](#register-model)\n", + "* [Convert into Accelerated Model](#convert-model)\n", + "* [Create Image](#create-image)\n", + "* [Deploy](#deploy-image)\n", + "* [Test service](#test-service)\n", + "* [Clean-up](#clean-up)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 1. Set up environment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import tensorflow as tf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieve Workspace\n", + "If you haven't created a Workspace, please follow [this notebook](https://github.com/Azure/MachineLearningNotebooks/blob/master/configuration.ipynb) to do so. If you have, run the codeblock below to retrieve it. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "\n", + "ws = Workspace.from_config()\n", + "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 2. Construct model\n", + "\n", + "There are three parts to the model we are deploying: pre-processing, featurizer with ResNet50, and classifier with ImageNet dataset. Then we will save this complete Tensorflow model graph locally before registering it to your Azure ML Workspace.\n", + "\n", + "### 2.a. Image preprocessing\n", + "We'd like our service to accept JPEG images as input. However the input to ResNet50 is a tensor. So we need code that decodes JPEG images and does the preprocessing required by ResNet50. The Accelerated AI service can execute TensorFlow graphs as part of the service and we'll use that ability to do the image preprocessing. This code defines a TensorFlow graph that preprocesses an array of JPEG images (as strings) and produces a tensor that is ready to be featurized by ResNet50.\n", + "\n", + "**Note:** Expect to see TF deprecation warnings until we port our SDK over to use Tensorflow 2.0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Input images as a two-dimensional tensor containing an arbitrary number of images represented a strings\n", + "import azureml.accel.models.utils as utils\n", + "tf.reset_default_graph()\n", + "\n", + "in_images = tf.placeholder(tf.string)\n", + "image_tensors = utils.preprocess_array(in_images)\n", + "print(image_tensors.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.b. Featurizer\n", + "We use ResNet50 as a featurizer. In this step we initialize the model. This downloads a TensorFlow checkpoint of the quantized ResNet50." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.accel.models import QuantizedResnet50\n", + "save_path = os.path.expanduser('~/models')\n", + "model_graph = QuantizedResnet50(save_path, is_frozen = True)\n", + "feature_tensor = model_graph.import_graph_def(image_tensors)\n", + "print(model_graph.version)\n", + "print(feature_tensor.name)\n", + "print(feature_tensor.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.c. Classifier\n", + "The model we downloaded includes a classifier which takes the output of the ResNet50 and identifies an image. This classifier is trained on the ImageNet dataset. We are going to use this classifier for our service. The next [notebook](./accelerated-models-training.ipynb) shows how to train a classifier for a different data set. The input to the classifier is a tensor matching the output of our ResNet50 featurizer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "classifier_output = model_graph.get_default_classifier(feature_tensor)\n", + "print(classifier_output)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.d. Save Model\n", + "Now that we loaded all three parts of the tensorflow graph (preprocessor, resnet50 featurizer, and the classifier), we can save the graph and associated variables to a directory which we can register as an Azure ML Model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# model_name must be lowercase\n", + "model_name = \"resnet50\"\n", + "model_save_path = os.path.join(save_path, model_name)\n", + "print(\"Saving model in {}\".format(model_save_path))\n", + "\n", + "with tf.Session() as sess:\n", + " model_graph.restore_weights(sess)\n", + " tf.saved_model.simple_save(sess, model_save_path,\n", + " inputs={'images': in_images},\n", + " outputs={'output_alias': classifier_output})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.e. Important! Save names of input and output tensors\n", + "\n", + "These input and output tensors that were created during the preprocessing and classifier steps are also going to be used when **converting the model** to an Accelerated Model that can run on FPGA's and for **making an inferencing request**. It is very important to save this information! You can see our defaults for all the models in the [README](./README.md).\n", + "\n", + "By default for Resnet50, these are the values you should see when running the cell below: \n", + "* input_tensors = \"Placeholder:0\"\n", + "* output_tensors = \"classifier/resnet_v1_50/predictions/Softmax:0\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "register model from file" + ] + }, + "outputs": [], + "source": [ + "input_tensors = in_images.name\n", + "output_tensors = classifier_output.name\n", + "\n", + "print(input_tensors)\n", + "print(output_tensors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 3. Register Model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can add tags and descriptions to your models. Using tags, you can track useful information such as the name and version of the machine learning library used to train the model. Note that tags must be alphanumeric." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "register model from file" + ] + }, + "outputs": [], + "source": [ + "from azureml.core.model import Model\n", + "\n", + "registered_model = Model.register(workspace = ws,\n", + " model_path = model_save_path,\n", + " model_name = model_name)\n", + "\n", + "print(\"Successfully registered: \", registered_model.name, registered_model.description, registered_model.version, sep = '\\t')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 4. Convert Model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For conversion you need to provide names of input and output tensors. This information can be found from the model_graph you saved in step 2.e. above.\n", + "\n", + "**Note**: Conversion may take a while and on average for FPGA model it is about 1-3 minutes and it depends on model type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "register model from file" + ] + }, + "outputs": [], + "source": [ + "from azureml.accel import AccelOnnxConverter\n", + "\n", + "convert_request = AccelOnnxConverter.convert_tf_model(ws, registered_model, input_tensors, output_tensors)\n", + "# If it fails, you can run wait_for_completion again with show_output=True.\n", + "convert_request.wait_for_completion(show_output = False)\n", + "# If the above call succeeded, get the converted model\n", + "converted_model = convert_request.result\n", + "print(\"\\nSuccessfully converted: \", converted_model.name, converted_model.url, converted_model.version, \n", + " converted_model.id, converted_model.created_time, '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 5. Package the model into an Image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can add tags and descriptions to image. Also, for FPGA model an image can only contain **single** model.\n", + "\n", + "**Note**: The following command can take few minutes. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image\n", + "from azureml.accel import AccelContainerImage\n", + "\n", + "image_config = AccelContainerImage.image_configuration()\n", + "# Image name must be lowercase\n", + "image_name = \"{}-image\".format(model_name)\n", + "\n", + "image = Image.create(name = image_name,\n", + " models = [converted_model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "image.wait_for_creation(show_output = False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 6. Deploy\n", + "Once you have an Azure ML Accelerated Image in your Workspace, you can deploy it to two destinations, to a Databox Edge machine or to an AKS cluster. \n", + "\n", + "### 6.a. Databox Edge Machine using IoT Hub\n", + "See the sample [here](https://github.com/Azure-Samples/aml-real-time-ai/) for using the Azure IoT CLI extension for deploying your Docker image to your Databox Edge Machine.\n", + "\n", + "### 6.b. Azure Kubernetes Service (AKS) using Azure ML Service\n", + "We are going to create an AKS cluster with FPGA-enabled machines, then deploy our service to it. For more information, see [AKS official docs](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-deploy-and-where#aks).\n", + "\n", + "#### Create AKS ComputeTarget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AksCompute, ComputeTarget\n", + "\n", + "# Uses the specific FPGA enabled VM (sku: Standard_PB6s)\n", + "# Standard_PB6s are available in: eastus, westus2, westeurope, southeastasia\n", + "prov_config = AksCompute.provisioning_configuration(vm_size = \"Standard_PB6s\",\n", + " agent_count = 1, \n", + " location = \"eastus\")\n", + "\n", + "aks_name = 'my-aks-pb6'\n", + "# Create the cluster\n", + "aks_target = ComputeTarget.create(workspace = ws, \n", + " name = aks_name, \n", + " provisioning_configuration = prov_config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Provisioning an AKS cluster might take awhile (15 or so minutes), and we want to wait until it's successfully provisioned before we can deploy a service to it. If you interrupt this cell, provisioning of the cluster will continue. You can also check the status in your Workspace under Compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_target.wait_for_completion(show_output = True)\n", + "print(aks_target.provisioning_state)\n", + "print(aks_target.provisioning_errors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Deploy AccelContainerImage to AKS ComputeTarget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice, AksWebservice\n", + "\n", + "#Set the web service configuration (for creating a test service, we don't want autoscale enabled)\n", + "# Authentication is enabled by default, but for testing we specify False\n", + "aks_config = AksWebservice.deploy_configuration(autoscale_enabled=False,\n", + " num_replicas=1,\n", + " auth_enabled = False)\n", + "\n", + "aks_service_name ='my-aks-service'\n", + "\n", + "aks_service = Webservice.deploy_from_image(workspace = ws,\n", + " name = aks_service_name,\n", + " image = image,\n", + " deployment_config = aks_config,\n", + " deployment_target = aks_target)\n", + "aks_service.wait_for_deployment(show_output = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 7. Test the service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.a. Create Client\n", + "The image supports gRPC and the TensorFlow Serving \"predict\" API. We have a client that can call into the docker image to get predictions.\n", + "\n", + "**Note:** If you chose to use auth_enabled=True when creating your AksWebservice, see documentation [here](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.webservice(class)?view=azure-ml-py#get-keys--) on how to retrieve your keys and use either key as an argument to PredictionClient(...,access_token=key).", + "\n", + "**WARNING:** If you are running on Azure Notebooks free compute, you will not be able to make outgoing calls to your service. Try locating your client on a different machine to consume it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Using the grpc client in AzureML Accelerated Models SDK\n", + "from azureml.accel.client import PredictionClient\n", + "\n", + "address = aks_service.scoring_uri\n", + "ssl_enabled = address.startswith(\"https\")\n", + "address = address[address.find('/')+2:].strip('/')\n", + "port = 443 if ssl_enabled else 80\n", + "\n", + "# Initialize AzureML Accelerated Models client\n", + "client = PredictionClient(address=address,\n", + " port=port,\n", + " use_ssl=ssl_enabled,\n", + " service_name=aks_service.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can adapt the client [code](https://github.com/Azure/aml-real-time-ai/blob/master/pythonlib/amlrealtimeai/client.py) to meet your needs. There is also an example C# [client](https://github.com/Azure/aml-real-time-ai/blob/master/sample-clients/csharp).\n", + "\n", + "The service provides an API that is compatible with TensorFlow Serving. There are instructions to download a sample client [here](https://www.tensorflow.org/serving/setup)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.b. Serve the model\n", + "To understand the results we need a mapping to the human readable imagenet classes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "classes_entries = requests.get(\"https://raw.githubusercontent.com/Lasagne/Recipes/master/examples/resnet50/imagenet_classes.txt\").text.splitlines()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Score image with input and output tensor names\n", + "results = client.score_file(path=\"./snowleopardgaze.jpg\", \n", + " input_name=input_tensors, \n", + " outputs=output_tensors)\n", + "\n", + "# map results [class_id] => [confidence]\n", + "results = enumerate(results)\n", + "# sort results by confidence\n", + "sorted_results = sorted(results, key=lambda x: x[1], reverse=True)\n", + "# print top 5 results\n", + "for top in sorted_results[:5]:\n", + " print(classes_entries[top[0]], 'confidence:', top[1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 8. Clean-up\n", + "Run the cell below to delete your webservice, image, and model (must be done in that order). In the [next notebook](./accelerated-models-training.ipynb) you will learn how to train a classfier on a new dataset using transfer learning and finetune the weights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_service.delete()\n", + "aks_target.delete()\n", + "image.delete()\n", + "registered_model.delete()\n", + "converted_model.delete()" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "coverste" + }, + { + "name": "paledger" + }, + { + "name": "aibhalla" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/deployment/accelerated-models/accelerated-models-training.ipynb b/how-to-use-azureml/deployment/accelerated-models/accelerated-models-training.ipynb index bb3567d1..778e5310 100644 --- a/how-to-use-azureml/deployment/accelerated-models/accelerated-models-training.ipynb +++ b/how-to-use-azureml/deployment/accelerated-models/accelerated-models-training.ipynb @@ -1,862 +1,862 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Training with the Azure Machine Learning Accelerated Models Service" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook will introduce how to apply common machine learning techniques, like transfer learning, custom weights, and unquantized vs. quantized models, when working with our Azure Machine Learning Accelerated Models Service (Azure ML Accel Models).\n", - "\n", - "We will use Tensorflow for the preprocessing steps, ResNet50 for the featurizer, and the Keras API (built on Tensorflow backend) to build the classifier layers instead of the default ImageNet classifier used in Quickstart. Then we will train the model, evaluate it, and deploy it to run on an FPGA.\n", - "\n", - "#### Transfer Learning and Custom weights\n", - "We will walk you through two ways to build and train a ResNet50 model on the Kaggle Cats and Dogs dataset: transfer learning only and then transfer learning with custom weights.\n", - "\n", - "In using transfer learning, our goal is to re-purpose the ResNet50 model already trained on the [ImageNet image dataset](http://www.image-net.org/) as a basis for our training of the Kaggle Cats and Dogs dataset. The ResNet50 featurizer will be imported as frozen, so only the Keras classifier will be trained.\n", - "\n", - "With the addition of custom weights, we will build the model so that the ResNet50 featurizer weights as not frozen. This will let us retrain starting with custom weights trained with ImageNet on ResNet50 and then use the Kaggle Cats and Dogs dataset to retrain and fine-tune the quantized version of the model.\n", - "\n", - "#### Unquantized vs. Quantized models\n", - "The unquantized version of our models (ie. Resnet50, Resnet152, Densenet121, Vgg16, SsdVgg) uses native float precision (32-bit floats), which will be faster at training. We will use this for our first run through, then fine-tune the weights with the quantized version. The quantized version of our models (i.e. QuantizedResnet50, QuantizedResnet152, QuantizedDensenet121, QuantizedVgg16, QuantizedSsdVgg) will have the same node names as the unquantized version, but use quantized operations and will match the performance of the model when running on an FPGA.\n", - "\n", - "#### Contents\n", - "1. [Setup Environment](#setup)\n", - "* [Prepare Data](#prepare-data)\n", - "* [Construct Model](#construct-model)\n", - " * Preprocessor\n", - " * Classifier\n", - " * Model construction\n", - "* [Train Model](#train-model)\n", - "* [Test Model](#test-model)\n", - "* [Execution](#execution)\n", - " * [Transfer Learning](#transfer-learning)\n", - " * [Transfer Learning with Custom Weights](#custom-weights)\n", - "* [Create Image](#create-image)\n", - "* [Deploy Image](#deploy-image)\n", - "* [Test the service](#test-service)\n", - "* [Clean-up](#cleanup)\n", - "* [Appendix](#appendix)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 1. Setup Environment\n", - "#### 1.a. Please set up your environment as described in the [Quickstart](./accelerated-models-quickstart.ipynb), meaning:\n", - "* Make sure your Workspace config.json exists and has the correct info\n", - "* Install Tensorflow\n", - "\n", - "#### 1.b. Download dataset into ~/catsanddogs \n", - "The dataset we will be using for training can be downloaded [here](https://www.microsoft.com/en-us/download/details.aspx?id=54765). Download the zip and extract to a directory named 'catsanddogs' under your user directory (\"~/catsanddogs\"). \n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### 1.c. Import packages" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import sys\n", - "import tensorflow as tf\n", - "import numpy as np\n", - "from keras import backend as K\n", - "import sklearn\n", - "import tqdm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### 1.d. Create directories for later use\n", - "After you train your model in float32, you'll write the weights to a place on disk. We also need a location to store the models that get downloaded." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "custom_weights_dir = os.path.expanduser(\"~/custom-weights\")\n", - "saved_model_dir = os.path.expanduser(\"~/models\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 2. Prepare Data\n", - "Load the files we are going to use for training and testing. By default this notebook uses only a very small subset of the Cats and Dogs dataset. That makes it run relatively quickly." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import glob\n", - "import imghdr\n", - "datadir = os.path.expanduser(\"~/catsanddogs\")\n", - "\n", - "cat_files = glob.glob(os.path.join(datadir, 'PetImages', 'Cat', '*.jpg'))\n", - "dog_files = glob.glob(os.path.join(datadir, 'PetImages', 'Dog', '*.jpg'))\n", - "\n", - "# Limit the data set to make the notebook execute quickly.\n", - "cat_files = cat_files[:64]\n", - "dog_files = dog_files[:64]\n", - "\n", - "# The data set has a few images that are not jpeg. Remove them.\n", - "cat_files = [f for f in cat_files if imghdr.what(f) == 'jpeg']\n", - "dog_files = [f for f in dog_files if imghdr.what(f) == 'jpeg']\n", - "\n", - "if(not len(cat_files) or not len(dog_files)):\n", - " print(\"Please download the Kaggle Cats and Dogs dataset form https://www.microsoft.com/en-us/download/details.aspx?id=54765 and extract the zip to \" + datadir) \n", - " raise ValueError(\"Data not found\")\n", - "else:\n", - " print(cat_files[0])\n", - " print(dog_files[0])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Construct a numpy array as labels\n", - "image_paths = cat_files + dog_files\n", - "total_files = len(cat_files) + len(dog_files)\n", - "labels = np.zeros(total_files)\n", - "labels[len(cat_files):] = 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Split images data as training data and test data\n", - "from sklearn.model_selection import train_test_split\n", - "onehot_labels = np.array([[0,1] if i else [1,0] for i in labels])\n", - "img_train, img_test, label_train, label_test = train_test_split(image_paths, onehot_labels, random_state=42, shuffle=True)\n", - "\n", - "print(len(img_train), len(img_test), label_train.shape, label_test.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 3. Construct Model\n", - "We will define the functions to handle creating the preprocessor and the classifier first, and then run them together to actually construct the model with the Resnet50 featurizer in a single Tensorflow session in a separate cell.\n", - "\n", - "We use ResNet50 for the featurizer and build our own classifier using Keras layers. We train the featurizer and the classifier as one model. We will provide parameters to determine whether we are using the quantized version and whether we are using custom weights in training or not." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.a. Define image preprocessing step\n", - "Same as in the Quickstart, before passing image dataset to the ResNet50 featurizer, we need to preprocess the input file to get it into the form expected by ResNet50. ResNet50 expects float tensors representing the images in BGR, channel last order. We've provided a default implementation of the preprocessing that you can use.\n", - "\n", - "**Note:** Expect to see TF deprecation warnings until we port our SDK over to use Tensorflow 2.0." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import azureml.accel.models.utils as utils\n", - "\n", - "def preprocess_images(scaling_factor=1.0):\n", - " # Convert images to 3D tensors [width,height,channel] - channels are in BGR order.\n", - " in_images = tf.placeholder(tf.string)\n", - " image_tensors = utils.preprocess_array(in_images, 'RGB', scaling_factor)\n", - " return in_images, image_tensors" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.b. Define classifier\n", - "We use Keras layer APIs to construct the classifier. Because we're using the tensorflow backend, we can train this classifier in one session with our Resnet50 model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def construct_classifier(in_tensor, seed=None):\n", - " from keras.layers import Dropout, Dense, Flatten\n", - " from keras.initializers import glorot_uniform\n", - " K.set_session(tf.get_default_session())\n", - "\n", - " FC_SIZE = 1024\n", - " NUM_CLASSES = 2\n", - "\n", - " x = Dropout(0.2, input_shape=(1, 1, int(in_tensor.shape[3]),), seed=seed)(in_tensor)\n", - " x = Dense(FC_SIZE, activation='relu', input_dim=(1, 1, int(in_tensor.shape[3]),),\n", - " kernel_initializer=glorot_uniform(seed=seed), bias_initializer='zeros')(x)\n", - " x = Flatten()(x)\n", - " preds = Dense(NUM_CLASSES, activation='softmax', input_dim=FC_SIZE, name='classifier_output',\n", - " kernel_initializer=glorot_uniform(seed=seed), bias_initializer='zeros')(x)\n", - " return preds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.c. Define model construction\n", - "Now that the preprocessor and classifier for the model are defined, we can define how we want to construct the model. \n", - "\n", - "Constructing the model has these steps: \n", - "1. Get preprocessing steps\n", - "* Get featurizer using the Azure ML Accel Models SDK:\n", - " * import the graph definition\n", - " * restore the weights of the model into a Tensorflow session\n", - "* Get classifier\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def construct_model(quantized, starting_weights_directory = None):\n", - " from azureml.accel.models import Resnet50, QuantizedResnet50\n", - " \n", - " # Convert images to 3D tensors [width,height,channel]\n", - " in_images, image_tensors = preprocess_images(1.0)\n", - "\n", - " # Construct featurizer using quantized or unquantized ResNet50 model\n", - " if not quantized:\n", - " featurizer = Resnet50(saved_model_dir)\n", - " else:\n", - " featurizer = QuantizedResnet50(saved_model_dir, custom_weights_directory = starting_weights_directory)\n", - "\n", - " features = featurizer.import_graph_def(input_tensor=image_tensors)\n", - " \n", - " # Construct classifier\n", - " preds = construct_classifier(features)\n", - " \n", - " # Initialize weights\n", - " sess = tf.get_default_session()\n", - " tf.global_variables_initializer().run()\n", - "\n", - " featurizer.restore_weights(sess)\n", - "\n", - " return in_images, image_tensors, features, preds, featurizer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 4. Train Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def read_files(files):\n", - " \"\"\" Read files to array\"\"\"\n", - " contents = []\n", - " for path in files:\n", - " with open(path, 'rb') as f:\n", - " contents.append(f.read())\n", - " return contents" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def train_model(preds, in_images, img_train, label_train, is_retrain = False, train_epoch = 10, learning_rate=None):\n", - " \"\"\" training model \"\"\"\n", - " from keras.objectives import binary_crossentropy\n", - " from tqdm import tqdm\n", - " \n", - " learning_rate = learning_rate if learning_rate else 0.001 if is_retrain else 0.01\n", - " \n", - " # Specify the loss function\n", - " in_labels = tf.placeholder(tf.float32, shape=(None, 2)) \n", - " cross_entropy = tf.reduce_mean(binary_crossentropy(in_labels, preds))\n", - " optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cross_entropy)\n", - "\n", - " def chunks(a, b, n):\n", - " \"\"\"Yield successive n-sized chunks from a and b.\"\"\"\n", - " if (len(a) != len(b)):\n", - " print(\"a and b are not equal in chunks(a,b,n)\")\n", - " raise ValueError(\"Parameter error\")\n", - "\n", - " for i in range(0, len(a), n):\n", - " yield a[i:i + n], b[i:i + n]\n", - "\n", - " chunk_size = 16\n", - " chunk_num = len(label_train) / chunk_size\n", - "\n", - " sess = tf.get_default_session()\n", - " for epoch in range(train_epoch):\n", - " avg_loss = 0\n", - " for img_chunk, label_chunk in tqdm(chunks(img_train, label_train, chunk_size)):\n", - " contents = read_files(img_chunk)\n", - " _, loss = sess.run([optimizer, cross_entropy],\n", - " feed_dict={in_images: contents,\n", - " in_labels: label_chunk,\n", - " K.learning_phase(): 1})\n", - " avg_loss += loss / chunk_num\n", - " print(\"Epoch:\", (epoch + 1), \"loss = \", \"{:.3f}\".format(avg_loss))\n", - " \n", - " # Reach desired performance\n", - " if (avg_loss < 0.001):\n", - " break" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 5. Test Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def test_model(preds, in_images, img_test, label_test):\n", - " \"\"\"Test the model\"\"\"\n", - " from keras.metrics import categorical_accuracy\n", - "\n", - " in_labels = tf.placeholder(tf.float32, shape=(None, 2))\n", - " accuracy = tf.reduce_mean(categorical_accuracy(in_labels, preds))\n", - " contents = read_files(img_test)\n", - "\n", - " accuracy = accuracy.eval(feed_dict={in_images: contents,\n", - " in_labels: label_test,\n", - " K.learning_phase(): 0})\n", - " return accuracy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 6. Execute steps\n", - "You can run through the Transfer Learning section, then skip to Create AccelContainerImage. By default, because the custom weights section takes much longer for training twice, it is not saved as executable cells. You can copy the code or change cell type to 'Code'.\n", - "\n", - "\n", - "### 6.a. Training using Transfer Learning" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Launch the training\n", - "tf.reset_default_graph()\n", - "sess = tf.Session(graph=tf.get_default_graph())\n", - "\n", - "with sess.as_default():\n", - " in_images, image_tensors, features, preds, featurizer = construct_model(quantized=True)\n", - " train_model(preds, in_images, img_train, label_train, is_retrain=False, train_epoch=10, learning_rate=0.01) \n", - " accuracy = test_model(preds, in_images, img_test, label_test) \n", - " print(\"Accuracy:\", accuracy)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Save Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_name = 'resnet50-catsanddogs-tl'\n", - "model_save_path = os.path.join(saved_model_dir, model_name)\n", - "\n", - "tf.saved_model.simple_save(sess, model_save_path,\n", - " inputs={'images': in_images},\n", - " outputs={'output_alias': preds})\n", - "\n", - "input_tensors = in_images.name\n", - "output_tensors = preds.name\n", - "\n", - "print(input_tensors)\n", - "print(output_tensors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### 6.b. Traning using Custom Weights\n", - "\n", - "Because the quantized graph defintion and the float32 graph defintion share the same node names in the graph definitions, we can initally train the weights in float32, and then reload them with the quantized operations (which take longer) to fine-tune the model.\n", - "\n", - "First we train the model with custom weights but without quantization. Training is done with native float precision (32-bit floats). We load the training data set and batch the training with 10 epochs. When the performance reaches desired level or starts decredation, we stop the training iteration and save the weights as tensorflow checkpoint files. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Launch the training\n", - "```\n", - "tf.reset_default_graph()\n", - "sess = tf.Session(graph=tf.get_default_graph())\n", - "\n", - "with sess.as_default():\n", - " in_images, image_tensors, features, preds, featurizer = construct_model(quantized=False)\n", - " train_model(preds, in_images, img_train, label_train, is_retrain=False, train_epoch=10) \n", - " accuracy = test_model(preds, in_images, img_test, label_test) \n", - " print(\"Accuracy:\", accuracy)\n", - " featurizer.save_weights(custom_weights_dir + \"/rn50\", tf.get_default_session())\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Test Model\n", - "After training, we evaluate the trained model's accuracy on test dataset with quantization. So that we know the model's performance if it is deployed on the FPGA." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```\n", - "tf.reset_default_graph()\n", - "sess = tf.Session(graph=tf.get_default_graph())\n", - "\n", - "with sess.as_default():\n", - " print(\"Testing trained model with quantization\")\n", - " in_images, image_tensors, features, preds, quantized_featurizer = construct_model(quantized=True, starting_weights_directory=custom_weights_dir)\n", - " accuracy = test_model(preds, in_images, img_test, label_test) \n", - " print(\"Accuracy:\", accuracy)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Fine-Tune Model\n", - "Sometimes, the model's accuracy can drop significantly after quantization. In those cases, we need to retrain the model enabled with quantization to get better model accuracy." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```\n", - "if (accuracy < 0.93):\n", - " with sess.as_default():\n", - " print(\"Fine-tuning model with quantization\")\n", - " train_model(preds, in_images, img_train, label_train, is_retrain=True, train_epoch=10)\n", - " accuracy = test_model(preds, in_images, img_test, label_test) \n", - " print(\"Accuracy:\", accuracy)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Save Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```\n", - "model_name = 'resnet50-catsanddogs-cw'\n", - "model_save_path = os.path.join(saved_model_dir, model_name)\n", - "\n", - "tf.saved_model.simple_save(sess, model_save_path,\n", - " inputs={'images': in_images},\n", - " outputs={'output_alias': preds})\n", - "\n", - "input_tensors = in_images.name\n", - "output_tensors = preds.name\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 7. Create AccelContainerImage\n", - "\n", - "Below we will execute all the same steps as in the [Quickstart](./accelerated-models-quickstart.ipynb#create-image) to package the model we have saved locally into an accelerated Docker image saved in our workspace. To complete all the steps, it may take a few minutes. For more details on each step, check out the [Quickstart section on model registration](./accelerated-models-quickstart.ipynb#register-model)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "from azureml.core.model import Model\n", - "from azureml.core.image import Image\n", - "from azureml.accel import AccelOnnxConverter\n", - "from azureml.accel import AccelContainerImage\n", - "\n", - "# Retrieve workspace\n", - "ws = Workspace.from_config()\n", - "print(\"Successfully retrieved workspace:\", ws.name, ws.resource_group, ws.location, ws.subscription_id, '\\n')\n", - "\n", - "# Register model\n", - "registered_model = Model.register(workspace = ws,\n", - " model_path = model_save_path,\n", - " model_name = model_name)\n", - "print(\"Successfully registered: \", registered_model.name, registered_model.description, registered_model.version, '\\n', sep = '\\t')\n", - "\n", - "# Convert model\n", - "convert_request = AccelOnnxConverter.convert_tf_model(ws, registered_model, input_tensors, output_tensors)\n", - "# If it fails, you can run wait_for_completion again with show_output=True.\n", - "convert_request.wait_for_completion(show_output=False)\n", - "converted_model = convert_request.result\n", - "print(\"\\nSuccessfully converted: \", converted_model.name, converted_model.url, converted_model.version, \n", - " converted_model.id, converted_model.created_time, '\\n')\n", - "\n", - "# Package into AccelContainerImage\n", - "image_config = AccelContainerImage.image_configuration()\n", - "# Image name must be lowercase\n", - "image_name = \"{}-image\".format(model_name)\n", - "image = Image.create(name = image_name,\n", - " models = [converted_model],\n", - " image_config = image_config, \n", - " workspace = ws)\n", - "image.wait_for_creation()\n", - "print(\"Created AccelContainerImage: {} {} {}\\n\".format(image.name, image.creation_state, image.image_location))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 8. Deploy image\n", - "Once you have an Azure ML Accelerated Image in your Workspace, you can deploy it to two destinations, to a Databox Edge machine or to an AKS cluster. \n", - "\n", - "### 8.a. Deploy to Databox Edge Machine using IoT Hub\n", - "See the sample [here](https://github.com/Azure-Samples/aml-real-time-ai/) for using the Azure IoT CLI extension for deploying your Docker image to your Databox Edge Machine.\n", - "\n", - "### 8.b. Deploy to AKS Cluster" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create AKS ComputeTarget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import AksCompute, ComputeTarget\n", - "\n", - "# Uses the specific FPGA enabled VM (sku: Standard_PB6s)\n", - "# Standard_PB6s are available in: eastus, westus2, westeurope, southeastasia\n", - "prov_config = AksCompute.provisioning_configuration(vm_size = \"Standard_PB6s\",\n", - " agent_count = 1,\n", - " location = \"eastus\")\n", - "\n", - "aks_name = 'aks-pb6-tl'\n", - "# Create the cluster\n", - "aks_target = ComputeTarget.create(workspace = ws, \n", - " name = aks_name, \n", - " provisioning_configuration = prov_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Provisioning an AKS cluster might take awhile (15 or so minutes), and we want to wait until it's successfully provisioned before we can deploy a service to it. If you interrupt this cell, provisioning of the cluster will continue. You can re-run it or check the status in your Workspace under Compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_target.wait_for_completion(show_output = True)\n", - "print(aks_target.provisioning_state)\n", - "print(aks_target.provisioning_errors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Deploy AccelContainerImage to AKS ComputeTarget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import Webservice, AksWebservice\n", - "\n", - "# Set the web service configuration (for creating a test service, we don't want autoscale enabled)\n", - "# Authentication is enabled by default, but for testing we specify False\n", - "aks_config = AksWebservice.deploy_configuration(autoscale_enabled=False,\n", - " num_replicas=1,\n", - " auth_enabled = False)\n", - "\n", - "aks_service_name ='my-aks-service'\n", - "\n", - "aks_service = Webservice.deploy_from_image(workspace = ws,\n", - " name = aks_service_name,\n", - " image = image,\n", - " deployment_config = aks_config,\n", - " deployment_target = aks_target)\n", - "aks_service.wait_for_deployment(show_output = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 9. Test the service\n", - "\n", - "\n", - "### 9.a. Create Client\n", - "The image supports gRPC and the TensorFlow Serving \"predict\" API. We have a client that can call into the docker image to get predictions. \n", - "\n", - "**Note:** If you chose to use auth_enabled=True when creating your AksWebservice.deploy_configuration(), see documentation [here](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.webservice(class)?view=azure-ml-py#get-keys--) on how to retrieve your keys and use either key as an argument to PredictionClient(...,access_token=key).", - "\n", - "**WARNING:** If you are running on Azure Notebooks free compute, you will not be able to make outgoing calls to your service. Try locating your client on a different machine to consume it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Using the grpc client in AzureML Accelerated Models SDK\n", - "from azureml.accel.client import PredictionClient\n", - "\n", - "address = aks_service.scoring_uri\n", - "ssl_enabled = address.startswith(\"https\")\n", - "address = address[address.find('/')+2:].strip('/')\n", - "port = 443 if ssl_enabled else 80\n", - "\n", - "# Initialize AzureML Accelerated Models client\n", - "client = PredictionClient(address=address,\n", - " port=port,\n", - " use_ssl=ssl_enabled,\n", - " service_name=aks_service.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### 9.b. Serve the model\n", - "Let's see how our service does on a few images. It may get a few wrong." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Specify an image to classify\n", - "print('CATS')\n", - "for image_file in cat_files[:8]:\n", - " results = client.score_file(path=image_file, \n", - " input_name=input_tensors, \n", - " outputs=output_tensors)\n", - " result = 'CORRECT ' if results[0] > results[1] else 'WRONG '\n", - " print(result + str(results))\n", - "print('DOGS')\n", - "for image_file in dog_files[:8]:\n", - " results = client.score_file(path=image_file, \n", - " input_name=input_tensors, \n", - " outputs=output_tensors)\n", - " result = 'CORRECT ' if results[1] > results[0] else 'WRONG '\n", - " print(result + str(results))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 10. Cleanup\n", - "It's important to clean up your resources, so that you won't incur unnecessary costs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_service.delete()\n", - "aks_target.delete()\n", - "image.delete()\n", - "registered_model.delete()\n", - "converted_model.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 11. Appendix" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "License for plot_confusion_matrix:\n", - "\n", - "New BSD License\n", - "\n", - "Copyright (c) 2007-2018 The scikit-learn developers.\n", - "All rights reserved.\n", - "\n", - "\n", - "Redistribution and use in source and binary forms, with or without\n", - "modification, are permitted provided that the following conditions are met:\n", - "\n", - " a. Redistributions of source code must retain the above copyright notice,\n", - " this list of conditions and the following disclaimer.\n", - " b. Redistributions in binary form must reproduce the above copyright\n", - " notice, this list of conditions and the following disclaimer in the\n", - " documentation and/or other materials provided with the distribution.\n", - " c. Neither the name of the Scikit-learn Developers nor the names of\n", - " its contributors may be used to endorse or promote products\n", - " derived from this software without specific prior written\n", - " permission. \n", - "\n", - "\n", - "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n", - "AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n", - "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n", - "ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR\n", - "ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n", - "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n", - "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n", - "CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n", - "LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n", - "OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\n", - "DAMAGE.\n" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "coverste" - }, - { - "name": "paledger" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Training with the Azure Machine Learning Accelerated Models Service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook will introduce how to apply common machine learning techniques, like transfer learning, custom weights, and unquantized vs. quantized models, when working with our Azure Machine Learning Accelerated Models Service (Azure ML Accel Models).\n", + "\n", + "We will use Tensorflow for the preprocessing steps, ResNet50 for the featurizer, and the Keras API (built on Tensorflow backend) to build the classifier layers instead of the default ImageNet classifier used in Quickstart. Then we will train the model, evaluate it, and deploy it to run on an FPGA.\n", + "\n", + "#### Transfer Learning and Custom weights\n", + "We will walk you through two ways to build and train a ResNet50 model on the Kaggle Cats and Dogs dataset: transfer learning only and then transfer learning with custom weights.\n", + "\n", + "In using transfer learning, our goal is to re-purpose the ResNet50 model already trained on the [ImageNet image dataset](http://www.image-net.org/) as a basis for our training of the Kaggle Cats and Dogs dataset. The ResNet50 featurizer will be imported as frozen, so only the Keras classifier will be trained.\n", + "\n", + "With the addition of custom weights, we will build the model so that the ResNet50 featurizer weights as not frozen. This will let us retrain starting with custom weights trained with ImageNet on ResNet50 and then use the Kaggle Cats and Dogs dataset to retrain and fine-tune the quantized version of the model.\n", + "\n", + "#### Unquantized vs. Quantized models\n", + "The unquantized version of our models (ie. Resnet50, Resnet152, Densenet121, Vgg16, SsdVgg) uses native float precision (32-bit floats), which will be faster at training. We will use this for our first run through, then fine-tune the weights with the quantized version. The quantized version of our models (i.e. QuantizedResnet50, QuantizedResnet152, QuantizedDensenet121, QuantizedVgg16, QuantizedSsdVgg) will have the same node names as the unquantized version, but use quantized operations and will match the performance of the model when running on an FPGA.\n", + "\n", + "#### Contents\n", + "1. [Setup Environment](#setup)\n", + "* [Prepare Data](#prepare-data)\n", + "* [Construct Model](#construct-model)\n", + " * Preprocessor\n", + " * Classifier\n", + " * Model construction\n", + "* [Train Model](#train-model)\n", + "* [Test Model](#test-model)\n", + "* [Execution](#execution)\n", + " * [Transfer Learning](#transfer-learning)\n", + " * [Transfer Learning with Custom Weights](#custom-weights)\n", + "* [Create Image](#create-image)\n", + "* [Deploy Image](#deploy-image)\n", + "* [Test the service](#test-service)\n", + "* [Clean-up](#cleanup)\n", + "* [Appendix](#appendix)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 1. Setup Environment\n", + "#### 1.a. Please set up your environment as described in the [Quickstart](./accelerated-models-quickstart.ipynb), meaning:\n", + "* Make sure your Workspace config.json exists and has the correct info\n", + "* Install Tensorflow\n", + "\n", + "#### 1.b. Download dataset into ~/catsanddogs \n", + "The dataset we will be using for training can be downloaded [here](https://www.microsoft.com/en-us/download/details.aspx?id=54765). Download the zip and extract to a directory named 'catsanddogs' under your user directory (\"~/catsanddogs\"). \n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 1.c. Import packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "import tensorflow as tf\n", + "import numpy as np\n", + "from keras import backend as K\n", + "import sklearn\n", + "import tqdm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 1.d. Create directories for later use\n", + "After you train your model in float32, you'll write the weights to a place on disk. We also need a location to store the models that get downloaded." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "custom_weights_dir = os.path.expanduser(\"~/custom-weights\")\n", + "saved_model_dir = os.path.expanduser(\"~/models\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 2. Prepare Data\n", + "Load the files we are going to use for training and testing. By default this notebook uses only a very small subset of the Cats and Dogs dataset. That makes it run relatively quickly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import glob\n", + "import imghdr\n", + "datadir = os.path.expanduser(\"~/catsanddogs\")\n", + "\n", + "cat_files = glob.glob(os.path.join(datadir, 'PetImages', 'Cat', '*.jpg'))\n", + "dog_files = glob.glob(os.path.join(datadir, 'PetImages', 'Dog', '*.jpg'))\n", + "\n", + "# Limit the data set to make the notebook execute quickly.\n", + "cat_files = cat_files[:64]\n", + "dog_files = dog_files[:64]\n", + "\n", + "# The data set has a few images that are not jpeg. Remove them.\n", + "cat_files = [f for f in cat_files if imghdr.what(f) == 'jpeg']\n", + "dog_files = [f for f in dog_files if imghdr.what(f) == 'jpeg']\n", + "\n", + "if(not len(cat_files) or not len(dog_files)):\n", + " print(\"Please download the Kaggle Cats and Dogs dataset form https://www.microsoft.com/en-us/download/details.aspx?id=54765 and extract the zip to \" + datadir) \n", + " raise ValueError(\"Data not found\")\n", + "else:\n", + " print(cat_files[0])\n", + " print(dog_files[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Construct a numpy array as labels\n", + "image_paths = cat_files + dog_files\n", + "total_files = len(cat_files) + len(dog_files)\n", + "labels = np.zeros(total_files)\n", + "labels[len(cat_files):] = 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Split images data as training data and test data\n", + "from sklearn.model_selection import train_test_split\n", + "onehot_labels = np.array([[0,1] if i else [1,0] for i in labels])\n", + "img_train, img_test, label_train, label_test = train_test_split(image_paths, onehot_labels, random_state=42, shuffle=True)\n", + "\n", + "print(len(img_train), len(img_test), label_train.shape, label_test.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 3. Construct Model\n", + "We will define the functions to handle creating the preprocessor and the classifier first, and then run them together to actually construct the model with the Resnet50 featurizer in a single Tensorflow session in a separate cell.\n", + "\n", + "We use ResNet50 for the featurizer and build our own classifier using Keras layers. We train the featurizer and the classifier as one model. We will provide parameters to determine whether we are using the quantized version and whether we are using custom weights in training or not." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.a. Define image preprocessing step\n", + "Same as in the Quickstart, before passing image dataset to the ResNet50 featurizer, we need to preprocess the input file to get it into the form expected by ResNet50. ResNet50 expects float tensors representing the images in BGR, channel last order. We've provided a default implementation of the preprocessing that you can use.\n", + "\n", + "**Note:** Expect to see TF deprecation warnings until we port our SDK over to use Tensorflow 2.0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.accel.models.utils as utils\n", + "\n", + "def preprocess_images(scaling_factor=1.0):\n", + " # Convert images to 3D tensors [width,height,channel] - channels are in BGR order.\n", + " in_images = tf.placeholder(tf.string)\n", + " image_tensors = utils.preprocess_array(in_images, 'RGB', scaling_factor)\n", + " return in_images, image_tensors" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.b. Define classifier\n", + "We use Keras layer APIs to construct the classifier. Because we're using the tensorflow backend, we can train this classifier in one session with our Resnet50 model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def construct_classifier(in_tensor, seed=None):\n", + " from keras.layers import Dropout, Dense, Flatten\n", + " from keras.initializers import glorot_uniform\n", + " K.set_session(tf.get_default_session())\n", + "\n", + " FC_SIZE = 1024\n", + " NUM_CLASSES = 2\n", + "\n", + " x = Dropout(0.2, input_shape=(1, 1, int(in_tensor.shape[3]),), seed=seed)(in_tensor)\n", + " x = Dense(FC_SIZE, activation='relu', input_dim=(1, 1, int(in_tensor.shape[3]),),\n", + " kernel_initializer=glorot_uniform(seed=seed), bias_initializer='zeros')(x)\n", + " x = Flatten()(x)\n", + " preds = Dense(NUM_CLASSES, activation='softmax', input_dim=FC_SIZE, name='classifier_output',\n", + " kernel_initializer=glorot_uniform(seed=seed), bias_initializer='zeros')(x)\n", + " return preds" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.c. Define model construction\n", + "Now that the preprocessor and classifier for the model are defined, we can define how we want to construct the model. \n", + "\n", + "Constructing the model has these steps: \n", + "1. Get preprocessing steps\n", + "* Get featurizer using the Azure ML Accel Models SDK:\n", + " * import the graph definition\n", + " * restore the weights of the model into a Tensorflow session\n", + "* Get classifier\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def construct_model(quantized, starting_weights_directory = None):\n", + " from azureml.accel.models import Resnet50, QuantizedResnet50\n", + " \n", + " # Convert images to 3D tensors [width,height,channel]\n", + " in_images, image_tensors = preprocess_images(1.0)\n", + "\n", + " # Construct featurizer using quantized or unquantized ResNet50 model\n", + " if not quantized:\n", + " featurizer = Resnet50(saved_model_dir)\n", + " else:\n", + " featurizer = QuantizedResnet50(saved_model_dir, custom_weights_directory = starting_weights_directory)\n", + "\n", + " features = featurizer.import_graph_def(input_tensor=image_tensors)\n", + " \n", + " # Construct classifier\n", + " preds = construct_classifier(features)\n", + " \n", + " # Initialize weights\n", + " sess = tf.get_default_session()\n", + " tf.global_variables_initializer().run()\n", + "\n", + " featurizer.restore_weights(sess)\n", + "\n", + " return in_images, image_tensors, features, preds, featurizer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 4. Train Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def read_files(files):\n", + " \"\"\" Read files to array\"\"\"\n", + " contents = []\n", + " for path in files:\n", + " with open(path, 'rb') as f:\n", + " contents.append(f.read())\n", + " return contents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def train_model(preds, in_images, img_train, label_train, is_retrain = False, train_epoch = 10, learning_rate=None):\n", + " \"\"\" training model \"\"\"\n", + " from keras.objectives import binary_crossentropy\n", + " from tqdm import tqdm\n", + " \n", + " learning_rate = learning_rate if learning_rate else 0.001 if is_retrain else 0.01\n", + " \n", + " # Specify the loss function\n", + " in_labels = tf.placeholder(tf.float32, shape=(None, 2)) \n", + " cross_entropy = tf.reduce_mean(binary_crossentropy(in_labels, preds))\n", + " optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cross_entropy)\n", + "\n", + " def chunks(a, b, n):\n", + " \"\"\"Yield successive n-sized chunks from a and b.\"\"\"\n", + " if (len(a) != len(b)):\n", + " print(\"a and b are not equal in chunks(a,b,n)\")\n", + " raise ValueError(\"Parameter error\")\n", + "\n", + " for i in range(0, len(a), n):\n", + " yield a[i:i + n], b[i:i + n]\n", + "\n", + " chunk_size = 16\n", + " chunk_num = len(label_train) / chunk_size\n", + "\n", + " sess = tf.get_default_session()\n", + " for epoch in range(train_epoch):\n", + " avg_loss = 0\n", + " for img_chunk, label_chunk in tqdm(chunks(img_train, label_train, chunk_size)):\n", + " contents = read_files(img_chunk)\n", + " _, loss = sess.run([optimizer, cross_entropy],\n", + " feed_dict={in_images: contents,\n", + " in_labels: label_chunk,\n", + " K.learning_phase(): 1})\n", + " avg_loss += loss / chunk_num\n", + " print(\"Epoch:\", (epoch + 1), \"loss = \", \"{:.3f}\".format(avg_loss))\n", + " \n", + " # Reach desired performance\n", + " if (avg_loss < 0.001):\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 5. Test Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def test_model(preds, in_images, img_test, label_test):\n", + " \"\"\"Test the model\"\"\"\n", + " from keras.metrics import categorical_accuracy\n", + "\n", + " in_labels = tf.placeholder(tf.float32, shape=(None, 2))\n", + " accuracy = tf.reduce_mean(categorical_accuracy(in_labels, preds))\n", + " contents = read_files(img_test)\n", + "\n", + " accuracy = accuracy.eval(feed_dict={in_images: contents,\n", + " in_labels: label_test,\n", + " K.learning_phase(): 0})\n", + " return accuracy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 6. Execute steps\n", + "You can run through the Transfer Learning section, then skip to Create AccelContainerImage. By default, because the custom weights section takes much longer for training twice, it is not saved as executable cells. You can copy the code or change cell type to 'Code'.\n", + "\n", + "\n", + "### 6.a. Training using Transfer Learning" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Launch the training\n", + "tf.reset_default_graph()\n", + "sess = tf.Session(graph=tf.get_default_graph())\n", + "\n", + "with sess.as_default():\n", + " in_images, image_tensors, features, preds, featurizer = construct_model(quantized=True)\n", + " train_model(preds, in_images, img_train, label_train, is_retrain=False, train_epoch=10, learning_rate=0.01) \n", + " accuracy = test_model(preds, in_images, img_test, label_test) \n", + " print(\"Accuracy:\", accuracy)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Save Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_name = 'resnet50-catsanddogs-tl'\n", + "model_save_path = os.path.join(saved_model_dir, model_name)\n", + "\n", + "tf.saved_model.simple_save(sess, model_save_path,\n", + " inputs={'images': in_images},\n", + " outputs={'output_alias': preds})\n", + "\n", + "input_tensors = in_images.name\n", + "output_tensors = preds.name\n", + "\n", + "print(input_tensors)\n", + "print(output_tensors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 6.b. Traning using Custom Weights\n", + "\n", + "Because the quantized graph defintion and the float32 graph defintion share the same node names in the graph definitions, we can initally train the weights in float32, and then reload them with the quantized operations (which take longer) to fine-tune the model.\n", + "\n", + "First we train the model with custom weights but without quantization. Training is done with native float precision (32-bit floats). We load the training data set and batch the training with 10 epochs. When the performance reaches desired level or starts decredation, we stop the training iteration and save the weights as tensorflow checkpoint files. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Launch the training\n", + "```\n", + "tf.reset_default_graph()\n", + "sess = tf.Session(graph=tf.get_default_graph())\n", + "\n", + "with sess.as_default():\n", + " in_images, image_tensors, features, preds, featurizer = construct_model(quantized=False)\n", + " train_model(preds, in_images, img_train, label_train, is_retrain=False, train_epoch=10) \n", + " accuracy = test_model(preds, in_images, img_test, label_test) \n", + " print(\"Accuracy:\", accuracy)\n", + " featurizer.save_weights(custom_weights_dir + \"/rn50\", tf.get_default_session())\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Test Model\n", + "After training, we evaluate the trained model's accuracy on test dataset with quantization. So that we know the model's performance if it is deployed on the FPGA." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "tf.reset_default_graph()\n", + "sess = tf.Session(graph=tf.get_default_graph())\n", + "\n", + "with sess.as_default():\n", + " print(\"Testing trained model with quantization\")\n", + " in_images, image_tensors, features, preds, quantized_featurizer = construct_model(quantized=True, starting_weights_directory=custom_weights_dir)\n", + " accuracy = test_model(preds, in_images, img_test, label_test) \n", + " print(\"Accuracy:\", accuracy)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Fine-Tune Model\n", + "Sometimes, the model's accuracy can drop significantly after quantization. In those cases, we need to retrain the model enabled with quantization to get better model accuracy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "if (accuracy < 0.93):\n", + " with sess.as_default():\n", + " print(\"Fine-tuning model with quantization\")\n", + " train_model(preds, in_images, img_train, label_train, is_retrain=True, train_epoch=10)\n", + " accuracy = test_model(preds, in_images, img_test, label_test) \n", + " print(\"Accuracy:\", accuracy)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Save Model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "model_name = 'resnet50-catsanddogs-cw'\n", + "model_save_path = os.path.join(saved_model_dir, model_name)\n", + "\n", + "tf.saved_model.simple_save(sess, model_save_path,\n", + " inputs={'images': in_images},\n", + " outputs={'output_alias': preds})\n", + "\n", + "input_tensors = in_images.name\n", + "output_tensors = preds.name\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 7. Create AccelContainerImage\n", + "\n", + "Below we will execute all the same steps as in the [Quickstart](./accelerated-models-quickstart.ipynb#create-image) to package the model we have saved locally into an accelerated Docker image saved in our workspace. To complete all the steps, it may take a few minutes. For more details on each step, check out the [Quickstart section on model registration](./accelerated-models-quickstart.ipynb#register-model)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "from azureml.core.model import Model\n", + "from azureml.core.image import Image\n", + "from azureml.accel import AccelOnnxConverter\n", + "from azureml.accel import AccelContainerImage\n", + "\n", + "# Retrieve workspace\n", + "ws = Workspace.from_config()\n", + "print(\"Successfully retrieved workspace:\", ws.name, ws.resource_group, ws.location, ws.subscription_id, '\\n')\n", + "\n", + "# Register model\n", + "registered_model = Model.register(workspace = ws,\n", + " model_path = model_save_path,\n", + " model_name = model_name)\n", + "print(\"Successfully registered: \", registered_model.name, registered_model.description, registered_model.version, '\\n', sep = '\\t')\n", + "\n", + "# Convert model\n", + "convert_request = AccelOnnxConverter.convert_tf_model(ws, registered_model, input_tensors, output_tensors)\n", + "# If it fails, you can run wait_for_completion again with show_output=True.\n", + "convert_request.wait_for_completion(show_output=False)\n", + "converted_model = convert_request.result\n", + "print(\"\\nSuccessfully converted: \", converted_model.name, converted_model.url, converted_model.version, \n", + " converted_model.id, converted_model.created_time, '\\n')\n", + "\n", + "# Package into AccelContainerImage\n", + "image_config = AccelContainerImage.image_configuration()\n", + "# Image name must be lowercase\n", + "image_name = \"{}-image\".format(model_name)\n", + "image = Image.create(name = image_name,\n", + " models = [converted_model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "image.wait_for_creation()\n", + "print(\"Created AccelContainerImage: {} {} {}\\n\".format(image.name, image.creation_state, image.image_location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 8. Deploy image\n", + "Once you have an Azure ML Accelerated Image in your Workspace, you can deploy it to two destinations, to a Databox Edge machine or to an AKS cluster. \n", + "\n", + "### 8.a. Deploy to Databox Edge Machine using IoT Hub\n", + "See the sample [here](https://github.com/Azure-Samples/aml-real-time-ai/) for using the Azure IoT CLI extension for deploying your Docker image to your Databox Edge Machine.\n", + "\n", + "### 8.b. Deploy to AKS Cluster" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create AKS ComputeTarget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AksCompute, ComputeTarget\n", + "\n", + "# Uses the specific FPGA enabled VM (sku: Standard_PB6s)\n", + "# Standard_PB6s are available in: eastus, westus2, westeurope, southeastasia\n", + "prov_config = AksCompute.provisioning_configuration(vm_size = \"Standard_PB6s\",\n", + " agent_count = 1,\n", + " location = \"eastus\")\n", + "\n", + "aks_name = 'aks-pb6-tl'\n", + "# Create the cluster\n", + "aks_target = ComputeTarget.create(workspace = ws, \n", + " name = aks_name, \n", + " provisioning_configuration = prov_config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Provisioning an AKS cluster might take awhile (15 or so minutes), and we want to wait until it's successfully provisioned before we can deploy a service to it. If you interrupt this cell, provisioning of the cluster will continue. You can re-run it or check the status in your Workspace under Compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_target.wait_for_completion(show_output = True)\n", + "print(aks_target.provisioning_state)\n", + "print(aks_target.provisioning_errors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Deploy AccelContainerImage to AKS ComputeTarget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice, AksWebservice\n", + "\n", + "# Set the web service configuration (for creating a test service, we don't want autoscale enabled)\n", + "# Authentication is enabled by default, but for testing we specify False\n", + "aks_config = AksWebservice.deploy_configuration(autoscale_enabled=False,\n", + " num_replicas=1,\n", + " auth_enabled = False)\n", + "\n", + "aks_service_name ='my-aks-service'\n", + "\n", + "aks_service = Webservice.deploy_from_image(workspace = ws,\n", + " name = aks_service_name,\n", + " image = image,\n", + " deployment_config = aks_config,\n", + " deployment_target = aks_target)\n", + "aks_service.wait_for_deployment(show_output = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 9. Test the service\n", + "\n", + "\n", + "### 9.a. Create Client\n", + "The image supports gRPC and the TensorFlow Serving \"predict\" API. We have a client that can call into the docker image to get predictions. \n", + "\n", + "**Note:** If you chose to use auth_enabled=True when creating your AksWebservice.deploy_configuration(), see documentation [here](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.webservice(class)?view=azure-ml-py#get-keys--) on how to retrieve your keys and use either key as an argument to PredictionClient(...,access_token=key).", + "\n", + "**WARNING:** If you are running on Azure Notebooks free compute, you will not be able to make outgoing calls to your service. Try locating your client on a different machine to consume it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Using the grpc client in AzureML Accelerated Models SDK\n", + "from azureml.accel.client import PredictionClient\n", + "\n", + "address = aks_service.scoring_uri\n", + "ssl_enabled = address.startswith(\"https\")\n", + "address = address[address.find('/')+2:].strip('/')\n", + "port = 443 if ssl_enabled else 80\n", + "\n", + "# Initialize AzureML Accelerated Models client\n", + "client = PredictionClient(address=address,\n", + " port=port,\n", + " use_ssl=ssl_enabled,\n", + " service_name=aks_service.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 9.b. Serve the model\n", + "Let's see how our service does on a few images. It may get a few wrong." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Specify an image to classify\n", + "print('CATS')\n", + "for image_file in cat_files[:8]:\n", + " results = client.score_file(path=image_file, \n", + " input_name=input_tensors, \n", + " outputs=output_tensors)\n", + " result = 'CORRECT ' if results[0] > results[1] else 'WRONG '\n", + " print(result + str(results))\n", + "print('DOGS')\n", + "for image_file in dog_files[:8]:\n", + " results = client.score_file(path=image_file, \n", + " input_name=input_tensors, \n", + " outputs=output_tensors)\n", + " result = 'CORRECT ' if results[1] > results[0] else 'WRONG '\n", + " print(result + str(results))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 10. Cleanup\n", + "It's important to clean up your resources, so that you won't incur unnecessary costs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_service.delete()\n", + "aks_target.delete()\n", + "image.delete()\n", + "registered_model.delete()\n", + "converted_model.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 11. Appendix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "License for plot_confusion_matrix:\n", + "\n", + "New BSD License\n", + "\n", + "Copyright (c) 2007-2018 The scikit-learn developers.\n", + "All rights reserved.\n", + "\n", + "\n", + "Redistribution and use in source and binary forms, with or without\n", + "modification, are permitted provided that the following conditions are met:\n", + "\n", + " a. Redistributions of source code must retain the above copyright notice,\n", + " this list of conditions and the following disclaimer.\n", + " b. Redistributions in binary form must reproduce the above copyright\n", + " notice, this list of conditions and the following disclaimer in the\n", + " documentation and/or other materials provided with the distribution.\n", + " c. Neither the name of the Scikit-learn Developers nor the names of\n", + " its contributors may be used to endorse or promote products\n", + " derived from this software without specific prior written\n", + " permission. \n", + "\n", + "\n", + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n", + "AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n", + "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n", + "ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR\n", + "ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n", + "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n", + "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n", + "CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n", + "LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n", + "OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\n", + "DAMAGE.\n" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "coverste" + }, + { + "name": "paledger" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml b/how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml new file mode 100644 index 00000000..c716614a --- /dev/null +++ b/how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml @@ -0,0 +1,4 @@ +name: enable-app-insights-in-production-service +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml b/how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml new file mode 100644 index 00000000..f72bf1dc --- /dev/null +++ b/how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml @@ -0,0 +1,4 @@ +name: enable-data-collection-for-models-in-aks +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml b/how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml new file mode 100644 index 00000000..c6d8e84b --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml @@ -0,0 +1,6 @@ +name: onnx-convert-aml-deploy-tinyyolo +dependencies: +- pip: + - azureml-sdk + - git+https://github.com/apple/coremltools + - onnxmltools==1.3.1 diff --git a/how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml b/how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml new file mode 100644 index 00000000..7a41748d --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml @@ -0,0 +1,9 @@ +name: onnx-inference-facial-expression-recognition-deploy +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - matplotlib + - numpy + - onnx + - opencv-python diff --git a/how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml b/how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml new file mode 100644 index 00000000..614209c5 --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml @@ -0,0 +1,9 @@ +name: onnx-inference-mnist-deploy +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - matplotlib + - numpy + - onnx + - opencv-python diff --git a/how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml b/how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml new file mode 100644 index 00000000..a80e6bb0 --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml @@ -0,0 +1,4 @@ +name: onnx-modelzoo-aml-deploy-resnet50 +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml b/how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml new file mode 100644 index 00000000..c0145ec0 --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml @@ -0,0 +1,5 @@ +name: onnx-train-pytorch-aml-deploy-mnist +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/deployment/production-deploy-to-aks-gpu/production-deploy-to-aks-gpu.ipynb b/how-to-use-azureml/deployment/production-deploy-to-aks-gpu/production-deploy-to-aks-gpu.ipynb index 672f92f7..70e02ff9 100644 --- a/how-to-use-azureml/deployment/production-deploy-to-aks-gpu/production-deploy-to-aks-gpu.ipynb +++ b/how-to-use-azureml/deployment/production-deploy-to-aks-gpu/production-deploy-to-aks-gpu.ipynb @@ -1,407 +1,407 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploying a web service to Azure Kubernetes Service (AKS)\n", - "This notebook shows the steps for deploying a service: registering a model, creating an image, provisioning a cluster (one time action), and deploying a service to it. \n", - "We then test and delete the service, image and model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "from azureml.core.compute import AksCompute, ComputeTarget\n", - "from azureml.core.webservice import Webservice, AksWebservice\n", - "from azureml.core.image import Image\n", - "from azureml.core.model import Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import azureml.core\n", - "print(azureml.core.VERSION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Get workspace\n", - "Load existing workspace from the config file info." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.workspace import Workspace\n", - "\n", - "ws = Workspace.from_config()\n", - "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Register the model\n", - "Register an existing trained model, add descirption and tags. Prior to registering the model, you should have a TensorFlow [Saved Model](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/saved_model/README.md) in the `resnet50` directory. You can download a [pretrained resnet50](https://github.com/tensorflow/models/tree/master/official/resnet#pre-trained-model) and unpack it to that directory." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Register the model\n", - "from azureml.core.model import Model\n", - "model = Model.register(model_path = \"resnet50\", # this points to a local file\n", - " model_name = \"resnet50\", # this is the name the model is registered as\n", - " tags = {'area': \"Image classification\", 'type': \"classification\"},\n", - " description = \"Image classification trained on Imagenet Dataset\",\n", - " workspace = ws)\n", - "\n", - "print(model.name, model.description, model.version)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Create an image\n", - "Create an image using the registered model the script that will load and run the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile score.py\n", - "import tensorflow as tf\n", - "import numpy as np\n", - "import ujson\n", - "from azureml.core.model import Model\n", - "from azureml.contrib.services.aml_request import AMLRequest, rawhttp\n", - "from azureml.contrib.services.aml_response import AMLResponse\n", - "\n", - "def init():\n", - " global session\n", - " global input_name\n", - " global output_name\n", - " \n", - " session = tf.Session()\n", - "\n", - " model_path = Model.get_model_path('resnet50')\n", - " model = tf.saved_model.loader.load(session, ['serve'], model_path)\n", - " if len(model.signature_def['serving_default'].inputs) > 1:\n", - " raise ValueError(\"This score.py only supports one input\")\n", - " if len(model.signature_def['serving_default'].outputs) > 1:\n", - " raise ValueError(\"This score.py only supports one input\")\n", - " input_name = [tensor.name for tensor in model.signature_def['serving_default'].inputs.values()][0]\n", - " output_name = [tensor.name for tensor in model.signature_def['serving_default'].outputs.values()][0]\n", - " \n", - "\n", - "@rawhttp\n", - "def run(request):\n", - " if request.method == 'POST':\n", - " reqBody = request.get_data(False)\n", - " resp = score(reqBody)\n", - " return AMLResponse(resp, 200)\n", - " if request.method == 'GET':\n", - " respBody = str.encode(\"GET is not supported\")\n", - " return AMLResponse(respBody, 405)\n", - " return AMLResponse(\"bad request\", 500)\n", - "\n", - "def score(data):\n", - " result = session.run(output_name, {input_name: [data]})\n", - " return ujson.dumps(result[0])\n", - "\n", - "if __name__ == \"__main__\":\n", - " init()\n", - " with open(\"test_image.jpg\", 'rb') as f:\n", - " content = f.read()\n", - " print(score(content))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.conda_dependencies import CondaDependencies \n", - "\n", - "myenv = CondaDependencies.create(conda_packages=['tensorflow-gpu==1.12.0','numpy','ujson','azureml-contrib-services'])\n", - "\n", - "with open(\"myenv.yml\",\"w\") as f:\n", - " f.write(myenv.serialize_to_string())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.image import ContainerImage\n", - "\n", - "image_config = ContainerImage.image_configuration(execution_script = \"score.py\",\n", - " runtime = \"python\",\n", - " conda_file = \"myenv.yml\",\n", - " gpu_enabled = True\n", - " )\n", - "\n", - "image = ContainerImage.create(name = \"GpuImage\",\n", - " # this is the model object\n", - " models = [model],\n", - " image_config = image_config,\n", - " workspace = ws)\n", - "\n", - "image.wait_for_creation(show_output = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Provision the AKS Cluster\n", - "This is a one time setup. You can reuse this cluster for multiple deployments after it has been created. If you delete the cluster or the resource group that contains it, then you would have to recreate it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Use the default configuration (can also provide parameters to customize)\n", - "prov_config = AksCompute.provisioning_configuration(vm_size=\"Standard_NC6\")\n", - "\n", - "aks_name = 'my-aks-9' \n", - "# Create the cluster\n", - "aks_target = ComputeTarget.create(workspace = ws, \n", - " name = aks_name, \n", - " provisioning_configuration = prov_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Create AKS Cluster in an existing virtual network (optional)\n", - "See code snippet below. Check the documentation [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-enable-virtual-network#use-azure-kubernetes-service) for more details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "from azureml.core.compute import ComputeTarget, AksCompute\n", - "\n", - "# Create the compute configuration and set virtual network information\n", - "config = AksCompute.provisioning_configuration(vm_size=\"Standard_NC6\", location=\"eastus2\")\n", - "config.vnet_resourcegroup_name = \"mygroup\"\n", - "config.vnet_name = \"mynetwork\"\n", - "config.subnet_name = \"default\"\n", - "config.service_cidr = \"10.0.0.0/16\"\n", - "config.dns_service_ip = \"10.0.0.10\"\n", - "config.docker_bridge_cidr = \"172.17.0.1/16\"\n", - "\n", - "# Create the compute target\n", - "aks_target = ComputeTarget.create(workspace = ws,\n", - " name = \"myaks\",\n", - " provisioning_configuration = config)\n", - "'''" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Enable SSL on the AKS Cluster (optional)\n", - "See code snippet below. Check the documentation [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-secure-web-service) for more details" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# provisioning_config = AksCompute.provisioning_configuration(ssl_cert_pem_file=\"cert.pem\", ssl_key_pem_file=\"key.pem\", ssl_cname=\"www.contoso.com\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "aks_target.wait_for_completion(show_output = True)\n", - "print(aks_target.provisioning_state)\n", - "print(aks_target.provisioning_errors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optional step: Attach existing AKS cluster\n", - "\n", - "If you have existing AKS cluster in your Azure subscription, you can attach it to the Workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "# Use the default configuration (can also provide parameters to customize)\n", - "resource_id = '/subscriptions/92c76a2f-0e1c-4216-b65e-abf7a3f34c1e/resourcegroups/raymondsdk0604/providers/Microsoft.ContainerService/managedClusters/my-aks-0605d37425356b7d01'\n", - "\n", - "create_name='my-existing-aks' \n", - "# Create the cluster\n", - "attach_config = AksCompute.attach_configuration(resource_id=resource_id)\n", - "aks_target = ComputeTarget.attach(workspace=ws, name=create_name, attach_configuration=attach_config)\n", - "# Wait for the operation to complete\n", - "aks_target.wait_for_completion(True)\n", - "'''" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy web service to AKS" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Set the web service configuration (using default here)\n", - "aks_config = AksWebservice.deploy_configuration()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "aks_service_name ='aks-service-1'\n", - "\n", - "aks_service = Webservice.deploy_from_image(workspace = ws, \n", - " name = aks_service_name,\n", - " image = image,\n", - " deployment_config = aks_config,\n", - " deployment_target = aks_target)\n", - "aks_service.wait_for_deployment(show_output = True)\n", - "print(aks_service.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Test the web service\n", - "We test the web sevice by passing the test images content." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "import requests\n", - "key1, key2 = aks_service.get_keys()\n", - "\n", - "headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n", - "test_sampe = open('test_image.jpg', 'rb').read()\n", - "resp = requests.post(aks_service.scoring_uri, test_sample, headers=headers)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Clean up\n", - "Delete the service, image, model and compute target" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "aks_service.delete()\n", - "image.delete()\n", - "model.delete()\n", - "aks_target.delete()" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "aashishb" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploying a web service to Azure Kubernetes Service (AKS)\n", + "This notebook shows the steps for deploying a service: registering a model, creating an image, provisioning a cluster (one time action), and deploying a service to it. \n", + "We then test and delete the service, image and model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "from azureml.core.compute import AksCompute, ComputeTarget\n", + "from azureml.core.webservice import Webservice, AksWebservice\n", + "from azureml.core.image import Image\n", + "from azureml.core.model import Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.core\n", + "print(azureml.core.VERSION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Get workspace\n", + "Load existing workspace from the config file info." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.workspace import Workspace\n", + "\n", + "ws = Workspace.from_config()\n", + "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Register the model\n", + "Register an existing trained model, add descirption and tags. Prior to registering the model, you should have a TensorFlow [Saved Model](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/saved_model/README.md) in the `resnet50` directory. You can download a [pretrained resnet50](https://github.com/tensorflow/models/tree/master/official/resnet#pre-trained-model) and unpack it to that directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Register the model\n", + "from azureml.core.model import Model\n", + "model = Model.register(model_path = \"resnet50\", # this points to a local file\n", + " model_name = \"resnet50\", # this is the name the model is registered as\n", + " tags = {'area': \"Image classification\", 'type': \"classification\"},\n", + " description = \"Image classification trained on Imagenet Dataset\",\n", + " workspace = ws)\n", + "\n", + "print(model.name, model.description, model.version)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create an image\n", + "Create an image using the registered model the script that will load and run the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import tensorflow as tf\n", + "import numpy as np\n", + "import ujson\n", + "from azureml.core.model import Model\n", + "from azureml.contrib.services.aml_request import AMLRequest, rawhttp\n", + "from azureml.contrib.services.aml_response import AMLResponse\n", + "\n", + "def init():\n", + " global session\n", + " global input_name\n", + " global output_name\n", + " \n", + " session = tf.Session()\n", + "\n", + " model_path = Model.get_model_path('resnet50')\n", + " model = tf.saved_model.loader.load(session, ['serve'], model_path)\n", + " if len(model.signature_def['serving_default'].inputs) > 1:\n", + " raise ValueError(\"This score.py only supports one input\")\n", + " if len(model.signature_def['serving_default'].outputs) > 1:\n", + " raise ValueError(\"This score.py only supports one input\")\n", + " input_name = [tensor.name for tensor in model.signature_def['serving_default'].inputs.values()][0]\n", + " output_name = [tensor.name for tensor in model.signature_def['serving_default'].outputs.values()][0]\n", + " \n", + "\n", + "@rawhttp\n", + "def run(request):\n", + " if request.method == 'POST':\n", + " reqBody = request.get_data(False)\n", + " resp = score(reqBody)\n", + " return AMLResponse(resp, 200)\n", + " if request.method == 'GET':\n", + " respBody = str.encode(\"GET is not supported\")\n", + " return AMLResponse(respBody, 405)\n", + " return AMLResponse(\"bad request\", 500)\n", + "\n", + "def score(data):\n", + " result = session.run(output_name, {input_name: [data]})\n", + " return ujson.dumps(result[0])\n", + "\n", + "if __name__ == \"__main__\":\n", + " init()\n", + " with open(\"test_image.jpg\", 'rb') as f:\n", + " content = f.read()\n", + " print(score(content))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.conda_dependencies import CondaDependencies \n", + "\n", + "myenv = CondaDependencies.create(conda_packages=['tensorflow-gpu==1.12.0','numpy','ujson','azureml-contrib-services'])\n", + "\n", + "with open(\"myenv.yml\",\"w\") as f:\n", + " f.write(myenv.serialize_to_string())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(execution_script = \"score.py\",\n", + " runtime = \"python\",\n", + " conda_file = \"myenv.yml\",\n", + " gpu_enabled = True\n", + " )\n", + "\n", + "image = ContainerImage.create(name = \"GpuImage\",\n", + " # this is the model object\n", + " models = [model],\n", + " image_config = image_config,\n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Provision the AKS Cluster\n", + "This is a one time setup. You can reuse this cluster for multiple deployments after it has been created. If you delete the cluster or the resource group that contains it, then you would have to recreate it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use the default configuration (can also provide parameters to customize)\n", + "prov_config = AksCompute.provisioning_configuration(vm_size=\"Standard_NC6\")\n", + "\n", + "aks_name = 'my-aks-9' \n", + "# Create the cluster\n", + "aks_target = ComputeTarget.create(workspace = ws, \n", + " name = aks_name, \n", + " provisioning_configuration = prov_config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create AKS Cluster in an existing virtual network (optional)\n", + "See code snippet below. Check the documentation [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-enable-virtual-network#use-azure-kubernetes-service) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "from azureml.core.compute import ComputeTarget, AksCompute\n", + "\n", + "# Create the compute configuration and set virtual network information\n", + "config = AksCompute.provisioning_configuration(vm_size=\"Standard_NC6\", location=\"eastus2\")\n", + "config.vnet_resourcegroup_name = \"mygroup\"\n", + "config.vnet_name = \"mynetwork\"\n", + "config.subnet_name = \"default\"\n", + "config.service_cidr = \"10.0.0.0/16\"\n", + "config.dns_service_ip = \"10.0.0.10\"\n", + "config.docker_bridge_cidr = \"172.17.0.1/16\"\n", + "\n", + "# Create the compute target\n", + "aks_target = ComputeTarget.create(workspace = ws,\n", + " name = \"myaks\",\n", + " provisioning_configuration = config)\n", + "'''" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Enable SSL on the AKS Cluster (optional)\n", + "See code snippet below. Check the documentation [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-secure-web-service) for more details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# provisioning_config = AksCompute.provisioning_configuration(ssl_cert_pem_file=\"cert.pem\", ssl_key_pem_file=\"key.pem\", ssl_cname=\"www.contoso.com\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "aks_target.wait_for_completion(show_output = True)\n", + "print(aks_target.provisioning_state)\n", + "print(aks_target.provisioning_errors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optional step: Attach existing AKS cluster\n", + "\n", + "If you have existing AKS cluster in your Azure subscription, you can attach it to the Workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "# Use the default configuration (can also provide parameters to customize)\n", + "resource_id = '/subscriptions/92c76a2f-0e1c-4216-b65e-abf7a3f34c1e/resourcegroups/raymondsdk0604/providers/Microsoft.ContainerService/managedClusters/my-aks-0605d37425356b7d01'\n", + "\n", + "create_name='my-existing-aks' \n", + "# Create the cluster\n", + "attach_config = AksCompute.attach_configuration(resource_id=resource_id)\n", + "aks_target = ComputeTarget.attach(workspace=ws, name=create_name, attach_configuration=attach_config)\n", + "# Wait for the operation to complete\n", + "aks_target.wait_for_completion(True)\n", + "'''" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy web service to AKS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Set the web service configuration (using default here)\n", + "aks_config = AksWebservice.deploy_configuration()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "aks_service_name ='aks-service-1'\n", + "\n", + "aks_service = Webservice.deploy_from_image(workspace = ws, \n", + " name = aks_service_name,\n", + " image = image,\n", + " deployment_config = aks_config,\n", + " deployment_target = aks_target)\n", + "aks_service.wait_for_deployment(show_output = True)\n", + "print(aks_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test the web service\n", + "We test the web sevice by passing the test images content." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "import requests\n", + "key1, key2 = aks_service.get_keys()\n", + "\n", + "headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n", + "test_sampe = open('test_image.jpg', 'rb').read()\n", + "resp = requests.post(aks_service.scoring_uri, test_sample, headers=headers)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Clean up\n", + "Delete the service, image, model and compute target" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "aks_service.delete()\n", + "image.delete()\n", + "model.delete()\n", + "aks_target.delete()" + ] + } ], - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" + "metadata": { + "authors": [ + { + "name": "aashishb" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.0" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml b/how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml new file mode 100644 index 00000000..addf41c7 --- /dev/null +++ b/how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml @@ -0,0 +1,8 @@ +name: production-deploy-to-aks +dependencies: +- pip: + - azureml-sdk + - matplotlib + - tqdm + - scipy + - sklearn diff --git a/how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml b/how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml new file mode 100644 index 00000000..509d9b40 --- /dev/null +++ b/how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml @@ -0,0 +1,8 @@ +name: register-model-create-image-deploy-service +dependencies: +- pip: + - azureml-sdk + - matplotlib + - tqdm + - scipy + - sklearn diff --git a/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.ipynb b/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.ipynb index b236705b..c0273170 100644 --- a/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.ipynb +++ b/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.ipynb @@ -265,7 +265,7 @@ "from azureml.core.compute_target import ComputeTargetException\n", "\n", "# Choose a name for your CPU cluster\n", - "cpu_cluster_name = \"cpucluster\"\n", + "cpu_cluster_name = \"cpu-cluster\"\n", "\n", "# Verify that cluster does not exist already\n", "try:\n", @@ -370,7 +370,7 @@ "from azureml.core.compute_target import ComputeTargetException\n", "\n", "# Choose a name for your CPU cluster\n", - "cpu_cluster_name = \"cpucluster\"\n", + "cpu_cluster_name = \"cpu-cluster\"\n", "\n", "# Verify that cluster does not exist already\n", "try:\n", @@ -506,7 +506,7 @@ "outputs": [], "source": [ "# Delete () is used to deprovision and delete the AmlCompute target. Useful if you want to re-use the compute name \n", - "# 'cpucluster' in this case but use a different VM family for instance.\n", + "# 'cpu-cluster' in this case but use a different VM family for instance.\n", "\n", "# cpu_cluster.delete()" ] diff --git a/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml b/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml new file mode 100644 index 00000000..49144ae8 --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml @@ -0,0 +1,6 @@ +name: regression-sklearn-on-amlcompute +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml new file mode 100644 index 00000000..744335c8 --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml @@ -0,0 +1,6 @@ +name: explain-local-sklearn-binary-classification +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml new file mode 100644 index 00000000..51cc039c --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml @@ -0,0 +1,6 @@ +name: explain-local-sklearn-multiclass-classification +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml new file mode 100644 index 00000000..0d91a84a --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml @@ -0,0 +1,6 @@ +name: explain-local-sklearn-regression +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.ipynb b/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.ipynb index b98487cc..f55dbb9f 100644 --- a/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.ipynb +++ b/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.ipynb @@ -36,22 +36,6 @@ "4. Visualize the global and local explanations with the visualization dashboard." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example needs sklearn-pandas. If it is not installed, uncomment and run the following line." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#!pip install sklearn-pandas" - ] - }, { "cell_type": "code", "execution_count": null, @@ -63,7 +47,6 @@ "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n", "from sklearn.linear_model import LogisticRegression\n", "from azureml.explain.model.tabular_explainer import TabularExplainer\n", - "from sklearn_pandas import DataFrameMapper\n", "import pandas as pd\n", "import numpy as np" ] @@ -113,6 +96,13 @@ "x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "sklearn imports" + ] + }, { "cell_type": "code", "execution_count": null, @@ -121,7 +111,51 @@ "source": [ "from sklearn.pipeline import Pipeline\n", "from sklearn.impute import SimpleImputer\n", - "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n", + "from sklearn.preprocessing import StandardScaler, OneHotEncoder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can explain raw features by either using a `sklearn.compose.ColumnTransformer` or a list of fitted transformer tuples. The cell below uses `sklearn.compose.ColumnTransformer`. In case you want to run the example with the list of fitted transformer tuples, comment the cell below and uncomment the cell that follows after. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.compose import ColumnTransformer\n", + "\n", + "transformations = ColumnTransformer([\n", + " (\"age_fare\", Pipeline(steps=[\n", + " ('imputer', SimpleImputer(strategy='median')),\n", + " ('scaler', StandardScaler())\n", + " ]), [\"age\", \"fare\"]),\n", + " (\"embarked\", Pipeline(steps=[\n", + " (\"imputer\", SimpleImputer(strategy='constant', fill_value='missing')), \n", + " (\"encoder\", OneHotEncoder(sparse=False))]), [\"embarked\"]),\n", + " (\"sex_pclass\", OneHotEncoder(sparse=False), [\"sex\", \"pclass\"]) \n", + "])\n", + "\n", + "\n", + "# Append classifier to preprocessing pipeline.\n", + "# Now we have a full prediction pipeline.\n", + "clf = Pipeline(steps=[('preprocessor', transformations),\n", + " ('classifier', LogisticRegression(solver='lbfgs'))])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "# Uncomment below if sklearn-pandas is not installed\n", + "#!pip install sklearn-pandas\n", "from sklearn_pandas import DataFrameMapper\n", "\n", "# Impute, standardize the numeric features and one-hot encode the categorical features. \n", @@ -141,7 +175,8 @@ "# Append classifier to preprocessing pipeline.\n", "# Now we have a full prediction pipeline.\n", "clf = Pipeline(steps=[('preprocessor', DataFrameMapper(transformations)),\n", - " ('classifier', LogisticRegression(solver='lbfgs'))])" + " ('classifier', LogisticRegression(solver='lbfgs'))])\n", + "'''" ] }, { diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml b/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml new file mode 100644 index 00000000..7d213fab --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml @@ -0,0 +1,7 @@ +name: explain-sklearn-raw-features +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model + - sklearn-pandas diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml new file mode 100644 index 00000000..067971f5 --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml @@ -0,0 +1,6 @@ +name: explain-run-history-sklearn-classification +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml new file mode 100644 index 00000000..69f81e2a --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml @@ -0,0 +1,6 @@ +name: explain-run-history-sklearn-regression +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml new file mode 100644 index 00000000..4955c060 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-data-transfer +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml new file mode 100644 index 00000000..3c4f7d04 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-getting-started +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml new file mode 100644 index 00000000..f6ab4807 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-how-to-use-estimatorstep +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml new file mode 100644 index 00000000..ec9828ff --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml @@ -0,0 +1,6 @@ +name: aml-pipelines-publish-and-run-using-rest-endpoint +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - requests diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml new file mode 100644 index 00000000..f3d36970 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml @@ -0,0 +1,8 @@ +name: aml-pipelines-with-automated-machine-learning-step +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml new file mode 100644 index 00000000..0f034033 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-with-data-dependency-steps +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/compare/compare.py b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/compare/compare.py new file mode 100644 index 00000000..1784bc7b --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/compare/compare.py @@ -0,0 +1,24 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +import argparse +import os + +print("In compare.py") +print("As a data scientist, this is where I use my compare code.") +parser = argparse.ArgumentParser("compare") +parser.add_argument("--compare_data1", type=str, help="compare_data1 data") +parser.add_argument("--compare_data2", type=str, help="compare_data2 data") +parser.add_argument("--output_compare", type=str, help="output_compare directory") +parser.add_argument("--pipeline_param", type=int, help="pipeline parameter") + +args = parser.parse_args() + +print("Argument 1: %s" % args.compare_data1) +print("Argument 2: %s" % args.compare_data2) +print("Argument 3: %s" % args.output_compare) +print("Argument 4: %s" % args.pipeline_param) + +if not (args.output_compare is None): + os.makedirs(args.output_compare, exist_ok=True) + print("%s created" % args.output_compare) diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/extract/extract.py b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/extract/extract.py new file mode 100644 index 00000000..0134a090 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/extract/extract.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +import argparse +import os + +print("In extract.py") +print("As a data scientist, this is where I use my extract code.") + +parser = argparse.ArgumentParser("extract") +parser.add_argument("--input_extract", type=str, help="input_extract data") +parser.add_argument("--output_extract", type=str, help="output_extract directory") + +args = parser.parse_args() + +print("Argument 1: %s" % args.input_extract) +print("Argument 2: %s" % args.output_extract) + +if not (args.output_extract is None): + os.makedirs(args.output_extract, exist_ok=True) + print("%s created" % args.output_extract) diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/train/train.py b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/train/train.py new file mode 100644 index 00000000..961f5ebf --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/train/train.py @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +import argparse +import os + +print("In train.py") +print("As a data scientist, this is where I use my training code.") + +parser = argparse.ArgumentParser("train") + +parser.add_argument("--input_data", type=str, help="input data") +parser.add_argument("--output_train", type=str, help="output_train directory") + +args = parser.parse_args() + +print("Argument 1: %s" % args.input_data) +print("Argument 2: %s" % args.output_train) + +if not (args.output_train is None): + os.makedirs(args.output_train, exist_ok=True) + print("%s created" % args.output_train) diff --git a/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml b/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml new file mode 100644 index 00000000..fb3c108d --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml @@ -0,0 +1,9 @@ +name: nyc-taxi-data-regression-model-building +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - azureml-contrib-opendatasets + - azureml-dataprep + - azureml-train-automl + - matplotlib diff --git a/how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml b/how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml new file mode 100644 index 00000000..ac67d296 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml @@ -0,0 +1,7 @@ +name: pipeline-batch-scoring +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - pandas + - requests diff --git a/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml b/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml new file mode 100644 index 00000000..a77e6922 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml @@ -0,0 +1,6 @@ +name: pipeline-style-transfer +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - requests diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azure-ml.ipynb b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azure-ml.ipynb index dddd63a8..4cb487ae 100644 --- a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azure-ml.ipynb +++ b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azure-ml.ipynb @@ -19,12 +19,12 @@ "The interactive authentication is suitable for local experimentation on your own computer. Azure CLI authentication is suitable if you are already using Azure CLI for managing Azure resources, and want to sign in only once. The Service Principal authentication is suitable for automated workflows, for example as part of Azure Devops build." ] }, - { + { "cell_type": "markdown", "metadata": {}, "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.png)" - ] + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.png)" + ] }, { "cell_type": "code", @@ -257,4 +257,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb index 4cb487ae..047068f3 100644 --- a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb +++ b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb @@ -6,8 +6,20 @@ "source": [ "Copyright (c) Microsoft Corporation. All rights reserved.\n", "\n", - "Licensed under the MIT License.\n", - "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "## Authentication in Azure Machine Learning\n", "\n", "This notebook shows you how to authenticate to your Azure ML Workspace using\n", @@ -19,13 +31,6 @@ "The interactive authentication is suitable for local experimentation on your own computer. Azure CLI authentication is suitable if you are already using Azure CLI for managing Azure resources, and want to sign in only once. The Service Principal authentication is suitable for automated workflows, for example as part of Azure Devops build." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.png)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -92,7 +97,7 @@ "source": [ "In some cases, you may see a version of the error message containing text: ```All the subscriptions that you have access to = []```\n", "\n", - "In such a case, you may have to specify the tenant ID of the Azure Active Directory you're using. An example would be accessing a subscription as a guest to a tenant that is not your default. You specify the tenant by explicitly instantiating _InteractiveLoginAuthentication_ with tenant ID as argument ([see instructions how to obtain tenant Id](#get-tenant-id))." + "In such a case, you may have to specify the tenant ID of the Azure Active Directory you're using. An example would be accessing a subscription as a guest to a tenant that is not your default. You specify the tenant by explicitly instantiating _InteractiveLoginAuthentication_ with Tenant ID as argument. The Tenant ID can be found, for example, from https://portal.azure.com under **Azure Active Directory**, **Properties** as Directory ID." ] }, { @@ -150,31 +155,27 @@ "\n", "Note that you must have administrator privileges over the Azure subscription to complete these steps.\n", "\n", - "The first step is to create a service principal. First, go to [Azure Portal](https://portal.azure.com), select **Azure Active Directory** and **App Registrations**. Then select **+New application registration**, give your service principal a name, for example _my-svc-principal_. You can leave application type as is, and specify a dummy value for Sign-on URL, such as _https://invalid_.\n", + "The first step is to create a service principal. First, go to [Azure Portal](https://portal.azure.com), select **Azure Active Directory** and **App Registrations**. Then select **+New application**, give your service principal a name, for example _my-svc-principal_. You can leave other parameters as is.\n", "\n", - "Then click **Create**.\n", + "Then click **Register**.\n", "\n", - "![service principal creation]" + "![service principal creation](images/svc-pr-1.PNG)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The next step is to obtain the _Application ID_ (also called username) and create _password_ for the service principal.\n", - "\n", - "From the page for your newly created service principal, copy the _Application ID_. Then select **Settings** and **Keys**, write a description for your key, and select duration. Then click **Save**, and copy the _password_ to a secure location.\n", - "\n", - "![application id and password](images/svc-pr-2.PNG)" + "From the page for your newly created service principal, copy the _Application ID_ and _Tenant ID_ as they are needed later.\n", + "![application and tenant id](images/svc-pr-2.PNG)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "Then select **Certificates & secrets**, and **+New client secret** write a description for your key, and select duration. Then click **Add**, and copy the value of client secret to a secure location.\n", "\n", - "Also, you need to obtain the tenant ID of your Azure subscription. Go back to **Azure Active Directory**, select **Properties** and copy _Directory ID_.\n", "\n", "![tenant id](images/svc-pr-3.PNG)" ] @@ -229,6 +230,20 @@ "\n", "print(\"Found workspace {} at location {}\".format(ws.name, ws.location))" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [Register an application with the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) quickstart for more details about application registrations. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -252,7 +267,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.4" } }, "nbformat": 4, diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-1.PNG b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-1.PNG index 0db6725e9c86fc19204e7a18272d5a4278c5ee90..26d6b335107afd478db03cd7ee273b86a6da87ca 100644 GIT binary patch literal 99315 zcmbTdby!qi)IT}~paLR_bSO%ffPgeeNlABuw19LoA_&qTokK`>H>1+sHN;2^-OVs@ zkAC0xcYlB0=kDj3nRDix{p`J0?X^B@?J(8%a)b{kAAmq0Liu;n>L3t~6$o^@^WJUX zABrFE?*KoyT-D{?f+|L+w}G3x){@GSAW(G-KFSOSxWE7Dot`TQ^sw{q=T@&%(FYI+ zn=dadsp)0Bw{+j|>GT5Lk))b)Cn3qKs;cIc%ka}N^^fBN0||An5)FS(sq;ECDsk|k zH@_sMgbdmv)H!t@e9PTPlKsMV@U6+I=-wgkX@+k~tBa&I+Fx8i$aP~Vl^6fbXezHO zjonmH4lsp(jhu}hL-aq_*+HCt=l!=;fruFYyOK!q_VyM%9pY7f{kqwt)^Vxj>dK1~ zM0CrI;(uP4xr>A2e>N^>{(Ei{0v2;x7U912mAZuh1j_zdYy!7mKU!H{ChUF<0<9O! z&0#SD_A?kXUe{|0D^f1wz1>|lM)t7C$i2zJn3T`I%Zh4iYu^D>Bq2DAp`Oo`apk@R z0yUSpSk5XuWeagaX(-*9d;RIpPXV}}hLY~e!9l?3#_&?BXM`G(Uq-?n!$#5?*e zVR?lPi)<`@x2wxZ@b$spW&CYFIZ=pvJrA*0%W;Lv4lfJb*q?^H#WlkH@9A}cY!oFJ zbG^xup2TIssjw+r;Gq3#S6CezH4Tj|&+FH2J5$Bi3v811 z*n`$X4?E`Z8cRj)7vfDVEzy*GcGD$VSQ}<;kG;7Ptj_H7$gV==Oe z6>-d?m>Thq2_^JVSGq0YLmM|smnGz0Vl|v*PS2~yn&AKmK(b(eY&pW}Y^nG^{_zXT zRh_7{R^iUnL0%q;yKSl3k>l)K9>SgGs-EXp8ntd95DW@7PND>cFDHI%Tgy60EH*i> z8922wFw!awCUN+lZAk`lqYbdTl~7U+{REG`X4g$-CNZ%r7wP7kR-E~n{|>EUz%8SMjCH#Za_*@T(f^yg=MGR{v0>pMF_o(GHS`3mi*lfij= zEmBzuzpD4U*1{dz5j*s9pB@;y>t@#8^m6WeIKR~CrHwE`cgPpFtq@uqD0Wjv8jvj#+}>y!6AYN`Hr&q@b>$54Lmf}5xTl>AH|kUvKdt4J3iSWE z>wm>--P$*F|Nf2V%c-(R%NwaIXiGqAdolW<`8ihuP$3?$}#HXQ168cyS*5^~!DnBL-ZzLO^x!}#)LnNjOS^JW%rGXgcGWA`fm z!M^>3iNx88Tf_PGWTE9?(iAT31$cXcL0laAkVXg)#b`!@Mn`3~!Wmp#T)Xbmcj^2N zHoHpvulV`-85w(K1yp?DfTGBV-?@Ez=m!6u<;C87{75O0*p>~rC{JFEyjxZx5W4OG`ym(LL`(fm$zpJd$X8s97CJEw-t}+Js z@u!^lM4jB7QM>$zW08m!0y`rk!DfdAz3%32wjgQ1rjvXk<9wOSVNmdKzQ$aCvPdoO zbdwh&ShM0aA)nv5w%XC?dr9`@w~DUl`$*w?VdFgShN!t82a2s}GkqG#*caYV9ch~G zwS|3P=}2rB4JPBJgMRWmzj7}1ETd!^Os{xtan+eYqaRX17(!kCxSpG}B+%e3TV|~k zn@B*S5U->`1d@2T%EHL1jzde_u<)zGnec~F`OZ2X{abNNjoqq4WdWTPPg-YrniC9T z`I8=pD9<>&?^#Z#u(W+7DXYcX*rbDyyhpKMWAl`I{!=X)1o}Es3B@R=*#NrQ?Ixzz z>cQbjF?a9UCaIgMuX$i}luJlRUumn%uz6x+IciiKD~L{&qvrYWF4$_SNbR+J>T7$zzVccPZB3UZ>#|?&&)0^+o-w*n@_+O~p4oE8-PmBq zQilqAb7dlwjN@5ci$ME59sV~i0ZAX42me68-xGF*;R*h@6SU=;xQkkbD$-DF-|jw z-zKwOzd?<{RIVISk`znpA8JA8^Tt)vstN2QtC2D#|Hdh+)#i#Q0W5Wth9V zv)!W11x3*y<&AJxs4Fk)go^(B%K+KcBk^=F!O;8AhaixyR*9x<+ysk$SB0cwJ=?5$ zLs|bf-1{=0*E|}K>NK852zw^5z}dqg%`ek&_rz7ahL_s4o_Mk*yD5aFU5~Eq90ecL zMi4ZX_FqP@DHz1y^u9`GS|P*YCRj;_@xwsI@TKFy9ywIZx9L(=Y69 z|4=6d)2eVs?Rh(l2Wy=s%@RDc%iFu^eH6aim|vhD`P+S9jPjA8{k#uMM;PdLou& znE~?@^=q54sbURyz>Py(qDqBPt9FHSL`^?ed&jFct%-v-;{Ucz_c$ z{3)Ap%a4bP{F15p3W?#7L+j;Inx@Nb-sY>fyu7{nY$v22vJyOaU^i1X4_H`Oo*WU3 zN`w(I7GOVFpxi-3WjkNPz&C2H^OiG*Zy?!Z?BU9(+{7m8V!>&C_u1e{(EZSjkK%cn zhes7&Cnhwr5K6L+b$*q!${0ipPp8HXrGUL zkS-2Rhe2F@sD|&|uw5j}i1_bA`A#>vkPO;JrPVsdc7~Gj;aCeSMtn7%q_Ai7Zba4t zcfE#JJ6$v~&DV!q@wr|}gazqS)xX0`>XJ-KVb}4Hj!D8{5a=`B>duY};0oPb9!A&> zpYP5_Jgw!cVVBx{#z#qA*m~2RMK8g@n#A>Dj+W$2;8eTbL8;MJ>FTqf_- zYORz7i&nM^AqTK}w!&1gP!Ii!7R?$AXHcezgGh#c&at9Ck;w#_k$a zaOR|mbUC}?H-b~1&s*miU;6G-am&fWc$~Wm*z8KCLn~jiWY$>gryod(-|W@qjJCI} zzq8v;s`NKBI<7DtQEN`DLhg2_>nH~-Rqnnze>^THCr_Rx`~W9~uqj+kfWMOzhNuv&i`{Y8oBq*B8%4+F?21L*GNBUf zvPkV`%+#YgTVyUr#5XVr&)6ON1O!+xgMM}v__ul}CUeBMs3IWYXePxDeyB`!$DSz&3l#YH=a5ZF*&x1Ld94^N8Ov4O*`u+zE3u)KWqaGKEu z&V9U|{YQVWr@xnUsCa?xvU#ynAF(9;(&^3r2u&)DhC9pYoqf!2iSzYpj07Htga_~v zh>O1j=C*$Ob}tRiHaB14<9vUp_M>83v#=$XIxMc9 zI_gb;yjsRm8%r!aGi7HH8G*$twU{f6%DPF~l)7mX`s9DDb=e<-=nMbGinu8|+nwxv z;vSJeyk_28yLjq&0HON;kDyTEFZQ`Ksk5ehpP7bEz@OHhxl#wB;+p+|=~7^_?Am2> z))4TqtFG?W@2^ifP&IiFlOS9|>HEG#+_iolKjjFqQFv^aJEzD#gQdNCtaj*s||njvr~7y zll5!}8Q!d+m`T@>RbtBCqf1u4Cy$>LYw>k^;rTM4HyD8xE#R2VaKMh-+ zM$uCBXVbA)S#fr6dUv3_f7=&!<IcE z0$w$s%s>Ps^8wLp4e@V~k7wkwn_@OUfQYhK6Mc3I<=_`w8qV3gXIH z>!Z933JEE^)+1v%Qm0MO&lzI=`q4Dm8sDDy@JvompLc~l$r$hX#lFu}w^rMM?GX@KShqg$E4X)gduJ;~ML@T3eI&s%6AG2X~Sc9T2uh2*JLqn-dCcCUX!hKCfjU2)o zMFN{kY_m#Z%OYX@X|rMwl37hVU9IXU1=RGD>Nt+)a(L}drkZoMy# z5pHtC+~l@B0qg_)@WOWm%IUJg)<8%KMjhkrH;#(kM4qz5yEHhBLIM_nAYXNh_K8J1 zp4co3&|wS)laS3n7VYBhj_}iNaoYwQl4CtUV7SHu)==#I?~?<+^SCYr0;U#?`_QUjL$HI^)yE$6(i}w2mTyCc;p)WAr^mB5HsM#1YRl2c9nIuWk+0or4ZVN>n zhp{Hnrasw-##dt<@lyPt>@kY$zafcAg$Xq4@a5wNgl~9>2xx@d&UdB(6eBxZrvnqa zH(zTFxI{p>k(~ix5S&IWn*8T+k0~i(P%suiU(~mN-FUR1pkTr==jaLu$#1suVr;(i z*v=F}0u+2*`oA-nSGP?AV%A1y^3w+U*L+k|Q~(H%8z~x&MVnk6EV0>njgEzp(9kI3 zY<(>JZC+jW35fkd-dSw~xfh1;BQJf=5dq;R1EV*E*P5y$WdY6SW0|mIJk*%p{2cV! zU_}-uGYjiHI5gtuIdX7izaRL6=Uiw8wtEQ^kmVzeA(}yR*Qp(^x9@5nyf0bY$+u2V zP+TToTq%e9zoi#rr{M+%{8*(z9i(#%_WB=Q4$Z!wQP7Zf)UTbX2oD2SetY8`Fnr|1 z<9MpUfEUWIfW8@Q5#9L4-YH9?H`TI6(?k z-tCwxnv+pfQK3d7^BXtz-{_PPKczg1?9IHgJanJ&0X-gFEE(Gw4iiVeK8^~pJs#{i z!yqH0CP;(96fJ9hC5nT_T#Y^B^_Loj#%fRSv97EHv-$VbDQ#r#%qIe=zZ7C*3tGx_KUauvP*CpVGkQfG#}UA z@Vfmw5a^kPIOggF65pl!;wV$agGCjMvm?OH9D>IU0@2zHJ6s|{uS^0 zCxFq5n7@|vU&{S8o+oGGwSsd{`$&&c?e*nw3isNq3Ah~~T$ZS>&BXk?HyU|POiV0t z4-9yTB#-6fj9lp02L-HGFpbFBAES;GI ziaM`$C2<(Mm6D=6Do@?w0Rpkxcz@ArZzAB^l(Y2^`0HQ$97_4Wm^bL>?f>Rb>%Pk$ zcN_Fsq}o4ppX=?N&vKe-*4EZ>cR?U#@)0HmZy&FwrlxPUAW)yOv-2@NDW{0xl&HKFvMUl7?=1!lYzK*0U_!fh}|Bm+z`;GI? z?>mGk6_X%;yjZ!dyHl5Bsyu#c5Odo9IZhfwH$+rrD?8Qkk@SuFbyzBowco-wT9Bj_Vo&QVs zP9K>aSD;#>B&XuNu|+&n9oeMbW6@=VjHM+% zSpZ3~udglFO-!`EOQH7P;XV^QR1sMY#uT~}XUBj};+;JMh3Zcajm(W7HXTn|_YdqI zIC+KJ%E;4RdML~`^9gXtG>L#fEBvy-*)AS<@tN;C3bt8>9*SO&%9ZxmfcgA(<=qsH89c#FDMwlx#IXl0}1 zUvq&hqeG=DFBn~CuTM8Ohf*gCRp^9$2y?|g8Q<*HSITrobM2jYRq>ocjs(gt?vZnu z($&L)*6P379N#A#+AVzS#O7+rpXd`X6AQ0xqEvJ%9l_sORtp(YuW1QAc}IFFOMCc_ z172NEoj-kgByW)rK_n)4<6XXKZ`3O0dVPWbl2Icm+%Jy(asw)w=>-QRQ6?^Zea-b- zwQGnkb)(kuOm7Z#)n~J7Beb)oPEPf74|FsHVP} z5y|oT={z_Dufy}+!!Xjxaf{4UEPZ_r6X~@EbLEha@rS;7^A7Fbv9Hs{X6Q-bw5X=q zQ{7Hh0yp8Jcf`;0f}W@6>BN&HSU zVVLa5p$vr4k2QfaD1S{mjK_DXzH~-dWI?ZCUiGjwq~5N+9F56TnL_O4bB>kg!Ojk$ zVGe9B<~L ztn@`v7`rXYwJ&nVam&G*H`ew0+_ryn%b?GMcFBSFi7RfcXDqkh5fYuYUH?YRpqPY$ zU&66RokYax0ukO6T@=h=j^)&=e`3YXb}jOegQNA>W975FP3WAFOQ&#ReHeXKr4Zg> zN3D#a@S6nG*|dw@#*=*5&ZPC^h$<@*rW#r!z-aW&OBO+jj830zU9QTFWK2ZzIB1yD6#jpWa{^?*Q}233>G4ggt?@wFyiVl zU>gIPz4RyC*z^+Z(g7wayqvT6Q-{fsv5YmK`s{_osH3g#Sna{k%HdL*$o3vzzG4;W9>km$Z{ZoSdX+QFx$+CedX$) zA*v|swe`XuTn62sUkeR1BIP49T(OT}Y9su6l6gifOjCI|41YgLsg@A2rt+^$IN2=> zA2^Z>tZkaBBfr{El*|5_to=I2Xw(j*iqfxrNf>eiAc8}6z1i=2i!XzV3u+(dk~;7L z9LQ`QpdYt3ZR^$#jcnmzoA*Iljdu-kn!75xr55jL-*M^oa#g;J-~8Mc^$_+pZ;Eh& z{O~PWtkLaNRNAwNnTuRC_ZU9AQe|Dc{{-);2d)!{$Q(9Rj7LP9!R@-Szt9-^km4~3 z$=TGTN`yD*m|p5>-6g}fx}Wi{*M`s<`s%Kme<zQZbh6%=mt~d*c3q8pHk=bDk^+;g7lJpoNU|%Kyv;` zkgmqdwJ(pK3G^EWB_xo{+dtb#UH!vYII2{O?4oSTfR{Npo?iCDf;+393Ut_O4ZL|g zn^Nen*nV?4ec5QUJ!IJG;eb5bUTSe4Y=cTTq&Ql}9SVxIv%Pft%#<(e0lsW4gg;TM zj5Z6L#5&kBnRi>ni91X_ewdZT(TFA~{u?8N(EIo#GlGVlOvV;o|`fh-V zk(u~ad!KHY3q660GA#3}*KPGKhaR4y?NyG%JObE=+Gj=JI? z5@qCmr~Y<;#^E4;>B2y|ogvrp*40MEp-1~X_oP$N(4%T(t49yMD#L!qO^T;}>!ZVI zn9wdx{gvW@iT(T+=USUdo-|Gmh}h`Fax{7Iu=-_pdT9NUZJogD+$uy+F;~_&^`LJY zg@P@%kZwcypDM&fyh8m_8pN(|4p2=SM|03T29g}$FmTuGlOMChI=YXz+Pzl42E2t@3ay%B=8 zZ~iPd&s}Q6EB#j%^9-6Z2zv0~e=C7Vn7v;8uQUJu!LK(4-^2NOy-Nf(XjT~dukh%z zNGRtWX6B=)`~PSF!g)Ze^xqW-gg*wX=)d;=H-7knx6gBg@uTzax(izWl7J1M+0dEq z|MlzFb*BS8mqGN1lS*P81gxpFnUb(3>_M$U{6zK5`le)SGD+(T{_U)kzqI}Q1qcuS z#Hrp4iZVhbbq+6#-R54#i)l7>VljOEivg>+v+)enIo6*__O3s~5N*m)cVs2%ZV%Ng z4;yDuq^@31#6ItMsxITWT;+s6!nJEu=)Nm7%K7dQMNFhG_ZpkM@J1B8GOX-3y$KO) zq!K6xS}K}pV&(>rRDh$TEx_2ZpSGj>nXWh56Q*9rDf&F;-mceaB|Rf@{WSCr+F;#WLb$!j%jU2z`n4A5z~w zohn6O--+xBti^Hw3k1GtVa zo4(g9YtN+M_S^N@PO=b4GY4b&zz6&c{|=$=-(A+(=&T)h@M!e;Kdn0XaxIg^;|$e{ zgR}hQj(rXUxn%U3k4AavTR&A@W+Q$+ehMaK1!WLxQQ?5LJf`Wk|EXg_c%+`-E-pt>A@-mA39suU|Ej7I_4SMx z{cakUO7qLiS3v3X$rI35iS7}nm;&BuE^5x?>f%M;=JCer6rmtOtN}S}d zVv3Gc_yPG~kFl~~2SzGuQ*+*Fu0)DT=Bdc6>T{K2;=VVe<`#Cx_( zJ_>iXC=>171FgzP{!Y#w1JnNQX_G5xEAk7{@t(Oq70aSXU3R+lpcrqb3R^i`lWIa! zzRfL9somJ3r)^P}BYVM%RRj)o4E2=4HZMdDJoGM}sn66*w1${OIv(%p94sCn#2A>j z2$$IJXhvTc1!dXCp$kPv%2Mm!jr$Oge7zxP7`a*#=hhHv%myzd>*)BoU!`|cc#al@ z+MR7EnrAUi-tMWX(b*JV_>&s8Voh?G96|uDUoGfRxx~sC41DnwhT2KWJ)7v)5iZT* zq;q{GuU1BV+iLM~DO0{tVQLEvp|28wN(qWx>&VI;;da$!P5GKfuoR0~ou7fcZ1gV0 zp3)9{ZO>`XB0U{t-(C8X>I|hh&Yn?66RI2vr)DN6{xexJ4~Nslo`0YJ4X+}qW1%HF3gW+{iaAZr7>niDMqLds)RDn~GVX&k^K2XcM3<5g=pNnT=PpRQ=Fu;eDW1>GZTy< zX@!UhdI$5VZkF-=wj$#NR3wtHe%=u!#U&~#I;?Amdd__qC_~Lh^A?V*=5dy9tX$;x zf44B&>3tJM#Zqhw)M-lBt0NSJ!tb}zWWtTxe+l_%AnT&(73W0Ww)-wPF2`-%sz{gf z@<=mw@<44!p4WjcjEAmv9H4Dup2O3SEKh#T+@}a8=;$Hbz0^1Y_8u1OVlxtN9OT&& z(DGtCUYk_grmfD_XoiYemn(bWHW9ZObGHV=08(Y{Y_gSubyC)-wmxb$xqW@y9+BCz z)*z!~V{yVMmRDbdNgwaJS-Qg@G_n9S1L@?4q-PdDTxc5ihl2UL*~<(ol(k*=F9inN z4W_PI=g^&$C8xD(L!+ZJTeTzh1LDqy0y+I?_!QVrv#mqH@!N#gYE~xQYb~NA*!=j1 zKO4d!Rv+|#>XKfT)Hq5YtierrrJ~!7n0g=f77LdYukaYx()nXy-04X>1%(#l<{FPz zO(`A98ywZ%>iR+vk?*_g7pKxX1@_Zt)C>yER77g8&S0~pgGSRMRmJ8~-4DDDVvkzk zo-?rT-xJ&#T@cjZN&pZ9{d_U7tcJS`Ijp^^wyuc#!*ASQUCI&K$=RW0o+e;lB0Km? zvEw}2Uxg~Ct zCX@%YhnIGi$U8p>vDv7W4j_Z3vMN)uV1xZHe~rCvfV0bSDbxOU5xC&L< zn+gbNRlNDRPxPKV7EW18ml|h#%i`id%M!lP?HOrbiSmPEGG1@$w+GypnHNM3o1t*h zx~0K^ggvcSlJ6D9r&#Ddfp4*B9}ZXB8O)Y{n-Mi1y9@$3Pes%p`6ptSv{myPgdVMi z{u1p#?n7!>!dHVa|11D?0v!?KOS)Q@JEDR-(yghLSIYa^;sH_QIInmaNAkmc%xJlD z8_CmwJL4N@wV7l`cuvT->FWN(ofNkRL-+ptx(zerF#BzFhd$a!)6j0W10~gHNCmcTe@AS+yrZLOuQNG zFMcmoAo%UHh;?JrqMqR1JMA$5?9INfXe_ZuLi+{P` zUJkU-tQ*3LmeQ2)$!k3;!&7DJ$z0hv)vYohsnon&V+QkLuBN>{BZ-vfH`mZef&02v zbrE!1KkCLb^K~-|_P4-f6}z@cJ56s; z8+ocXlgei*`?F8isU8!hK}Z$rXY8qQ{PX(54ZpHlT2u2&I)VK+{XdS6Vw*g-LSqUv zVy6z!$vn}{`tFCEJ+SF|uTnQ`Oi&?2y^T?p7XAo%-gJwr!NKlSv%|aFd~+_Sx(TNs zTsZCG)*P%y%DPSs|AL#vk2!z}eKv9VBBi{|r|}Plp-${hd2hBf_Kzc}F9FG+PRERQ zZ?AcB9~e9$p3Tf)DB9*GVyLJZd4smg(_vj!o>rz2MGBzV)ykMv?07g#+3kI`n!PkG zMWV?m%=Nkp?vSEKML3BDI zfY+ki&I7|rzdexZzYE>fyMDX=211KpPVn$=`0<&2@Kg<#*J9PEt<{=Vmr3q~_Z~`( z?(U!DXP-uHE9Mcr%kcX2S=?MHFk1{?)sk@3hl^)YVxa9-_+#!x$b?;Q;8hY{-p>x_ zr(~sfn1?6`b;suHMAq@~@Z$PdSgadIw`R9`B z`!X~SlZ|N*rP3%8Jq{AocKQfcxDJsxZ7PMkpOEgL>TM&Zf<7 z)S~X}55-Orz4GwK!t`Kk#8A4hNx_rNyIgh#eRQpEzNevK_=Ftob=P2s2DxFbik!cR zT>(y6t@p;%iT2?H7gA_Q^`>3Q%!F(8M{^$5Rv4-~-=c!6WGwP^Bo`k{rzHm_|3H0P z@ncrP_xi~)?_>-Ei6x?_65+8ZT=!{}sE*0lufldCbQ_g}ty9B!KTUf{*YN7YGX@7y z*uNl9FaDBpk` zM%g{?98V=rnlUO=M7u}cR#VYCw?X|mzR3&IZX*BPTFMcI(nfF3&hr`!yu0j`NR&x_ z?+VtF>~S5RbX$^~B5TEv&pgvn&`-07^5wMpcAV7O<|x7&KY@X~uFT<} zcDC6gB28?8@9r`CJ*VEzd?f0?|F4X*=HUy`0z_O%&p>E4D+2biFn& z)rYayN|^=@$MZ~14qc~KpP~n?KH}AD`CpppVdvw0bZH-$ughwxayU>;i;h)qipa2x zU$T_%{)_ruB*fYVpP5Tn-ps$7?(p*QW*3)LMyc~t~esJxuT4yT^ViBRRt7I$!ywAKVd<;Js!uO zgiaR9whn8p&e|a~4 zboH5k{*b$gtQL8Lp9I7j`Tez0HaMPESX3ePBzCG1j3NHCjV(~oCr@3Su-t12vcI8- z8S1P1OqjYqUA|(mtDz1V&IqlE_llBAFn<^MrhycPMwF#Rl`;UdmtxCnHM zB*>#L#z_g(=jzl@_VI*8Blq1b5`kD}!tf0yHiK72lut3YUC@p@6L&VnFuO-fH@5Wn z4^kDWIw-&rEaSD<$!55T>d~Sitf-yk7hjR0M%>@?cc557H;SJxa@NXp<5dhN_DA_M zlH&13;K&-+CH`q6O6`WDi!Uw=;W+%I-2L!f0CsthnHLd?Q^F;>)v)zGEV>rzGryYr zOvH~Ltec04Uq#;)P1CBJwnVbzZyZ^^p!<{aPg%UqfZVghQ|E)7FWB$0H_hO^&H3)U z8hhL0?!)eIp}G=dvkZte)QazM1^i~WOKT+b_Y2F>-+ItYw*wx2)Di9toJ4g_%Wp%}CCm zF(}u!gkNmRtg>{_EH5;zO3!kW_r{FT``QjdBl~1F;`EQm%m_W-WXY+$K(H1qdVbKo z+wFZ-=}0HkuSR#<;xfE=M8;RTMPH{PVqs?nbwnLCEFqcyY}a>H5W1RLE&;r;R| zmU*SmT6h1(M?mF7iOqD0BmMnY_(W9u6KZy=^$wq>3P~)+XRG^D>6cxd_Xt@N%l@o; zt!T5V2r)lIWKh$2vS?KVzu5e6@Tsfj<)zwkW8r?&Qai!|=5TZWx&3SF0Z(ZAMZ6J0 zn7{1DhJCRjuq+WC&X?<#zJx-g5m)O$wq}N^Mw&e3@#G~-{)di>zX?M}_D734dVW#x zKAd^PM$wc+D)<~s=lB+p{eGiYy-x4oxKaB!EJh7wt0 zo1?p$V+XU>T$OF_YUjoBW=KSN1N6?n1%gh;wu+t76?TWK6E>? zR@pnW!h4ljR-MV_;J%v974*$gn?DKNp_Q+B-V_1k*Jg9}$c%&MrNPr0*A1Z8)DAnVMHoxtq==2w)DiNe9xK-%H!oWYd4G>iI=*&(}7A@z@Y7L)T+ab zAQxe24$LUc2?*>YY0BMQu`SzVcfM=De@a1_w6Wo!6Ux2}4?dL>n1`kad=_xHiZFW8 zyrNb;pdl!5@t+90{H+|}nJQI1DVI&`t*=}AQ^!7Jgq|L9m$5H$!HnADa+SS{<9fjPsHso#7$L5j+6*6C563XL+bD5uSRYx8fb z7YizylVJ*9$z0$w$FX=bS%%@u>vVnddvz?xWUmVV2M$gWEoU#n~-xsL5)Fh zedYZpl-WPu0{X%-dh#C9SUNpW9w~SIeQ8JY<6heI$n7W}1IS@r=^cE6#u;m_8F%sw zglQVRvjdOR^Z*R)Q$Q7Knln<8_>@-GG-}5B2p>LCxx1sStZllFexO#%bm(+am;Y(m z&Dnl;20^zBH7PdKQheZU5}*)<{)(sgZ+wZs5TMj%FoQaRgl$!VjyqGv&%i{fsBs31 zv^?d4u)|(BPJ&Mc%^-!XjCNA@Ne{`of3$F27Ne4dB8K=QRjPD1%~)X{#NpS4LiL6v znvJomr*84+-EIzl$S)NJXS4FUEb^IGV>6B1DeR68OD!lsgHp9N*2>EoCs_VV5#Zbh z4&FWpKl4NP-0c$%t-V0U{F_^>$=_4FC+`78zXL300uTMqT!X%XWGN&h1OHZJ{cSi8 zWP$Rif9*345zO+B?yun-Y5D&>%L^ikz}w+r-q6p}9!o~}#%P2mKGOT2_m5Qj0+J<* z89N+rS%g33$~rJ?PBAeFRhaJn&sYIPO{kh{Waor3q-1>|?}MJT0;JvV)X`2~|2Jl8 z$$pY6WBoQr&k}>|glktk{y(KZmW6}aN{U#8eG@lUjgSf3?d?LYp3_!guD%aH)o`1u}4tbQEJ4*aiBs*g`E;8l*& z?k3xHTNfJV<#~Y6C0Q(eeOpAsp7sxY(X9XS)}YV7+3byQEf?DVOmw7c6i=AztEo>; zNGoXkXwezhUJ9T3$Xrl6Fm)pE{#*m)W*cN?)|Q~g z2m*Z=@9?Mp$Do)tb~lPQH*I?p7uSy&6vJ$lcl`4>@2GbTcUB`#WoYf$3u@`#d`xpX z`LZ#bHnG{Rd+5{|5=Nom?}Sj~<5fS-Z1F5p0{1OF2+-)fVZoouPm75>&uV$8YXGpY z<{AB4+euklm?j1HTjfdX8xL;$zwDuvzwiJ?ZPzY7=k3nS=&F;nOY>rt9L=d|wJ3Fz z*?IkRvcQmek4)YMdazVei}+Q>0tIh1~gMLDWQcV2w+@_CL!(D+o+t zQBf%MY_?|79^8`toxbMvMjpXIdDeT#R5{y3r3r~%!S@$(b}w#sSxdI{6$)+=eIuEN z*np1?S8vLwVgrdT9$-t~h#J}N= z`l=cS|6G8%1w5U8jJ7Wb$RurG{3q3LZTkx+MrdLRg!|?X&UU?8av~ z`59yIyKoXVPWABbsKe;=(vH?fA0)bcg+5}jo#U}yzL^hZPk1)9Btjx zHG4=FLPicqMrR>}H77^m`o!+=XPqq&B^y^7+hKkM*@=m_JtAYWx&^YCsW9@5#V7#4 zMkn*25w7j!L3l#$kBmqtct_k_NpiwDP9{8=B#b?ReBM&}hoX2u@2^I!nb!hsv(I>W zZ_CPR@AMKeZgtoJ0{HRfOaoCnI)(W%oIc(As4Pcscc!=c-&=M; z6@q80t3+YmpqLOr*9{S%N*6dI92OP^R1*bY(U;u>7XPbhT|Hm>YDRUeAdor2VZ$X;?R1Q@MT zc4fBi@~*hKi5ysyNV`3}#218bmrlCvMQ1>nT==kA9l{=ZE{#s3(fZ&0km@CUI6y7x z-|2N5+P@*E6>UoPV?>W|@M)C=2gPqSH)3S&gBnZ|Zix*1o%Yi|`RA0;g)yf}910o)YhZNOUzlq{zH_j_FT)6xX3Q8Bm zise(GDF3zAbDaQ!lDDp&bSm@L-sO}}X_`?lm=6}0kXqdaNgWHHzFzb{iAkK}%^}^4 zox0gRUlw;>dk@QTS{zLNo%!7`ZeI?v}vdG70W3L4v#6-~;!N-~am_Zr#T_4^vc8bLyPa-Me?M zz1G?UF7793=ePz=d6&{mtcrIqPBP)_EzRm(zXXSu@Nnm1$*pK`jxxtCL_xv9VR)h) zD6d~*F&Js@v%Lj2njg+(ldX+WXNYOeM3HX-%y7@8x_>X~0opa*@&&c!q7QyP#aaio zFjyvo$d~<4?2VOc&JQ9JK4SgmOSwa6+e}M~whU!c5`h4ZOjpD)rnR~Z zYy$mqyPW=kR9dZQmBE&99=*LDe1_ejdabToDk;B{m{3?UQcF8Ve^_b7SuiKCerw{Z zf5pqYwZtE%_Uqxq?@0&ZE7Zeo_#s5_>EQzSmCsI=TemZ70bJACE^f4Bk}GsN&IeDk zeW_NSh|VDbTJuMToAHCE28%B>%~1-(ZunShejVXq9t$78-h3h-U}S_@v;uz_0wP8s zuPeMM50jZ=5;g^q>uw{O_0i>`EGhmiIs99@+E$(B{6unK*!SehBnq0{N)Kp=Wy0O5Eu{&eyz6IU|JkFJ4#aR3yDwHW&TLwZ>JDn-v-+DE%|7 zJ_1_F6B!-x$wEve8%h{k6*7%w3q$B9RYrGg5pu00GHyp|39lLm?naBtl?YUJC<;jMG;miTdF}vYO^H;>-E9Y(8I( zA@mSRxLFRo#t0iP`cWk~dgo{AMC{Xf{@hy{NuS~`qomNdmw(?Qx=6iC_3lQC8eqOF zZT4Nznzvo4e&p)!6Alc)>G?Hcm2y)kO>@`wrw!a%L0xkD{?}M@WCB7-pP<3ll>q@m z4x@m80DzSqa#Orz*Hytvym@7cw3DKfTxL*zGuO_Degb-|GhjOw_T703tXLp7_$3hH z3zdo?AF<#-IR99WqV0|zBAsMNRfLO=FX<<^wMHE_e@F27SdA$aLMtO9(`MnEzi^BN z57&?WOj)PL0)}>bXNQoGaA$kF!C|BKFW~z^#7{*d>9I!xC3|UWyU&ESyYsD7RT3uS zLOc%jG&e8px&PxWHs-FnyuP8GsSoH128vFf9rca}ko5wc<<6b7%{ZZ9$Y;VKcAZ2A+q$Y{AS&S*Ubv2M)Jc0ot!BjDKWB#&~?_z6<8DV)cZ z0|$08XV9kHNi}4|sP$p-vcat$GGwOGSxT+R5qCL(!!Cj)3$bv#T!-RV|vi z#_oL)e?sz%RyNX2J)$ehVQ$AzS2ti;QKFc&zp+7Gj4z3SyNVk#P(Xx*6`Pt$@APIz zE%4lfH=Tk(Ji@-%SKC8(^8_OSNyQ-5DYS>WV(eYphXoY=jyfOJ%viRN#kDHQO|Jm2guxR)2d@uY7Y0mjpnQN77EQgl+1VdlyDzmT zQ3A;BsL-;%7H7&;=m+;?1n!fI$}xXU{8=E zHW)cL7w?4%Ev%3;bD*!&TaG`x;Dtg4PZ52;vDqYc3isyWTl)ngxkdv>9Dwe;1Gc0^ zw<#~b9CI_j@MR?t*hiZJTq(b3>;3;A~Ho93%9eY$us;da33#y4o;$yz{q-BevY#_pW5RGW2jrm zqQpVGbn>HrKQW_LbtOwx{>(&RA_J1}_&N~!mGp(l5#fG4$BNU}sa!I!<}U%aH!s{$ z3~4DiB#eM~X!xqx@KV?bMSXW6tEQ^x^wb`u{5C$N+IbHp6$fwlaIrB4hfBukc9yhg zBqyjOo)pdg%P&OwkaLC*#*)))rV@GCilH|GevfURw5bm0IDO5RnmF{1ofh(44rT{B zy>D5KOfme+WE@HAsw8bnnw+FglEzy-4}5PY*9}hnlve7mMx5}H&3W10AIquz=#DBu zFX1|pvJ^b>CJKop7fQ#Kx7{PLYZIfyqMaD^ZdmWEir%d8<3!XNgwzV;K)yxjVhN zMmvLMGx@9U104Cy&XIsM5S>+V^Fu}A%r-WPHoq8BbVeVUStdWA=vCY-V$(01ZgZtI zFg;6+*yx_q%?wWyHWC*Iam6mNU^|;vG>?B_y&!dqswmL6US=NGLtXC+^KACJnWLEch~8!88|cFXv`gRBGn%#j0doEz;PkGcsyYeym(W=xv+8-y z++0}{H+-KAeu?^*@tk~kVn(gFQkapJIUlFsBQ`g~yshXqR2HnFw(V_aXF=buSzX9v z1>sawV$A87)lBtZ4vTFBS@A za9c=sxW;A@e#7bMOcgbk7D=K;r<6Kg1R#4l4*-+*HpUs@8`KyURNdcvK9E2c!7Nu= zc31SKO0v)q15qUo$3iLMM5@jg&vav$Q2YWn%D;T7cZ){JUD<`>xJTzJvnl2=k(C z$XjPd(|V8X8}H;SjNAPBlBpmqUF$Z-KvN&w)w?C!q8=~?x4tc>4Pw^5;Aj7F3k%Ng zIaiQRCcVJQ!bg1h(w_DyDL0cDb{re<-At6P4}`7`kwNNygxyp4Jvab(L~J9lqH;4o zxLB@r1_K)wMM0ill7zUp?NSq^soD4Z)1<)&H7+*SQgdQ?W2N`c}}eL zS)ApqA;j}zVb{dn2RWRjUBO+JIRW+60mJ)Az;FXzN|!+g$t?U+$S-cq*TkVa8FrSq znAZ2M(2H)|{W;duydm8Wm_0#Pn^ln@qJMTvhL1k?SAd=;Enix3v4gnyz^>&7frj~w zX_oNkB*HCEz)h{7lWWNM=3Cg-LP~|h2(8vWEj=@bL7a?xEH%iBuep^#uEY0f1Y`6> zNQZJuzr`QX#ZUx~f;@ zS4q9t1QGSsqBu(CRFu@W&?tt~xL*nZgGQMr)5rFito|!{)-PHzsy5t1{&l>ujG#*J z!6u&vRbQcUx&Q{f|4uxud^C0eVih-=+@vgp*CA1m(RO^GYYJT z`zS@Z1Xhv=SUiEYpeopIk3Ed}sQFdGM*Q9hM%K}#bPVAR>79iG14sZSIeAUhUhvNz z3l+f9Ey3(xe!+RnEar+K+_}L3o!-WPyb9j2UL#q_qD#qJVIn%{Pw?tp*+o6jvb z$=`_02{46zd|ILSiX-`nGc)(QLtvUT_4F5h>ovcpdkU6R5?%`i)jNkTNli}Is>emw z*G|599z=aqmhAF5bBoDQ0$y%QsQ3)gpf@g2JK;UG(IjlG>bsxq1C`k&{K)wgTGL(m zXm(3TL&aKs04BO4mrJp{D{T%Orul_mAsFO?M|mJEjT>x&JB|KsS|*9U&d=nkZwc>F4&E!o(84G2pRwA}*fBL_^)W{47GeR6hE$1|H5B z0pQK5xBR83jyR&m@Fb(gw516kN&J19>3=+r*a1@_%G0UW zt^d?phq&+p)Lj!QSSO9%U>kO=y5X^9ZtWfKrZc6FQeU0)Q}$5SMx_AyPG7aH&84TT zRQt&CxaLe>s0q{+bN;gtkwn&lUG$xZ&;I}zzI4qK_i78ZI3i`KrxF8|gE$A*+K|++=nJbb<73t>ARMMs${wbHBDk7kKi<;{-~q^T zghX92|9Btf>FVUv)Kp|oPsTD=KY5FJuAg&HP#R#JBJ%W5r&XhGxbS5Vk6WJ*H!UyJ z+obY3LJdsvus^-ll1U&Fyv-z-5qQaxB!$>e@H93b-#t{i=g#)`GkleBQSAqBQEj0u zts?1O=2~v&!5$1yw5>ufXZY@%k^b^HF-KXR;9ugZ>KC>BS01S}{|9UO!9pdz_eX^c z8w;1(@ld(&xmjPK(T8^uTI^cC72 znT~)XCdli94|v`1R#npcyxnZj?@X2^qN|yLRPrOOLm6R*qNxW$WWsqmQc5x>bWd@pY&@la}*T z6S1AEeZwCEQThwu?A6Xyf^mn+$?b7&yl96%fS}kz+TmV=leM_Z1{^=%p6GADD+HPyG;7$K0Hzg~%h*6I-<>ntzvK1^a3Q$Geik|-KLvIc9PiIm_yKq)02DxA zSBpRjUZvX{ziwt{XD5KKnW|F$1y1&Vr}?`%g~b3Gl=p z9${%|Nl-`#Fc+P#&SJH07^qrDJVOh-<9WfoaJ9Q!@U7Ma!_0ERyA^|KL#t;fgK*K4 zPcxcYc>%FwmytkusA7!a&APJ>z%@R4QBOzpC-Wx!`;D+`(N(f!0TgR6nGB7wi;&rT zbMD=nQiI_ySB{eO>Nh>s6`ExTFUgh7OF7M=*N?0Jz)YxsTZB{oGk<^AmA|a*C(0!R zd-T)!Nc{G_$J3p}yn%9K^%?3iN=u|0$#!8?UO|J#bbFwR&)>EmyN`u*ElEUM`3UKMs6p{T)1KLZ9BN z;0B0jl7P=EscAM)(=@?KV(ho`s-e|E{2~JpXnnBY_*-^mj|&Bx?+WVf_pWzBDt?n8 z=){G?*({UKH+}lLACwZ{w#7+9I%7tJL9UjD`hoJLf=0-QYZAC`@ri9`uD9a4TL zGN|c$pY?S9F@OtJ?{FddbTG&C4asb${S^SP^at{;Fu$!~f4-!@U_w3XHv>=J3Q)sK z8sXP5*A6u|)QZhzQ0{^t|`hw{D6kNr1rHNLHiQR?^Ply3+cN_5sgvCz2>9Q#7+hc`cssthSz zxTw^v6LO)a4q(zUheD4_Oo+oRFlmE&J(0qfBIyWAz29n;bqDht2&u`L7}H9hZ=m^3 z@%ZY{@J#m1%>_rORNQ%#(X5LT(g(P+Q(kZFe?z81jIB9;`u(qj=U@3;kW}xav57`^ zbtN)x%YMse9fp%D1+|1bA(Q*DC3m@v zye2~{z~U4=dZBzz-DWgXG#%d&-jUO369qz;+r}@cLUdFE&}=wCV+?XZde!HVgKK6$ zk|JUjus=TS$=HAiGGtY$yt4cvqIs*j!j^lZuyUHm`E$r#vi%k!MNvKUh-lL}jv6~b zmOJ?!ZCq8h_Xd$=v9}yO!kme-S|O{O{@W5D+=3Gz6Lde#e*a%(RP{QnzPo zor6X2I-?`NAc8L`M^c$e${9oBu8f~x#m#=zyY>@N7$0q2LX4R+)<;q$R|l)g;)i0J z%@j6YrdcD}G?M?7IZs0w;q+elPUX~45EckWvHNsQGeRz`u%)F2bNx*H({al$7uiRY zj7@rLqUFv#$PZUl1&k;m3#v2Hu7WT(*xTU@{!?GckK*L z=Kh6G|9c?+5Utr3w0PTPAvYL?&+)QSYkK_P9*LWp-Ae1&UPR+@Sbjs(jp9tUurh_H zatOY;9Z{iZyPJ9KNh9d-595IMwFsYlqWX%V`Mu4Hfw(Z6CdHKq)NWK%(^p(cE`xKp z!vM!_!Yy6%h>!OtIqv@oY!$Y+b#E1o|I<4?zksW|F6P>sI|;aCD70Zj# z{eQhXa&v#WC5oxoQN#^~-~Utr<}OhrC)^L*{|)pqP``wG+X!d@eLDX*5!P?0REv9Bbb#7gw)D?L zde3g5=b#w=SNPR`R!yFkE@s$!J#G&fcILV*p65V7ChEL%jSr0B%-??4XytF%h5wT$ zy1N5U47aLs?Q_%973S6*6wqqrpg8Ty(u>S+Cc8h3PIyv6gf8^F+WbQ>pIuDIEmRzM z-N;&5Xq|l{n?BaLk~VYOP0_>^;rd;T-uc;vA~U^$EW5zUiwwn8vh8*l=#+8Tx)r@n`Vb^1qlE@w>`ieGGnNYJavd(c803A6Q+zyghDg_ zu1){Yy(3#gPoaoFqABTLp!R=%;+YW8p**7o3B7Cd+qp5W_N$_Y>jp@jvM|&#$ zFIOqM^1b7(Qv1(@@)3*#MwCFaPQE`6-27CUL`^-J?Kxm*ouR< zVe@jvZt@*WpzhyQ-alRLZM~bA&z@X$@tfh4Uq6pt?%S3^Y~1F(6*UGbli>?k%`u7pj25l|O|hrRUD z6N&zmi4PcJT=3|D>hIJ05&b*=L(@l#WMG=5Y!#oa9Zc z3wmd9GEj!R9Ln(G?AvT0*;0%AxE8`}t>64H?LlpsH+dwPDPKF&NZ2vJGI=D{NueQ{ zH^@u{p$T+nQLD2Fa%UKgOB(6~Cnz{xZnj(wsfyd;QBdo*>Qdw9%BM??Y{;{AIhnxE zyNlzi$|%|s&6793C1h_ri^0e9R9P*@#4dBZh$;#i<`Qvj`{^jT%@Q}H3J=O`%%b&R z#ouUFkDOej;(d9w`^zdXN0-C~^#tp2oSCswq_qhh4;E6?Mf%)LTCub2S4I9blJ z@ou$GM>!xFOWT$X3O~bMs}pCUaYZdbP6Bi6OKbK+PixyK+n7>iILZ^;7`R2VV=pIa zwAM`{ko7WX%GcC8vF{SZk2a6;2LYHMRmZTyiyHV@X^{``Z#LNMHwWhipEYjV7#*B?Nx#a`w*0Tx`(0x~i`XSK1<;AaCU@KXc&F zX>@$4;FhwCHp0$O{V7nMi9_ipHc!4yKK|gkxVfR(k!tsiQ$|aTuBzn+@9$0tdpPCO ze{=E6+GC(?y5KH%|CJot6z3j?vWmOJ+q>F> zKiyi(Glwly%H?ilRz{- z0_SX@|1ls4Y2)AZxhSYKh%H}-Sp+5*`G_MEQE_^RS{RvhEVXn0ommZePxFDpoZ#p~ z1WTT&jukR~tQle!d!3*D%IsW@YtM;Jj6HmH)F?&5hM}XY!&mVg{471)c+2fCk*6xT zm=t1`_%^~jrwnvi0$%Z>TpVBb+Bik1oKep9z`&&Crnp#rAEd~$+#X+I8V* zCc(~=jzo_|1*8jWJmD%8rEiXSeP_RkfSJc;3fe#3Cof9r{N`UE4Iox(jm8=4cq6(4 zN3%Pf-(EPPzMEKp8-wF)?#`09y)dlJOg;7-RF>w({H*_*)^Kpjao=b7d&kfL0L zpJ;YgYyp#ECXfKl<*T4fo+sw0W^tffhr<2(Tp{FfWRr#uP8sgsG=qzSA>wm;j9V$^ zIjh7C=7*bb8pKxHGDMazDTX6$7PhI$T6EybQDEHE2dg_z$j@~fic6%F;Jvd?XAL%a zC*QNnm?rF;G>k~hZ6ldSNK=Z#*b7y=>E>vboMmhb)$E>Ica&6rU!SA(QKwt$!P7uq z?Hj51{TdLu7Z9A&Mzy1p$v>X{$`UDiF<4TXqO5E-Hgw+#Cga3z^!@4w#H1F|~qmwF@{OI@FijFva6{ zB6~Irqeq#DdM0X}m-uZQUp5QT!ZcN9EbBiGPI{R5el%ygan2W#*kah+a91KQ0+yeB zAm~#7)r^D%qYT}?lZ}x30sY9(IU5-bsQ$$H_)x~G=Q1kqZQ9OxAwT~@j<`ueHm7*T zFOK(F;SU+&+(TM=<6qaE;`4pXYjr zBD(pf(LfQORRs>LN!f0h%ASOL^htQdMmA(3fT_h3ifnZCAXYGQRzq2p=!#87y}?^1h2P^UP|iVRKPJV-?cH?? z;EfWgMg0|YWC+H;Yn>&SSV6()AMqM2J)>3O4X#WaYexfCW22fl2EmV6HB%*`1hr0J z2$V_qzM>z&f0%E=?Oq>^+rZc@Skd>2PFcP<5`?4{+hPNR3+?1n(KicT>iWj>#^*>; zUY4aAm-mupL!-M2ms)y{L~}Xz+)}Mf8w1b8@==4zvzT3v9%zTE~{|Mu%2zHBQYE z(TEqX68OfAnas5=zHT|dyEIeg2M_}aEbGuK{&sFBK^=%*X_^l~5#yep6={`T=WZ}guvuaf?DMGoVdCv_rt%2ko0Pw;K^hyXUU^|~Q|l+q(F z>=nJDUwahpG(ct4(ki^CEt`noFOI19>a_H8)6-IVPzA%Dq57s;{h-ix<+5&j!trU} z8IvDr%dcC+-+;p-8EIwo^2>hhDnZ#>9(oqq!`aCKV*$2S)<-_p^$L0Mxh&`Ukp$-+ zr3CMtSYdCYD3!L44(XYa{LgzZ#~Y=|nQ|csoKl={E@K*6ts|}!!&iePP*V_p{Y7&F z`F5i>5hl}W&tAeFKEI8yO;(%L;MaGJA=LUU1SQ~ssco>jg4@|Pk7Z0hcR@`i>si%b z*?J@vfzn1#fBNY3x^CxE)T+=x-e&y7?JijoN5-|+A|8svz$$D~{!FuBBNY-hJEXx8 zu4+uL2>+h#6SuNZrVL+#rm^EU5E}{0nl|{>TXuXR4=iJDbJlb^QAi5Y7P{Yof+=<5 zX{(~7@-9M##ygT?$k{(|$bZ1TtR%xm;Va`HU7esEpO`rLAVu;iB$BWYvnu%d9huvL z@hJGJ^5svx$mq9YTiJwGyFn#4nJ5t!2!qp4to9ooeOJ@uxvL|Euhe8fs9E4yMK3Fp z$}BDQy4#w4NK1-xdae;8>cu-4*9t$l(E`&6BW1d7$6dyG+C2*B&XOU;U+Wis+J$f!qHokrgQs|xOjsVvxcA6UjEo#YOaiOYIfWj6xw-ZNJaOtZ{@*qE4VJ@B+O#uA(xnC zzjWiZ@3N=KAaQ{(_fEWIZ@ce#i~hX63!bo`|$TvkPxkgO$Tm-`NS`Kc}D5 zPdD7c+E;|J7rZAU3n@)pthcjAtph1`fd1#{PmvDOlK`9PA{cO{2D;FY3VCwr)tL+t zU}DDX?nd5!f(usks+rtgDSYF1jNvD#`7kd)HMH83yafQam}zVxq=rYc&H>`T%Z~{E z;_IXCcy|??p>p!mhSL51o-awdd|Jl7yC(}k>GX%Se@y&;Pb%~2br@Yncd;ICku)ck zYYd*PCuQJWIc?`f@bZ7v0ccFnE|i5vJBaZ>{4Y@`W8d%JYkxR6-g@j!7G-A}o-%H| zKi1CWOx!NP{EkjCTW{6-Mp0HNTQJRZKpyl_N4+PZ7L?s%0W$8#8j>i6KRoknJwI`E z$zu`(*!OC)x~nIBpmyNoH91bR+N#GChU_n{7~K&arVhbS-X!>fS4r@Yho#*!W|%4L zFx=}zgZL5hy8yMv;{{yox7yP$fldjoG*-NA4))h05?*{I2k*xmJD2LM<{E5ze6;#s zp#1GYKz_v!{nB~H#6TgH6Fb3Orrpr}E~jsnyoH@oybYF$=V1T(;MTp$v0-^vO64vH z8F#jZw_P`P5a=CrH^RHp)Y=+DUM2kU6MS1tj1u6mNZ(l^NiR0!x;q}gbMfb=5ji>e zHzdq#ZaE*mpXoXRuXrZ}LL!#sz79r27?kP~UYF;T9}CxJ2R%fQk| z0=uqkQ@5p&Ar>TVA?O>!nUWR#Md^8OTQibrRnx)X%L}b(ctn$M5}yihkmsFGK_Ee< zu+PG}6)OFYk{qCVOG&v-riVA|#0xKZLpq@L<>cfLUz1G-s;^Nuw%uu}osS z{d_CmVN(t8FU}_r+6$y-*tjfL^nIFuliupn<=zzDj=&rH;W6Vm2~p8sKvA0>o0wSE zV$>KJ8QG{e5*w97N(OND|0pRr0RpNTiw!n%LyqY?F9dVfqcPeS5}zKc_WhzO>P`+? zF2in0N0;Dd4D7Tz)E(L$0k|7(bs|3a0Ma^OR&yNHmNzog#*yr7s8lv~O?9iTR%@o|Zr@=-@BP7IeyKsaD zwc3;=Pzxc$V@r2gD{m=6U_-sC}1HMEp1MgkswEzRZU0&GjodbvEYhh~I(VlxmuRF9WI@;DI zZUWqHI}mS86E}4;;TKc+HaUC_HB+g|NCnrjs+ptfz($ASZDSOSd?o0b-M^%)v*)4iMtYfw+u`IZ4FF^&MdPCl;8`!7r z+^nXU;bXTP@U)KT^r<^+3SaR4L^$s4amj6;>h|IfHj-TK&&c^QAQaV0YCNE$2JP>Z zzb0J$=Q#!^iZLjjB4*j0e}CVpR`8@GZ*~n0!TZ%}9|MoLLm3x-9pQRO^fB_VR&=jK zm>P>V2di{NNl+RHNhwsGNX}H6eT*Q#pu6Ru73p0svlN4&_;QcRdPvAV)mUp>=?=vf%D&M{Sq6@`t5QktZY%Mc%|uG zaoz2m1mt*`m1yEo-GGpAS6?Qal2jZx~(ywl&w9#0{!#?9zTuGas zD6o4~{_?&Nbu4s$C3f$;hK*DVtc7ZMhrVhTy%n1cVQAVb;1IW!6%Ghk{(W*2fN8t_ zc~HBsmgUCAfF|C6=vU4)RYKh<&d08<7nW}RCr$F5H=#*C!`+lg2%+uBQos|_dO;KH zgfhu1(Pd6y;)!fDZR}~@W|IaQejbUZI3}@zG=>vfm>=)1I-raCrCOg2|t+&x( zX`>P5vp%`lk-j;gz&*|A5c3)Ova=y(nEdP@rKvq7C`z$|iK><(tV|XCQ38y|MiE$T+JgO zCudc?D$}kr1)9JB-hIGFw0Zy-^5BLse7CA($GiTSzZv{=Bmj477SLOhYd_P)0oXl| z0ibhXNRmZpRXbci$eW!vMa?|zmOGH?V- zZ97+f68l#~1pX(Qms~$b@vlhm?2z>0|J$DY{}>^{`6K>!1J9TJ|G~HLpAGx-wSWu% zH#Bg!jQ{rqvCSPk!ah zbSPRFMJB>I}F`m5gJE{qGTf4y}R$ zYwt9+R5K@c?1~J1;*jB#`S7n|pI>`y%;}dGg89eG?Ot1VBXSI7iQA8XUKV>ZLj*g` z9$(Hk2LO$gVLp5AEV+QIy!@{&79oZCyD&%J6vvbAI2X;1Pu2a;Rat*&v}dN4GtjXD z-~h}rRe@btG^;ib*!TCSrvgJrh1uEP8Xdie_|0*9m&DdS2aRjqr}yb?j_QRMq%hwJ zyUbMfH$clkeOLDhJD=)kB~5_sjl!f-GiyR~;x=i==fX^&*G#2E2*z_O60Vww zLZ05>u$~H6cfKB0bw%ki^KC^2OdD^n{sqyH-K&?JYLV{Vd{j^%}Q_c)$cM0a7NSPS$ykL50?G zS*hcl<$mJs5CzQTY%|F#(V)P6p}S#5PY!>>kY{}BwVwCEJ?bUB?nJRMuAZdtPbgE;Nz znyc&w3|wMv4+B|Mo=kO!@}DteT(hxke1`YBlom7PNsor3>#!nxCe3j~0y|=SppOXx z&2GL&XAts&H+KSYuZ4RmOh~fXRkiTMvT@_sa;|Jnk>0CA4VOR2PL5MHLidQg35dgl zN$#vPn`XZYlj-_xZbhS7Umynxtx6Ij2e)ab|%4!}Y zx7EVoZ z@;WQ_7x9iR>hXj9S@IU6jZHIjO2ng52#iwjj4`qKvZXwOc{AAQ6(S#MWz_w(WYcsb zeFwq(UDWH~jxixUXI$&`2&1AY1@27*p84^HQ_ z(?W*`aMCx)#F6i0Oqv)kvXuBjhK8>CU-grA^5bxaJsu@Iai$l@x{GO(OxR)bM)qIz zlTtN?60GU5n7(<$I2CDaQ#q$-0@YilaSed_^@MKR<2pk2E^h5o_;!NJ%=kl09Y%90 z66ou;`sf%EX!mvkORaRjb2dkkZ>ibn`s3Vz>GO1IbWvwyEI@=N=jN~VseR}ZfSqge zS?OM?dges8L0W%BK$;X6N z1;c<7j5qusUjl2oPQ7K1g*(tCSW5)Qjo?(t(YR=02}#t9S<+{rT@=b2I_Zs}2KNi1 zX*^jdbN9#JzD=yoT{VA2fP#WT|D&tpMA@i>Z)aE6-qN=GfN?)&52d3hI*TMkb2}jC zomKL;u+lTIzJ4Y#wI2mAKj2Xoqmzqx)L?>-p$ifj^3WlPU$>rMD#l+ao!mQzBoU`Pl7n zLC=D^I}u$(J=B^$iXK-%zhqr?965{A+Anfd66lzJFZ+`yR!5ue4v$^C99#-c3PTS7zQ=G%v;P<;=e4@%qxwYnLBLf-`qP- z97x!BI-yFL_j!o!2_c(3Y!`F#d-FLMFd7k*EcU3@W%B_|vr^uZjq0gDdcrvGZ}}kX zQ0w~hXkA+43k_BvP_dDh4&_RI6X32WoD9LC8Y1_d;ys*i%@A3ef`Kc`*7l7*|M-AS zDz!%gibq2&F#OszZG4LJb2_`7q}XDJhyX7q(vYO&4ya`|n^s(9G)yS_6HJr`g^Z#u z+q&qo=SojUss!)Le1D$m)b!WvC1(|qX;+&$f@R5*q_9WgHtBWY*NMbB3 zQDjSqAjfMmLJ!;w9i}N=O5R29n!!31KY0c~kY&~rj$bR2C}ZJ5WJIvYFmM;Pl!Mo+ zBhWG2TSM}cuaZz|dfWQNMwuil8Gr+@a81q42J4lFl}ebZf_;^?vJ<^hHR;~BI`4Hp z?N6-N!kzFgh%9MKwMM7+-AD0g9#fU)U}Snor#A0OG>++ai0o7iCfXX*yz4p5Fq~oi zra@g{agnrrtVr5bv9~}&AGyP&O>o$Y46^Kt!&5v^IM|RC@rY9y(~7kK0bLh^G#l^T zdo{qr26flP)-uX{j&?a%e*sRA2iuU|tFcB1^q{bC*JH=^dp68?6@RqwrGhH$NdAjV z3mw3^b|iz_X4$0a&e{5Ov4hpJSc`;vJBJ9jgdVzRpCe{Z!p2DYULxx1enu&pG>?^= z6Y#Du-Q`vW4l9Yb`o^`YkPOztm^bsmnjC?(&7v28+XS`IjPF$6T}$YP3>We+Bo_+R zzUrm0mRL{bqSun078VwnY0gY8%a3w>)#WFg{WvWj3Q?sm%*n$H$|{A3ku79EOo7*ykWhuRgea zKBj>AxKll)W{{d+jDwEl7e16LTmSf4q9Vj|Jc*0aT!yZmebvy*W_a| zjdx;CP(ZyRy!ehj(!sX{d0(GPb1~r&;{vy;|3lIi>$jBxxe-{E^#d|0TeyJZg2Qrot-7fEB1@Bk4-G%3dem)wbRdBC zd_P8TNyZ_3uZE1F`t3f98vH}p@aDuJJmNaSwCe@n$02mssE#S9{;1dzmt4)v?+JsD z03x43nUIgIjqjVT29^?=F{)Tj+Yn6rPO(uo7%NR8YMqyRD=}c@$EMF&0=Yn(u zh4cmcC4P&L1T6^Z%|BFsl6zuNjlT+WcAdCoIQjHG{E}WxYBe5B(jBi6ZJD!3IQ%5y?L0AbfguS1@ZK;MRd zsN{^!L_a4Mq0dn`L~k~qweb}|_-S=)s!n>TK763z4&4Hr-=j7jQxMtmVzg58wAJ*_#J*H6?($%=LsN3mH$+X`}(k{LiDyd+Dt*MNs#8 zOR~tz5ddp7r;=KVMPyV)kNFK2`pJ7N1t1S5ZV+2n9B4+0 zJComz<-{12^X>M%^DXul79$%9%jXZD^}4FvyJ$N)NWx=4R~r02Asr2vhV*+$rgPaE z-&3RPTmR8NZ{Ndrdo;UjY4XFs_p|dXa#@Ug+?-#X89V*q&nbVz5vs@Uq+wmF`sCqq z_zSIz4*{T)E*a(0$Tz8542RvpO3UrqjaIB?y=Zs-B>vnFUBjhLz$aX>Lmy*EWuM2t z+1xL2ptd^!iWAc{CqU~FWp}`8HJpmZ2Vp@#7MS! z8jqR$u@vgkJ*rgNEgH_TqWHwI6mD<$!XyoqbVvHUnvsX&w90U0jm$CwLXA-;eldPp zCMqtOI(LT!l&U6KeY-Ok#yn2-RJOA<%F7kQtxn&#?>RcLWHfwj`?hw2UMmI;)!fz6qI#m;M7YipZe?ih{5UM=B&X^4cAJH?5UpPtK(a~ zuW?xzU+XDmaxonmt>5DlMz?#(BZAM6u%GxB<>QmmyCaF?g8{sdtvf9o zPX1icXn}qJ>W1my=%;!%=uAy!&RXfW20d1V-1NSL>z9_iqPUs6lE&k5-I9tkDv9bf z|9=>J%do1tsB0Lcl$4h4l#uT3?v$49l928Okq+tZMx;YPLOP_ok?wjIdcV*2{CIzS z`-hhooWnVLt-aQobIdWvpdjbgYe@}aLRj1N^~Jg0@ryXBTU41UM8D2>8hUNh-&u#< zs{4gZ#FSdP=ft?$Ab8^pjuW*p>!!co7<;+;R0$@HSzYcTdPmmcA{$mFBJBX))A7P- zWqEe?=@GbmU=#W5jINSN95fG`#Qcg$IMS@e7aLAG%6s(C{cZopb|Js(d^pRwSOOcA zZ$Cjg_J-Lbv_j;UQg8B2p-{-)U&T3kewCaL$suktliB&6X#~qv5=8nse1KCu#pI2z z9JM@)yAvc#AO`s;U0moO{*?H|8K-5q=7!4or&|&MRTdbMJ#7MfbDFc}N)U=RZZ^F` zq0C7RfrB3V?r0wm$>q2aif<`en)o$pZ6&anfMkjyAu1{Y1iO?zKA8+5Ffo zlp#GU#iiXhUKes&T4}5T5m~s@vsWSYbs?MT^KELj(JnAVuEJmo+3&+WOU`sfm0IIRa?~HEr&(o4(q0Tb{*T z?9Fu<_51PTMBI;;Hf3$5!e(UciUDKo>N!QzybszhIXHDZXlS-aDbM_m+Mn8n;(7cU zlGOWBPKPmCZtmM&i64%fXG$VB-Hq@tO59Csq@>gfJY3}PoBev90K(&+arBn+7i!YK zkFU1tH?)80NhKW7jZP$G#{emuew2S;oYwi>`t;FX3#xa*r81%-z%Id9Rc7 zZ*m|Imc3~GmSy3~P7Y}Ob_NgM6m5Kc|LW}1#**>GL>>c8Fu6lv$!Jf7mLr4bz5|*$ zjihl)MN*0D7{y7Wp9s{CGka3%Nj9<+x^>c%2|r)#4Mb{uCCL?T86jTIvtq;@jd*r% z*$mVT<0y$|P_MNP0|nqG@^FstYP4$i)u)%pQ9nO82azk!v9anmb%}%#Ni^yM=TSUX zb7D%ug;b3MkwiT=C9S)e&b1FO|L7(p)lOS|jtNG@5mu5@&z0!s!=RnuvO{ynvIl?) z5)u+yOeqaA3L@fAXKe&6zJRyq>TQ6+lQsb;8GFFRyI8BL#^cYKg0ivs+_nT_z?Lj! zxx@AT_nUo0_}H!`ZwTtinwzLs7nX~qIwz0g$K!ThtHPN-_6OqW6E59an~fSC?h6DM z_0+S%6*J}RPrN&0zLe4w7opIF5__8-Z6U1nu)rjC-K9VxtbzR&g`QX6Kv=cLOsXn# z6#=7QPcI$^-X&))$?(SjW zg3xQtMtWz0u<&_XTI_*%3Qll&qe=!}tr9JfF zuv`rS%If_Rzwu2*bJvHRenvCFF$Y_-mHIRq9yUkJB%n?}Xo>n&ztJ`LTnqd};mbS; zP3(>HYqoh}8{%fH2G#>azprFd3lnu1-#t=oV1x(KFmJFDq{`a8#1?Gg7$D~6PW9Jg z%6?FxRz_h|P*Aun-#Gt5A#@*+~Mv%6NR1l(h5V+(!@@%z5@B z)OCj-;*e50hCcTDKjfe+GK^nUCHH^d>+Hi8)kO!G5Uusii4>DzflaUMLm2?m%t&xkY`Qlqn=Y z8MY82e0hrs7~Z2m02H7kq~Vpo6EzxC%f`qYTwSqQI*7^1dqC-PO@Rq%dTf`~bSRlj zB06-pWeW6H0z=EUo=%ky(q0nf}Qmtx7~ZY82A$@i*B0?p{k&jy{oT~N=%{5j%R z2H*bbs#2jmK?dMVfMO)8D=DXW05bKH)oii$LzKyHb}tsUMEB@d6_(&IB_Sapm9au$OFs6mfoviD zMV?;VhvGa+Cp}7j1TAjCK*kZZS7@~0ctalglQdqTImiWBVVrc5-4Zstt0st7O6@mi zjkXYGW7H@vjU1+9+k$KgnT#~hJW6J@_>x=|sNFRzlgIBp;T|@$R0%(TuDhDQcD0`)+;qP4 zpXcC*)6z7^DkwCvzgf~zR2&7+DYcLT4LuuBCJ^3!MPIaRajT|6nUq(5+$02uc^TWb$iMPxcM>%8 zV(&mUKfI0j)se^b&jt_EB?b zmnmIeg7--0OLHhpvI}#{Af8j`(4eNv<9icMHfX9euS}v?j zMg=w+X#__zXbmw3#&Th-EZINyOb!72jI9aW+n^_PY;@h5q#+^ko7kczF8rnoJ5`54 z#4n7r;&H*6gp3b&b4zCK&)x)3U8Aly^fG3+GnB5lS1|o*gRK(HVy4P?ZgIPs{q#9a zs;`FE#^6;k)%t7AKPY5ikT4DBBXR5!k(e7e4OhMP^C}1!>*_mm%Z+PmBbo_C_>7oe@`CxfbXWPfc0lX_sa`A zc@>*JN;Z*VoZb*{b}qrYj4toMWq`p zr=-&uVKY74+>SQ_vQn)k-@!Lp_jG^4*ng%brFO>B(zAi=!uHK7^Ct{@WyJwhP8*D% zvUCMw&0ofmmgDcDt1mWQx@!m0f_K=jaHbE;f0I$5-uqL|7u=Cu!}TyvU4*D1pGH$d zm+iY?>}2f>Kk8S|kx`1N)hk=_1#G!8Q>Xrcu9W4eAsWeXBgDtv`{=Y(qEp^NHf6vs@8?2xC3+w) zxjWHDM=|Lg6&NqSl*ke1d9N&{7YOHNPYEP5G5;Q-F~`H2$c43r$19yJbU!{~zyUMm zxIRY3DhWTLporbMP-ljF;v)Gto
v@=G^`DFXE2D`Vlr~6tWpG9w!{NCip0_< zv~U#*zYUob3rmKG5(;H+Ezs8X#z^b>dcxUsG}|psZ(i^*Eb!F!F+Crehud9_8~$Pd zyjz);Zt!pcWgxMSpd!{|1Q#czhDLzlO)0$Q0DXH}vz-rsWoPGaZ&2#9zJBmNRYa<- zm(VtsIB`Qlca|O;ga3rlmx{7wrZ&iv(P}*@At+QMspYt5WLXa{Mxz7@8DR0cjq;-y z7VWFcUQ+IyoWX33@F+&gjd2R_8#o~P8!H39sJybQOX!bueKzYc$%X`W{!IV^99yeX zkkuO&q(0c5!Ktg%s0q3pg=8Do9(kE7W-d_n+hhF)mK!Z31aTTD+=8Cf&@Oo#Sd|?y zC7izb-=T+KI|lpuGrvU|4jMX=NI=gADrJ2YRIrZokfx2~Qxp;gLzZZqhLV|hD}>gL zzmQqZamU$YH{qi-+RRV)9=evecLyWk*E7R^p?SI8gkP-eKP~*jSV!8!7k%0DEDE32 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 Z&7GfPEZ=jvv~97sE6A$6EtNJ2{vUXSYQ6vf literal 38043 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

m6 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 diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-2.PNG b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-2.PNG index efde382826d1d8270655f0b211af215cb1f1b998..086a6a0497319027da08a3e4a889b215e4a6e6e6 100644 GIT binary patch literal 84045 zcmb@t2UL^E_dm=wQ326aqzSAIqzFir8WjPh_t04g3DQNRBs5(GR_Y1{2ndMO5JIHa zgcSrN^b$HjKnR2qS_mWr{=u@}^8cOpyzhH{IVW(Q$viW6?%cWaxu3c7!o*0I>!iR* zHa0e{`}gjev9bLEV`KaA`tf6|l4ybEzgho&@Hf-F!&cgJVTtwdv)gUM+iYxQi5&aR zzp$Q9JiKS)&&GDD<>>oEyKj*z8{1+2{kyjJZ*Bgcu3eM3H}U6NE(vr|7;iD)G15f%$i}F1|!SN^d6P$-mOq1wjc;yg)&;jHkFi+q5B){ zKixTg@%ia*e{ynASwXn1%pd+$bn$s#ACHdAzt`gObKbu6&pWINr+NQBRSjU~S;DNGDixiJ%CN(E>dH>aU{LRdOVH@Y)depbNTtNOM}3if#-Hu^boH-gqCWj%(9R&MHe&jlY3 zNS1}2VaciB^$KRK&62<*OwR#yMnJlrMhwxf_Pz>P!!K$qzIj$Y$5&<8dbh~#d4KFt zqt3ZQEfQY_XigT0jcN*<3K)^~94<-9SCQq`*fxg7c+b{aL%d!}K*i2s zYHt*K;r>ON1!{&9Q!aF?Pb(RGJm+N&%P1^U@6C_AeP8Ka;OcT;H3f04Lb?=`WHZvO zLs=@!&O*e<-J?zV_jZ*qc50e3`A3nKo%D^WxdgS+S0~4Yx#S{1<7ENQtgs%6m;}J? zd+sRc)Y9VT~{szHGoHv%k8erZ`z{auNs^IQeq6?ueGIh|A~n7Pb4SR`-FATVyww0+1T;tnA{ym_z&eHG zdz%Og<1O|1SLR6psz|L5EBqvn3`Fy5@ zc|39GXSmx39ZIG9Ae}%F9h?UKiq9Y><>g&lO=_vl$j&ieP(??^gwHEGsc2M_hYsW+)E>hkKj| zwJ(L2BrWdnG?v|MOd45O-r95(ty#EU-@l~G@*YviX51(!Y`GFlaJ=nzX2A`czU7Tbo>B`9k&V3G6m`zpirNfJt)A58>8AjR2qz5B?)lkdRhef+B(6+y zUJ=nPw-zW&pr*%=+OO{Ieb}U4S#DVnBI&CpbcTk-cakqXk4pKbIqz7u)upB7&mA?Z zdD~h}yiTEhf$D$tqf|V29u}7j4pjeO%ab9IY_T)$i2;+a!e5+yBjnyOjMX+>9W4OA zDDji^K;pA8?h3@V=Erp^bV?e%gNA$vx}#b{tQh1oKfHXoXJk6oJ80m#M|!^}q`+#~ z7LgEn;2R^GJ90pIp&VW{ZmFN={Yyz4$UF1*fGYY3-7IU|{!z!(vDCtxop3_Qct(fR z{>};H!8v0v@~{E4we>;tp%k@nS9RFa>p3aM&n`jQ2U5Jj zT0$@147GS+&)wq?>1 zX<-?efN^Ut29dN^hlrbK@9Ud>Lp>RV5?k54B6duh?3C)8j>VWjCFN<|edzFBdD?JK zs?CH2;CN0>{uh_E?vD0&#r{U*`qrL!tG8NyqXw)>xkNoueT-N(_13nhFikbUW5<@G zI3s3I%3ZOF)6UnV^E2V3C;EaVE-n}yRkcZLI%Fb5hR~8I^MRDJw!fgt3h`=JGqnx@ zkV+TZh@Wsq5>Apdly^n{3B~S3Me`Bg_TQcXb#gf^Oj(aDRtqT2qL^G5@iBF+J|U7` zLvPMNk|#Lr1Ax}9O)F5z5z|L%U;taXY} zyd-AYBW%}C-@}*k6u-YGhjgNfkS>2;50`od{p+n<$h`D9IgG`G(Y4-1f}f)6`bN5(H_?@!u<6ZvQ+-zlq$M zCFPb)s1LRE+$M+K^B^%cwk#MoWsGCyrhOxNW^v45%Z0hZI4AQeO$8&T1;(G924sCO za_f)N0;()fWg76}rg885j`7;R6bu0NGW@Q4tNhL}S+8C7+So~#F-O)N{#@6%J)n&{+jN7tC?eskAr6YA2u6mqLRhz z_B_KDEk7(&x}siUojpNAuQxZ%KxK{RaKcBLy};6J_u!zh2Ex1d;mj{2uw)2$Hw)V4 zJ~Mr4Pp@{hrpc%psZ0E`Q@+(@%iLXl%ryIUqosxPlqxRWMNGt@ZNnc+_)MH|$BZ64#|8U7vbBwzk@0X^-yN3{~0{WO6=-H+a>O%`DgJeS+Vh^^H3 zMs}I&q!SkvwppRWTk>Y_f}A0ElS~-tNcNo&Uy3FuK=>QwPS1&elKs#TZ+#eP9{DH( zqAh9Ei10|$6Ul<+?}S*mSv!l8AOZ48}W7sNRxmu@ueys6U}uj`Zf5tru-g1h%K2 z@nEdb|1j#Qr!NWM?fNke*uc?fR=drK4FRN=16D4wT^ zo1t=9AwtefDQjCS8U5^?;+ofSTppThESe@GKiEx@lu?>;$?X~e)@?Q-#gN!p>v7LF zX-cQ&3pcM&!FxET`%H;xgIm5zNuy)3S%u;5md4M zXqmxv{#=|okz^(5hBnx(&omSbm_PAXS7)LU9CS=q|au?=> zEam$w3shAP=_OVEAun|DELRV7Ki8Yu-p9hI)bNi(FK? zQ@B>mvudaetFk4?R@_kXoU1f2LS)_%EyAxVmU_y`TQWOGE$a--^U>UrgJs_NZ*JH` zKy*+E-C6-?Zs!dH$Z)YkmZz`(i*75bWY-%dbE`71(j@Yd<%3CqZ2raOwg;e{Fl#kx z&W1SJA0gNOMe7AaEyX~Tf#VQ=$8XeyITH)7zDv(dl&=NEYZI=T?NvRH_0AAyK6qVy zx-UkDzg{jdKnme^y=$V+^Fr=majHd}(S1A612xW7kfhcPOI^9k)+jyj-2(FChrbWPCJtK&fD@Pkw(ZE(&KvA3Qpy{xn zo%H55BzHWbL3LQ=v{jZWEootfR0<1U`?8`f$<73QKzUS$ntC3guv#LpNbi`8q|=!@ zQ$_y~n^jI19dy7>&Z80*A-T#aR$zSIu(kK8De3qTt9TD;r4`*nIJ|1fL^e1&y)yM( zPgU2{P&Jn*yd!ro#LQb}+Q_}{E3;uz^Y2mqTe5%@GHvQw4o{)WH9o8@RpJ?NAiNr> zIwgwEOPMLMY_PpiT)9IbXyVeJS*4Jp$IZO^KV6x#BH2p@SDmyu+|q(3CBO^i2unK2 zz~;pxLZI)RDcPCli29(4v*zmFCg(4?K}C2gsz(6Q`>HG$|7yjlnjz95cv3*ZPC3|p z+5!D~_`Cr9OtWEz5?C@AydF4_)mZjF(hPTw2Q>Zv|6w~e!^c4L-4Wwcq{Q4@xPL^O z9M4(%M*2j>?@UnSXK2Y3BhA_`mFgdJX?%Lpdlgg08dts3SNi3M!(auR7ZpB4eqlDg zKCz0l?Em*G5Vqb&&~NREE&X(u#`^9a7#!|~FNGb?5nW)i_j}du@mg9v%9Z6UJRgE<+4H0@DyL|2lX5~6&t#wx=?HqT0`biw5hjUmy;iJ=JvG(keq(q(*;n3_SUXINjbJ z=>R+BJ4@sKI{F9KkY3xTTz4-$KPHC2!WrA=YkaLMAclgKSL{_`u-fgin-M;{YfU}J zYk;2vta>$Q_Q5RbGZ{?CuTvMUE;jM#*{f&nx3`p@O}WzPF$}^w1A~lE?L&-T6ZbaO zr_{^?GKwC=P2~*Ku^M{o^{2;Qq`i(4gAEIzM*wnWt$|{ck|RBkoEqf-OIR##^8TtG zlZK(KnN@&=^Y((c{F5_x?q>#$BWJQ{41>V^jYJAhj~Z<6%L)XMnQ)XbLhw<#{xNA!E@@i!jNA*<4K0VlZu z`zE8_L!~okMW!XY(l!kEH0WvJ)Fd5B!!(a$$ZD0+18D7REn+67aCX0Srs$blWCss5 z>y(|4{ea(AGpA&ThvO*Pgcc(RNx{c%K<+N#N6$+Vf^g@htWPHZ4tCeK2aUx#2V+QP zX6}<=dq}^0lE&WK187W+3MH^*)2#82p=fw2Bi5y$&59aU78+q62OZ)f=bqkr+uBN} zb?GHkI3e0hUWSi%w`8aOAr*LgEF`qQaUc{Dxpk71?{dHc@-k2z(VZHkjRUP>!i?Q} zlCpCb%B!#=+RxQSAT*}K8PJ7WmvuC)G+dJ+03 z_OlbpjgnrY?{c_o&y=bk)IFS+_d7^>IFw3y9-_AtBAs=nx<2@R-Tqic*0MgJ;(U(WbGM3WvailfGSTI=%LFdmqyMdTZ1V}R*QX&}X)mQH7<}D!M`Av` zR^jjwViw-3U6z@R5^^)UkP&hTqCItT9xH>(TmT2e%q^<*{-VvaY;>dbh&Sruyl!vQ zPBTnZ03C@9yN(Th5~U)~2G;z|nN4O?uEs6$C7E9rYd+VftXkX08sEW zX;s?6g&jfv`^+AHcj*Iro^h*iVMdRCYY2Z~Zaq9Zc|av*FuxkURcu`ESIb(=EnLcz zf3YNWYk|vY={ne`19}~86s}1Q=KNnatSMGRRmjnmuPb-JVLWU1^O^%8G8i- zzwB*@wGks4ueNR(z3!FD8s9&uqWCN|J&wmIT)-LBLFJ!4+h@bEdohMbl zY2?Gs1#vXQQ_w|rrDxmMghT=>Ku(yUY!`TY$`bLRMX!%@C?;V}#cCMCmu;@y^PiaBdoKe8|1x@(KXy=__RC6=a-`dOSuyP2iB17@JAHm=VKLaku1_)$p(m* zhbeoH4;{l^C_OlM{gCSKcQEj<3rLD)Jl>z2eaE2jMvw8#muBM8rG#F6fGog^N+5=- zE<81x63BCkJ)*RmFDI%eNl|2!Q%}~ZaLSf4q>&fqR-C$!MKuf=M`_Z8pJ>F|+pV{0 zJOPrgXt>?72aZ;{RLS-tSa_T^@^F8YNmR>B)lbo((tF$jh2L)CH}ZQz&}2X%eruWJ z`zXv~WrL)olKsalG?l&sSp>|!?voH54Avo{XeR^p04D4o zp|J!yNm@C0Xprc5y{TRxqb_yeDvQ95v@GB5haADz%OV7z8hRRcE2yDm-&JMEE zgOFE~51E6poCn}zHMlcH>4A6c0=s{UBG&If-+3Fz8^w+ffJCCD!;toxq=mY$CE``* z#79$42V&GDoP+Brg`;W#x8^TGQz4>|K94%H()U8s@Qg-Q8$-xa_Wz2OrPp_d0 zx)xyl>q2e$BUM!yxSz{aVrmIiB)82gASTQ*4v+y-pd^*kdg0x5(EA%V!zp^C*2KZY zhFO1;l(ur&IGh5tuGnE|YmQ`d!OrQhwLFH_oQ?7FQ2~*aDFc&uNeuYattv)c&FOU_=!J#N}69(@5Vn4YK%--va3esq4xX!?NMtXz2R8Av(Nu5RL4 zFI(Ul$lZRy-(ZxPt}@^F1NHA4Ic_vh{8q@Pv^!llqsZGU!lek%k|#X@)LkdKLL8hM z2b@k2&+~pQ0P4NLy-n!FUk^M~0f(iM-Mm8*#3Q5^?EMV`?a#;yqGDgh7oG#Ct$8|? z*y*){zZ>~g*6C^b8&3{ya&#zFa%`hevo0v@;Zy*V{Fxu?i=|o#R!ZB;2Rhb5tX?;% z$EmoQ2ETbrTzN;hc-5i45wljp+boRpF?8qkT;Xsog51pE zv&&~`1y|H~nG?Y9V~?iY;@^+>Zz0utU+mGVLA;SScp?giBW_@(8M3#U4m7&j>;ytu zLp?(eD-By#Yo@c9BC{4y+ybP4w8-vZ$g5D}>d_MbJ<;9GX+BZ^`{Q{A{qMy)j+b;& zvIpBF1zQ#$#xH|to(n0R6*yhMr*ZYuI;jl(uKUb-IuB@?nZBY?^e2)Yx$mhnVJ$gl zSt+7X-xuUAo$p8L;rO;1N?75hNyd;(E^p3)ej6wfG5YI##(RXGj_(}AU&zeqM2n!J zjhv+Zf?Ii`5V_LXRt}~HJN0#MxT9snicPETLv>{S?sL`V^Zz)z9&J_}P?WtlGd`B@ z+BMYbj4G3fg66v}_uX80e#2b<{saC9zNxc1H3!cuGod389pCn-4R(I)NcD_A7b^{f zjjiPb%NoY7#5wXw@6U+&H(PnjYp8*@ale)d*}X1wPjteWNjyvW&0l^Z=FOnA?+xKYn>+W4nd=dr`Z{uzB$)yX5He#OmqMP@ng6e0^qPL;uJcwGu0- z;d_DYFDz`~{i}}5_kzlkN5bT(=hEs(Sz2H1^+<6KOjxW$kzq09pYGT=kA0K-`@0W# zz9@$e`@Nt~f<-cTureKf_*U}9@_$2SZT)y+L_@Bac`;|sOVK#T2ke6dh8!p>v_yV- z?5sq%cGVcK+*y@?K4b>js9E$zW!*_5Qm?A8>wAj*%c%qh0gW&^E6V7rQ|kTq^mw z!IZ@qTHeZSzE5Z}V8j7&A2x7b4F2dy930)9rI&BA#DAX=S=s;RvKx2fh$oz_-2c{w zwR~{Jwkeh=KZl#^iglj$h?0eEm0_K#+Do<*f*LRDOF9AAlp+rHy{2vCe_#&(3}v(x zXKwO%z7Y{KucJ@-G_2yoMm1}o8`-SgP|C@hGk)vO%L&&Kor(hle?eiq&kVJ>tkljW z*fI_?Xl0^;icnjTu{gSLiur>B+n>T5+eb}#&>pA=%!yD42 z31ZK}DxUVFf7)y_;HpUX!n`Ahk+wjwn zs0daH?=S$yg8k21MZEEojM{X4GoeylO*~z#{%u{bJ)ti?$^}Y7fl)}18Q2UN>e0`s zl)E#El&pBHLwSy)g9^@j&`cE)b=jS!Fy~YDJT2s(hGFltKJ)dpnk@9~t`NJ|NSU#hplIPr^$ zmxJEvwf0!Zi4C?PEyf2fD&4!>Qr>H^x+)(pf4OR4h#CcfJV}{5-~H+2*{YIezPB)U zP-Q|H1mfcn65|qw^Rr0syp>z?*4sS+cZh7A8z&`@O$+0RT4LRX9`mS-<~r`izoe2s zY}Cl;rBJ0KG$ci&#D6l)GXTh60Y4Ie@Qv@BlsgCCX)B}7fUOaZbLgL+qcsU+$BUOO z+~t+gfR2p!!h$tG{HHiWt9ac#fehyWZ*?8TTxHFg6yFkfS#o$R*_85%1 zJbr8TKMjk>w^}$LF}4{q{ahR&sO7yCN4)9Nzi%K|3&&%Cz19qxjHST4t95%^54Nq+ z7RXHzBF_+N#nRP2AH{m(3=rW&Sw$BRFbXpe?&QFq9zy{aVdHNP6kGYeO?P*1E9LKm zoSaB&MDpiq&K@uuODJg;UZ~{>j@G#4h0KYY(*u46#u6z5W(Mj$A;yn=z!H*C=z618 zl+vkhOM1~b$aku*_jMdx$(Ui9kVDV%(DD5v&)Ht?cOfbtmiewMrRKq+XR?C5C1$8V zGZ#45;iNd3__{84JhDH-IEx=4$LzZGO8vZ6R57QM5D8t{-O^_;*kbNAer6xlJLkJ8?#0VI)yXN{VidmWjC5I9KUnqs)(Owu*1_K6Zy%_12O(;aC+j&t=@?0mPK?drYX^lT%R? zLt3?rjZ5IhJMSqS8R)%8f_Fi`WY=o~pVxj8HQ6J#vI?o>(>aJ5UnXptZIGdd-*>Kc zgG{-+^n3^(79nRoY^o&2$S5>}Sg1nz@%^q(FC+0QpVgoVC`&CPgT9Qg;m{s%p{$r&h>5JI%EtJ5jhVpPdDr|EjAuYF}^&xw2mkOsvmS@)}Pglk?7PLV9sB7Bl?<4S*U#(_Q(MNlGMdYR zdQME&QxSP}lBizy5qx}8*7fA&4 zbSPyJ)_iRm8!o|11m%`lMNIS{C7XArX=$VUJ)Y6qUF;BF=!wD|NNAoVOKpCw_Du~zpYb2GD|#gw~?yXke6oK#o<$hzD| z>*f-1Y{s)GTvdHzw!Z6A*yAz!dIJ>If+3aQfBt4sY?(hCfid@M0@#AfYZ=+-aYaY# zrW^Ynf&G)z@0jjM&yynvr|>G?aj=qiIPw=y<>%9PkI8)B1=w!6y+6Wb0;hB+Qyh+` zdePz6-8c2VchSfH1%R=p)hP6_w%PLZ=mk+)De|M5@ISi#{oWTyGUT3HBis}6IWn&w z6$ZQU4`Qq5{sMV&T(_koR=d4Ki2V`PCeGUhR@=oyX6#5*>oW0L)ltgx(dS9vv#-*| zIf8aNG11EqDeh>atPBMhJUo!FoIn1x8XH@m_!qgN-u8QTIaFMHO>b|VM+=6l1z83| zT0Qew$<$vziwa+$o7UK(BFl_^B~ETyiT?AO@Ygn;{WB|UQz!mMG|Ph4-(UKF15qo7 z7N_>kGNOb6h2Tf7e zcGFTXI2owA4m3_RB$khbjDL7H2RQVU(}{52emirSrN#$8<1T-F?aldgm+dv^=cO4A zKtx{iXSin2)^_-p-l3pTzeCIwkY%w|E|97x|EpwS6bS9rHT16BB*>%g&JALjX}w(a zOR`{2+mRmP{|@@LD2AHf#1pXzDry&YW83M7#sR?L{)@bR$yL~nOmhPJzMUK-+e02E zwsRgbpq68wg{V#GiF1TDZ<1pTsn>6rlNGKB4OFo9ThfehjPS@XP!SMGx|Qe!2RSl6#x#<(tx&l&rpG11Is` z2RcRJ|3gzWjPMyv*yss_845-_7|Xr?3>dPV9=N-G&@rk_c=r~l;~l9YSrptygL+O~RY{wIE{56AK6>Ei#b zcF4By!xk+bQA%MC-F?*k5aWsp4AR_>)5}%v*F+1%5lf7mMJ;KI=>Em-+44Vlt8`d? zzVCRb?$?RCb-mI{8&mE7u3V^TovN2xgOG%E9?ep?rH_LSCSQnI9eJzh#(xdDZ=SWZ zT69@0$%gOm%CeH#SAxgN!86SqGF=I%_dy@}-UcnTJ=~YEiZae(+zD>4Aw@Rrw@tod zQM%EWF-~U+)M+r}*B#;(E28=usPc`)2`Wpkl(h>xGwDAHhom+QM_wQw?jwK-`wJb9CaWLyB*7-@P-eIR0b4X)>^qp>{{~ zvq}y6l-oYU(h_+p3M|4NXD4v55VF<6ly~av>3yFnBME=rwXWg1UoIQgFX%kP-y2QK zeD|k6`{eZ%^2TZVcp)RdZEOMcxmn^u;95 zu%9AJ5~>3pC^qfS%e06WzBbe2I)&wp`6~QBp7ZbZ+~Fql`!sB(EDWsUff-B8s=6Y7 z#rqdb0@%z0V?#hjMjVhLdN}W3Y`dFbam1EWYEtUEpPuDgFm{(-1t3`PWVbz{Ht$kq zs>~p8LgjPL-Ukx;OuGB_a12oyOZlR!KF9CGK$Hk|-&@gRClJ2~zaj3r=HdC{eF(u-EzNo(kZlRB7i`_)~i5~=($K7JaVHPDRy3=hF=sSMbxxWlMo#Wg7#L}`* zgk1|^5kBU2EFY2B+~t66)A4oG$dQ{)oDI=34KSbMlNu+^7PAkhnC(fzK)FD`WWjaW z&A!mhILGeRo@WD)@{>Z0z+z9*MIXt1s40%FSc1y|sZEpHQ}@Cx+Y|5aX-a?TwemYp z^JIoyhw}Hw^-fkwxl~)h8`-m?UG}EvtUu!&eeay4&U5uZZqVC!5OFN_Cn;pu;&nyF z!LDAlo3C(s;==E8xeDO)VtbPAR$}ixPUj+xsa5$I=%mwRhmtN|M#aPn7f zDV_Uhr~@W)uv8f*;Zp3*_(LL;1WsomDeH!6-&Whp`>cHbQ|%%Vp#}22>J_gh1Qxn8 zQmH^mf!@y@$rW?aBH^{_M&{`K?Lt8J_(-h5U#yIOWlwG`jyU4e7hJbn3~b?0t-(Aw z{wUS+1o8>rd$tYMXqM5A9(2k|I>E;o%5ha!a3e^+dVlv*pOt|PTx=>K)v2anY}ttaM|`aAjC~t z*ufD$ly0GJ3P(7iMb0!~So=W~xfS$9&?gIDVWCAFF;=?%+R?p+dhaM2`^N zWoCC{;*`g#@v&0==IW#x0YaR$@96L|&WG|zSR|*`vl*{#9Lw#@^ekZ@0KS6lmm`?t zM}E{M3;znpQFig#S(B(7#5!&Ozm>*aK> z8|yqLR9Ln^vTSk68tTPvk`H;5FVRhY?3t7b_-1Eq;cCUHH zm+6sB`U=WV>QL}-$qHuhR^JX+{3R{(f}-|}CI}24tSC<)aUjm&43=I!Em2Lu)FV~N z?HTVNr&t#YiU3+vW~fDqoI8piyRl~y=K*@M2C5an+6KV(u%6{2=k=G~HaMQ*0OD+l zxuUGLJp|dk{VvMehpZ=HS?9_7fP^%WgH1vI3bc@FBo5?~%=bmADDJOd8oV4Oak@n|n?76&AztR(0zLK*uzdYcD#jC($gpY%c7%$00q3k zCr?z?*GxspE3jg`FBZbKcm(Q&1DEAzLV~)wwvnt@|5=0z_H_ca$_cP);27tq@Ms9F zL-~c*UiOlq;}giYDP?1ux?>(k%*QmEp5$%j4hKk1Du!r_6S7rMjsc>+ZgIpH*o!5v zRaI?vUY@XVzNut6?17{ZPb|qPd{#Uc9>>K-X1U(4GSo*(E5mf7k&^mcLKUKUP-Hc3Qg!An#HP=5T@9^1}u$B2D&9 zuce2s3sz|-kIlNS{wn%-TIt*~CuCf`U8Vx;hu;_9QjUMisJipux2jNWN_Vdmmzy!d zPEXNO+qeIwA);1qa3_tcx*;6uZ;w^pluH*}Of>K*J=>B%{yz}-c zyZ?ggvaa!th^l$QJGmhJ4f)qkZCU5{fAbeH;~d>i0hNCTZ(I2ge%mW^jVjG8W|>eu zAGXEzzoGa8bp!%`b(D~2ZQ?Pg<4qit9Znr^voEk2$!VPX6$~x;VFq^`U z(`UZUo(YHH4HzJ3aBha9DfrT07J%tLN~7y`E*Tq!DulW;U!LD~^9}7nG;*rn}{%w$wbU<=^?&WAjGM@>4n{7a;gSQH{rj-r4+Fssnd{XTgH zA#l_=|MHWb{y4^OiL=+TW&=5<>wNa!PzLMI90eXM^5nV6Wskys$+JLiUPsoA;Sgk{ z3X5VJVrNbKEs^@fb(%OU8}T~)F?W5?z)eBzEdjiRU=vYh%J&p+&;Ba8t8vKlB>xKu zkr#^x+J`L-87%kDN;zc>fF-+6F3mWCZZ2+lS9$pv)cOVMtgYzC9X+{JAH7byBFB}% z1mtD>phNj658L82bZEK(Yv5&x)c5A-KD5VO)~Q~k&*=5(k$x23Af-?h_OUGV@ZDKK zY_Z^o`JM{tb+VA4UZL>9P)}k@&0x#(d3_ui~|k@Z98VQ5ad&p4;er3r%u92Nr`4*2Fo_nc)&Z&n)-w71hOa{d{wB zvEp9BxGeaKVBsC(t4AS7%;zbNKWh!z1C33Vu`im2C$I_5dr7T4!-+0ME1R7%m?BTm zW|*gxu&1Q^1z7jxfN9s=o8fWLy>MKTGpRGTGV^TJVCcT}-O%ZI#;*tAh6A-0VmOQ$ z!X=+>iaSFib%qLtaFgfvw|co5D;37#@V2$}y1BD(VobHiwwe-7WgiOFU}0LQ{u{`uB6oc?*jSOGn|2-F=lR ze=l(Ofk)xS#HiQWCCzB9?+4T@Lm(f^1uD-5Ca)}iP$o3{%raY|$E`!=C%CX=dWJ9S zI_Olbpx=0IXsfbRlnFpdQ!q6L6vFnR{lbVPE<2F|x@m^$_f&2Q+$s?SXP@6NWNCOB zq&%5r5x0B0xoGF~?*WA9oJtMOQLTiW&_mfb(6&H4Y(7x82uzK;sfRYUMB*2$I zC5_NH*ksN6HMEF|qXxIN+My%RGt(KnwHskiFp}p+T*lI7abZq=>TsJn{;sI_tC6$2 z;rnTXp7t^rVbh!Q&fGhOcW=%e{>D(v6i9rvd%SUjqiUl}F)KJUC4pgz@LeWR zIu&F9Svtt}MFJ~n;jL#)lUNLfL@1@;YZ$-eXiDKWnat-VbxlpCAq|PA!@Y2&n{y0) zREDzV{K&6rrM%O<1UcQ<{Kv$)n9d>0_&5`e-=tfHE}Jvk=Qfsde*WeM?i8H~h-E3J z3zyLJUg=eW?|Q(NH}_eB4=z9pQ@>H>D>5g-cDHI5=QCMqYuhf|>f83y2a~qxTd^Lj zkhBEYkTZ-T-w%9ND(G`)^7}4dF-Z9Usyu@nEhdTleD^|Ug7*HCIntG(XhLFVT=3+^ zJTrMl`P^`<$t|6e!6#R9kfl-yD`VOE|^RHKtaI8 z0n(m4^-&X5Q_~#RoJNB4;l7Ko(&hSf+Hb<}c9vm+Xg_W+4JM=SwaT*YQ;F_lp5}cSkq)_HTM3K=xT97_xX)`?QmIfea0_-R_sYp(4cM_`l>nTzW2Gs*G7AbXWTH zT*o2ifh>Tl%y{AiU+&CpbKl&!KrmW`la`^O2Ggm|H=fTH zaVNLNwX;~oFxqc{(^9NE;y|Ui;X{rk>?` z^m2{E1Q(%f8Ar}rbKciToSFMTlP8PenqF)TwM~PpLnA~k?Q7_g545POxVDe6LWvi% zv^4zBN$6){hM?P9<+SE?N;2R)p-uVK*CUy{enwA4*0tr$XrN`D@g8&F-P~1q3B37@ z(|0mi$G(nJvdcPSMOL~PWb{u{aTD>?j;qdk$}+m)wdW_RwIH*T`Az4=p_4{LKR)JB6Q8Z||^cfP%k`LuP5$%n5Py(FBW>$lI0PS{#g z8!{$_nLB2{Hoc|7!=o~}XBa#iDn)_QuVTDOH#L3z0C&oGE|+FRyL_N{%fFype|&u; zU}E#}^d}4@Q0DXUhdlhghQ*{CS@Zo!KQkIbq3E*AY`tu@*01TU?Mkrlf&Gx0ZccUa z*<4r*sh{th*0ryO);yl?a_Z_AaL?lOb}leXY(xPNzYF3PtzuMr(tGdH|kuZxYZ38Q>31o}7U9h8ut_-~{0c2!5?`aln zhH|z7#}mSwV!%taoNT}6{VEs|nEl%q-`^MZBHy!4*>v1Q!&iqqT0^D%f~PTKZptK( zOs8=^>gN;5V>cfs&XPX&M!wr1BvaaFdK2mE?toJ{bG7KE!p+fI(Bp6g;bqqF0lwjJ z=fyB0THangRI^@TY*gtqGK4FUjLK>v{n;Ndor;Q|vilHrl0c)~(z6qqXM~)C+t^}o znXI2FfLZ9D9%XYo8+9xi&)>o>LIhG&O3`JuF-I8}_tWM!P+0?-M3bMtrXtm7)Nj|{ zN8EoYq4nTe@stk6Cmrizp{qO-_{_UJ;{klEyB@w5{lh$?-b&YdmYrP^6L?l6m9Yz} z+Eo%p3DyW+6Ur8JQkr}a$#S1lNWsU6KBbnupZnuOUyD6X_MxJt(k8T?xl)<&O`FW0 zqVb{X1lrt)_3G2RyR|!gpFra+&s}UaL}Yw?!`kOqfC!m3^I&t~?fQtv)KAt>G)>e> zv~^dGpzM-7_^y7~_`>v><#Umm?i;PQXoh-rbt0hadoiRkKWjlaD&hm8HvBjSK`|gV;O9kC)fX{||NV z8P(*rw|nDuEwO;;5~PEQqJWK1rA1*0Dpfj!A_Ack5$Po8qNy~M4$`C(Lg+m~iGb2u z2sH?iB1Kw43+2q<+Rxh0^PYE%b3PuwIEIdqyUcmda{aICH;th=)sZ*!u7>3r1>B%4 z-P$Q>P+M{P^S#}c@?tDjNq6Rgt5eo=p4=Y(D`n|-iZ4%K!+06JWOGml8K4z%;b1ig zx}q&PX|MQ^%H8cw6D)j0YI9XT+?3Ylmt!mZg4CZo4N6ZzM-1g7$nZyOdL8FbPo8I} z&xPQ|p82xmhaO{MvR6Djmi*-jwOOn@t=})*?>6EJ8|SbaG~G|&=PjPhqznc9UlrA3 zV!kd?7#YuwO(Gf_2Ix6LL$e0&C%glpfAq{9RebjB55^kq?J-q^S7yMaY+}_)LmW?$ zDmMJjhQ@M^yeO@4FV?%yumeAWk9DNq0_88I_nm4-V~LBZONK41Ul$MqjpA}yOz)~OGG~Z`HDL1>>T#n z8DYfkU=8m{mf_j4`T6k^X}2h^mwN*7CT|0Pmy0zIc(qGEuqqQw#vG{vyLFc)6gic6 zJnJVjmWxgZ9Jf;}zRGQV?(5Zv4x<~~`4DH@^wu{O{@J#LbH>wXe!n#$*y7+#719RK zGQOA;qF#ex4*sg8n}Igcbh!D2POen{zc-q*EP&ktm6~2?pB-aT8R% zLW-Zx%`R*LCzHx-N;g&uoh|eI$%1D;~R> zr{>E4Yp;Fwhzs@ScK&W-OT#U>h1FNuM&ARTF5#H_Q~gkdA2g+Ggi6!3!G34)@4^iEmdD5Nv zh!JBT`9lbU*pD>imO-sZe#)Hs12h}IkyD^f-9P)TZuU&C(5R*8c0`ge^(?EIpNFj! zFU~sW2tNOaZ1Ors+X&g-oo4N|#l<1R}`lP{bvvIB7fTXwNX891)T2?;Z~ zN{n`{f1N9y-TYi9_rHq86*uiOy=JU$bnew`bcUu`yiFU9mNCTmQCsg_EBa1sC4Z=y zYda$CORrL&Zc8`6CB+Amy`EHBbXCGyD6m?;s&TeM!vgVB>zY8_VC#}ow2ZXmXnzE# zTlR=4R@;~-^w*U-Q^~g)P~i2r6U7X)Lmca$4zQRI4 z|Uhbz-nnlv9dxvdS^TO zVzn_xE$jTWfEyrsqsrYdR4tBJr3QA~QjT>!b#Tq2l$OvZn-k|3D&pgYWh_UW^}??$ z)*%9-jdzpp)LW?>WrL2!N6s7eRl6Y<&tAW(dFtQ}A9+K-TMgA-;Pq6R3@;|AK_9+N z18gSaiXAQ(lt9E}8PrN=8ohf3`>a7ML_uG}T}2JkYDVmiiZviuV&PBHz`sCS*Jh#8 zk~{qos#r87#T$A-?6Sh=$df{RNp@S0j$swBmT(S)7C$ksWjWt}tD*_i zn|?%e2eO@<7=Wo5R~G7L{BAGC40A!_A4d;n5$p4JOfRhsYnVe@9N(SCY{Ol(Q%7YC zBYxEg(@!|O?x|b*c^KR7a<2zpDU4_wKv3l*q({zPa4F2O=3pH$!To+qqH3J$4?ik= z>s5`uXx9~0!ksZ!5I?BZyC&sZ;Y(!ENlYGw+=0okcu688Trat-oKrD6vQboLn1m1P zsT^5PY$r;3I;V**Rwciw^>2GcCX*Xm;Qm&#xuJ(o%BJ5;!L@=C=(9NNsz`8+^P>t$ zMkC%<=PvJ|zY2g}_n!_IYfQDIyWT}PYLkr*FZRr{_8-3$SP86U2&dm3o|d4mNUG%{ zEeZ#}#7?@MosW50nYhf(Iws2aTz`oYx|v2m68^eaWydzcg^?Y2V3CML85H+xNK|vL z!X75HE2PSn*mKGFc&s-z&P?HgQM*y*10NPoq*V~upbCtdwnRYgnh^4!e`#8Gt1gkp z+l~0+o^jI%>*k_peY=kI5Zi>$ z*LEUZic2J9{etViAdY&fKOa?eNRP1Rj8uurYMUw+5*rVHH9V7C{x6@4LV@Howqs>D zpV>8zv~dN@gdamtHU0Q4Al#*epOCkE@~Hg6^4r%t{ZZJaf~l6lQ#{uhNpq%f7~)?P`Y5x(3J3#}T;=~^Zp2S0$JyDu*j*y33W zCh|;;IFfC6awos%$8F|ww1LWYJUPr)=Y4;|+}ma^@L9o2Y?wxj9ZfPs`+B1S9#53b z%;dQ!?)c;pZxg*8&s(@T!7=w$(My)S^w_zwvECUCinxUFjF$Tsi5d!VgsU0cn-Gxo z|4Z|#?eY6~=x($&*5&*92!d_qy}OpD0&qS&8MF!whVK5*HmT*llDa+n8_G2)y7|K` z&r1-B&1)Y*RcBYAMo!G>V7VI-hqDK-#);5r*Z{>L*F2{v#q}#)Yab7@u>`Ei1n4QT z(Rfw}26AjW=Puk9j5qf**}~sx@#JT=mjWW3PmaRg`j_9Wh8IZc`JIr7Paak+dYti5 zRY24ug|n{y?V?~bVGgJc)tGpH6STLqP+aEo8g5#5BmAE}-z~YAG$=^yN^Yh$ouBZjfhPewVYP zTkbptWjtDnTrT;EAwSfpV=S|LTKwI?`teu412+wyWnJ#V(QamhhQgJYr06gHw036p z{kFgJ8a>xDNy*7PFew$OsEo(dZ8^Xx z3uh>Zi3Qw1Q}vNtlvft>KEbwaMPF3scA$QH#0FhC9JES1^}%1!`QG07=3c{T!N>mf z{e-YNQ+Wx1Vem18T1-3^_gE05`YS+B`c?OO7mjYLXFQEc1Hr7_(aF#%?|SKGN6PXH zscNM~{B?GMvfIL)p3HiEREUNJQpwj(LdAZ#+#&m~Js@XDN`jS#N=iTORZ)PaOMBh) z@qk@J-a680&j6tX9!Jt+!teu6oRoj!V^Mrp*sCQDmVn*L8gH*B(u7uWfCHLF{!{QXG|u+%F!)o z(%9U|X;Zc_Qc6pZkm(O~C0I7NvFiF+_tr7ZqC=ZFTvA6m9o}9 zk}ad%v7^C_Yc2CsUep!Nd)c9h2oY6v4l+$#r6?5^rR8?ZBHw#oSBmSrZ$`49tW7=S zMbGEMsiGDN45yBc`hnEnUB2|vz3Uo}kHNZqxS;4xV3dxW^r_vz@Zs%Ho~ANpw*A|a z^nBXH=PFJgHYi?OyJ9b9og)31AgLs%N+!guQr5B|5pL;jc`3tw{aytD6C=$OXlN^brm(gQq_=fXxS7h4EE(D0In&*kw<6XM0Ob4}lA|Urc`{l? zjk2-ntoGr^BK63vQJG9%_kFee8=e@eyG=(~ux@o1|!lM>02ve{dCrfn8 z>LLyN(y1;KH(nlV#~to7m(M~4w*~)o_~QWUQC3YWYNJkzB_g=UE3U}1L(?3(EA?=( zPyrt@H#|qD>d))BEY$LyNv;kNR5Md6@t0HL3<28o344ABm$kbdq)gn_M%3j9X0Ay% zYdAODqGGMY|9!&XsJ@0e`YH$1_KV|=bo`5Oa=#f1QS+@FG_{|Pm>ZRg>X_x=v3}pl zOM3GAF9nB_i6`TSn6G=dh2rfj0hXP^Q5KX?mT^_vy%%b1=hi7_17(B5P#?HVKflXC@g#k*xCEuhR93IVAKcuYW7 z_~s6c5DxrX5-Kkl>!wX$$qkWP!M*6FbrrP`i)!%gy`Gvh(D$YEJr;R*DQ)cIj~(Cp zkeoA~j%zER)cy3whqF3^b?P?v{>76Sr|4@P-E;FyKjW*v<}#gaf%7RSuZVc1sV#U} zSXxF(bzaSFwcUWYA|6{roHdX~^PbsoK-!!6xCCv7(_dLPExo7^pL7@hE_2Wwdy)!9SA4Z(fdP+XxK zr8!tcFDFI{zsx_^2583Jg(>l%>NQ(_a^S2ISf1T1{O_|-*8RIe+98O*@INz+>@6Us z^1+sv{yzo^k4-EhZa5M`bm2;nDqZ1Toi~X-v^m@o5&;pIL*a1CH3NRLtY(Q7@KM*i z35ju*v)9dxt+=$`%a$kza2NAX24IMk_bL)uYd4YG)LwgV=||On{X0w<0S5P@ir+W| z)(Loe9AwTeiw8##7)h&YCZZr=x~jUDFCI2Lk{Aw==&(soOcZvW&MLBLD^~);9S1v~0EtV&fLtx!`MXxCLJgW# zf&1fj?jB^uv_{VTqWX-%bhe}~b#}qs&1s@+WMl;9%NZgA|BWr{?Ni44O$V|aNPoHl zv;nZ&yLx-x-5gk&39;-nHECtFT7w~CBjpGToU)6*C9BJ8Z0vO=p%*zQwLvylmj>ME zU%JBK_RiT=N@qMu;T=is%dw}EA>SQ)it2tQaeE#Rp5^b4FF^`I2v7;q>&JflW-eI_&;KTHX&+*+H7e~_$#Q^n2+xP3mIt-qVs;Mj1Rt@}aJHh+e}8wmAy zKzt(DLT+}gSEz}FYj}=TY?|a*OCgS9ietGs_jL@eO1Rnn5oIHQ+MHNW81yL-r1H3q zKsSk1Ih!q=?O*tRqydqXiD7$TM2K0z&Sl;)Ii!xW-b~s@dzUi#G*QE1%6w-q%)|mW zd&q*#lgutfsS>dEJa^OA>S7Od>1d)zZkj;j0BnOBFXuWre&yLMHmI58rb}B0Ze5h) zcGb6l>Nb{#n4{8VT;CIT9Ru- zY~HoD$G)12FtD<11UNV!q&cE@&Y0#8ORUY0tt$Ye;_;=!g-XbULEy=fNjo%rA7;4Q zwoDWAufP#zdysI-ll{v?ay?P0IX6E1p~NE>zd)^dFf2sQEE7~Vw@G2vyL$k0%EN0xf`89^o6;dW<(c7gc-nS73sy~eycp6jv zLYDV7I9W#iww|JS{(2+&a=MPeS2MSh^ zsio=De0gy`CFA{l9Zw(hjLo^d88|NTbrVUdQ{G$-Rq>vY@AGh`VW&TN@-c3jN$9UQ zZd|Uw7Ms;t-@Zh2q@IUkZ$iG6twS+4BP>!@+GFLI|7alS@z|@Ov+g*n$2Ge_H^thL zbTqW{Mgv`^`g^i{ktMDAq(?~@$RtEONs+hK<{7r54M54X>>C;S^>+5X{BIZPW6(v0lL2(>40;XuR?Y2 z?8rKr-?>T|(s;&Orm>`8^JiOkhacjZlI_KL#e;;)b1QX_Na%~)4lSM~H?U%%dnDUH znK+QJ_Rtf5Cz_ZCpN^Z-9QpF)8j1Ep_kC#%?Ctf$>TKD&ft5DxaS>$Q_PnAf1M%i+ zsN^IOi2l$*&lT$%!X2ZI@9vnfRX!L%RGka#=lv+U<}oQvo{eA~is43AmWYeh*PyKt z*Zr)Gt~B3PMHH|-_w1HZMI29zf^ga18x|<3Ncb2?wA}YN>bLMv@+GaxZU$-scCk-ZH^|Y> z$!OpXgW>9)IwlxDGgn7@iy19%bKngds(Gp*%_a9UtJvM&s3Jx$^VG~`^*9xhi$zBK zs?Or7QP%ke$b^#lDNRGG=^}lw)^v38=%}VOqiJ?I#l%>JxLi-TbaFk=O+8{OYnc{v zz#eYzRYPKO#67f$t=T~7?(-~Ph(<@bB7Nah5l$Ls5a{CaW?1z?9E+xe?)*axWxe#; z9~T>Ffi^JHdmUzX%x;*jpovX9*y%59#Rpj-7Ioi~yTj)*EV4DO_FY!m{0a)?tbBllok>vC}T%C=rV@;bhdbIM3T(>(n`~Ak1vc`Y^gHVJdAk zijRt)-^H#|Ts8kh1CKCm;CPwqJf`Y8uYKOFZFIvny8ZhoaM%$TYn2p)#Opc-sRHiA znX`C5$!5c(vJU;!9;fL=5L<}Op-PW9M~53>sWR5$9OK)iYl)XxfM@^tTeZFjav*dg zDzOA@fUEntlc52XUs@`wC*XJXK!@Hs#9;1>i&{8JvM42kX)6GL)o&Etf=cf zyu!^sJ&unhSAS^>F-u1=uCWh$`t(K{4AH(;HW%D2(*jp0>f9Z7o8#A|;>8E9MxT(< z-d|?hf5VNYFEs>gc6#CUq*-j6bHf`=vo}T`-_KNwy3QM|^63lzy={f$eDs#X?;9iI zd+Ro1uF-oZrChK;FRf^*%4 zcrM!OjaYr8{LktqKwFH{z4`skS8SJZ^!kt3t?U3ttU(A@-BFKRn+INft*%T*8Klf1 zMFaVoh0?axjVHb?@O_4_P9Lfzx5_FutXIAiaiS-GfQL5cNsKShm-ViHwlc53f$^hWrh9hw5~ z2<*MMgALg3ODm;loq~R6wfTV7pl~|!HW^Zq`75`>=Fh$(VTROVyKGt{`1z#U{ zE+G4uqQ|Cz!M{b2R1&AB1q*7yKt^mIj_H~2F^e%Gz9$_=AcZT(Dq|i(w{z!37+1p$ zd!7ndV;cL58|NdY;GyY4Qfzv}a6^J}(C|O|Os;;f&p&cGHF0i~2izdffKz7Ej9Z$Y zy(5O2g_QMtf7Mow$kL`WSJnH|a?1s->wALO4b89nOLFL3;xR|IKw-k<1Er#-a!58{ zaBPL<)qAeg6yvE$8>gh-^oU(`y)MOtg9HPnx#0sEu-0c}Tn6gO$w9^E<}k%t)dzb# z_8V7(>4N!=XfFJyrQh1{F(5r-}cv3fK+7~eAJ$t(=Afx9m>v;_&cBZL-c~X@hB@W@bS`D&CeZD=xq?;=##=dBpx-|`M6K=y22Bo| z1qou8DcvQ4xi|XIO|QD{RCpF-maEyKSOok&o*vLX-b7t1Waw0<89LR;K`C9Vr=sMu z#lDI`OI~D3zv8P{((rYBGfcj&$fzz#M?=tv2Baz#w@D=y5b#ne+SaXMh-4u;iei1} ziZUhtG`RtEgyp93ieLIOvdQC{g7NP#umeepfv5mHY_#ncIYdU-c%&9eC8 z!bqKk%^Y*KS^k{F4*(=2;BuKQAbxL1>^*gvVFvXP4Ns=oH^CJ(?9`gLJvQ z5X-z&*J|rmgZeaM{?ky|qX9xof)`o#Z)dCw5#F=_fpjHdCqYv>ZgccA8}I9>5#TXd z)U|#v#q9>8MJq4r&78|T25t8I_JY}v>)tQYG7IyL;`0XK;R={9Iab6gBw zf^8Xgy=-&gyKwMY1k0A)m)HTiL>Zo=p_%x;^0+h`qSsfi|M8dQ^1;38v+RTx_PLhk zv&lsjJh)bE?h-eR`L<F`Y=VWvfLJ^5L{DX&Y zd?pW3$E<^^r@!1xO5q8#T7$$?R__^a%I5nqLE!-lOCr>{C2N+Z)is(s5naKFM^vmV z24R<4&KIPdur$KryXyTiJaw@>=<+F|qjAzP$L72!D>%l284{^j(lZ}1@QDj2U75gO z`xoK;S;l2l5ibAa2PAj+#oZqFboa595?{KO?gj>r-KHl8ShkEWpO~5%d(vX-7{Af| zLCN22KRv_W@6GZRZJB+%dHCcuYH|gQ3wXYK@ESHD$+_9+3U$TwJXc553#0Y`htWM0JMkJ#ZiDx^xh-*N>-6$=&BWM}w{oWn5Y2gswQelg zxMN8WGghJji;8u*+sWqH3TvogbX2~aQV2LW2WfehEwf7L9~qrzFOmz2x1WvRhqZEX zjoHkH42h|FEU1n_nYbR%*Y`pcu1kQ@!~Vx_^G)LZiiF2TDsDU8yWT8Kl?q=$@!;AH zAtWku9q(G<*|=S%fZGlgj6yiE$ynsC^+o}a1$DiAQl2?c8I<~oIaRntmkI3N9JUdF zM)w-iNETM03Y&jrf23h|GT2trkbT!>((&RD(sLzi}yo>>lv7^R*|LRJ(07Y{^}qA)Q625bzDNK6O^ zolJT0T2a<2SV_<~R{Z@cRzRF(;K+ZaNt6lWr`NTq%q5)jIv4O;+7B1+e1LTy4pVHG z;@`cJhI)^f$2jf1z_aV+XnbuatFS3bN)(eVUM51~jhn*X0O^ z6{0uW^P)i7x^e*lPVY;X`Z7v|JEHSF(@rI0x>LT?$0QEjms32FSY0{C`70>QAkaxR zrsIVwhjQ%K=a{QQK*}wD>W(`6NtM!XUxU`g%#tEmG>`&7zKiMM8z?k6(+R?ZjV&(8 z$F1{YbuA&EPeON=+s{Rp@bK1-z2t5rn@JPQRLvZV@@Nn}qoq)?ATme31K2a(@0#=b zFMrb9at;?ztYznBN(v4q)^lV7HlCM#CNXctaY*ZVkNmHY;;(gOzgN(@)is50p}2LP z{^Omls?B)|sE+oFa#U&Ei6ch4SqP%fC%RuX6 zc{a!on?G}c?wG|SRuG?kJjAT;j}K|xKX(d}Myuh1qWpcP5K=>CMAbAACN6~)&);EhtS*UM zH1cD==+?Ev6KjT#gOqdxL$YK77{y=6h3BeZnf$rbwh8I$=li-hs*JUdhg%t#>EnMM-TsGH5Q{Q*{kn?z zJC;XNUs(o@f77y7%}K0)`7l*ioUw&(@N^2WORy_px3jbdp^9{toQx35bhd)ReJpEjSn{i+Dg)uLhX6z>e14 zJuPNI0Z)D}?VJ?+D(*FexJ%x^bFS}wZV_8O&C_}CIm~W)1#!pwgk;OuGQSJAcH&$D zeWVm_<4K77I|Qf_euo>!%umHmY*M1wEESeN7E?|NeqHrcSmw2gFQE|f(1Hcs*DqTb zT#=VVXA&(-|L{rx=#F3B)47s0Fw$#tkZcuy2G!g3Euvy= zB|eB>b@6Qlcg6hm#G!d>aM&ipz4_mGGU4QCExG)98EpGVO4KcAIM04d)+eV`aAZv} zWsiqHr|4=#zcDaMCKrb3c%5MEYF{iJ2-ZwYdBHhcIQgZmSHJrYv0-=qDdRj3h%-lr85XK{7=E)1Us=dxg8V4p2v7YWI*cfHM9GijC&<3d~YWugXOu2~NOQs;vj zWL{D1QdyT$_UQ1d78l(HRq|GzN2iEnyaGYoAV*=oC90!)RBz@ouxu1MF5SglalN*5 z$6|k*&lDe%WOsa@`%OVc!b(jAin6yu7f6G3d?H~DjRwaf(-5HQyB|(`>Iz=VBl|wx zOs}P-RCXmV>izqf8=3Tg5=LWQM);qNXjjTCtG-YnXlOxxonDHBN|Zg!)L#*zl)q1- z!R|ZXY;m7Yiw7>_CO1kRX%=(EYvv50N(w{+y}*$a?SQ*Sk_9K%LRhKkOiU>mDXL2h z(l2**Ny&UipR1(ruD3^_0x@vlyilu#s6-9R)C_k>fnClMv#w-2NUm;9Orjxq;q^na zFpenAEqt2n7)yU{no-5t1idra{diMIp6?3)?l6JX^4TBXSwnPqBr)~4r#IC9H;*ibG_g%vQ)9F5? z`Y}sh;z1m8V>suNz@H0;?u^{pWd2PpD~%Ao7%c+;7W@X zOm4-eL@_hn9d@8L=j5M1JlJk7;oHF;SHHr;TY|T6K7}s2vF4E-V*XXX_-&L?2WHJ~Ki&sVkq?sGn_7ovL$uIb;(1>#3PDQ7R6^w;%^oveRE}VB|vSy4QYt*Ke|R zpn<(>{2o$^;+em^lm&|jefZAgkX(q2l5viodZKvM*6WDpwxasL$0ef!EA z2xls4C%NTf1o>6ejiYr7C3d$|`Kq)_dSGuPuG%`UODNI5a>qNutR5RCCB>>1w3_Xp zR2_O=E}|{(0ifgAH9BwJZY*u)G5m+9Di(A0XB)au;@c*2^ifudv`qx)U}BwzOVySn)9eG$+Ou` zx0s3B8=toxI7KQiQe;Dh^NJoWu1&of$&Z{~TtSK!McAd+s+x6yW3KY8(V0sAENI5M zfDjMn*2}5hkGa64H?-2}hq~aunjq2m;*7u*UfjxS^1MJIZ)DwM^YYi-1|8?JHLZ!D zGg^5sO+*qSOSnxWFN7U||ESi;cl~-wN>rom-Gs@-NOOpqgfHPwVuzG(cIzI!-CR)Z zJ655yeGZV@w4SVWh?}81Fd-}29=o&(3?Jf}uvCHN7~s@oV-ytz31I?~I}r_YKkj@o z93DO#2tBKG&E43AU2ki>`*c-L$!gHt>|opQq1$ggk!oM;X-6ExHB1mxUw;_l^Bj@I zEMp!1b56?kvvl7q4XLu$?I9#x+y1nS5bkG^qMDXxq?B4{>o*D`u+fcC2H1fp!GO8pN7tR+s&$va}9?RlN7N7gGus2~y+i0x|DY zr5tl>n`SwN(MD`1AWwr8za7P_8sJ@U>7lv4X=)f#M~g3_%gl8 z;WeUe$5tzc6jabu3s?I)+lJ?4?@9p1>syw<%eK!F55+>7>u=eTc)u1Mf_olo(&Bd7 zGo7GhH~%p+3=E>UYHad`u5HCRN=Dc}zHVT=(c3#(A>vYR1MKJFweqj5lkko*J zepi}anC*;o^PF$Krn5rxl81oLt{=Pl6nR$P+3B)Fr+9(2b>7sPZC*sH0#7o{wk2fx z8y#y%(rFNt&~3&92-42q~8xMZam_x;p2ADoj}0HsMnDk^|M5+ z^lG-E=lHdSYJ%E3TcP_+iaz`p$;aZp@>mTi(~mtl+PFmE+}l((cH+OE1FRpwdj z*~5wDfG@S;oq?hv-`m~hCQIs>XR`*3-4qhFU2UQ*)QX=f8Oq)yw}T?p=U3jYN*FGb zfCxC)d1wQ@lG_Obixj&~5q1egs+)-;i}(uN_{7RBJwcS#KyLFp8N;YaqPy1Q9#T+R z+fQ4+E0X4D`z}CG3NENe^RMa6udyG+TZ9XSEmK~-n&+7#b>#*4Rz{_1Ws|lZMbcZQzkKNl=aksMN@DcfE%fwv8gxbQZz2CdlS1;by4>Q6b z3p7W)%#=>aF^do$X0;xfQ}H3Qx2u#~(<;DkS3C{CkxHi2^TlUzyt4usru+D%YPP0I ze_BSR;m?uJ;s!18^saa{aoj7pZo94xsE)bH?sgZxVr}Mg{D}Z9T?%97oH!uv_ud0x z7>+iRUqYZ;Xg<^zaO~Fw0IatU7S_UyXy#QPQM=dZ?1{+7^w9ALk0c+ zr=|QK+y)~uS2b3}#*CUGa1>m=!Eq*I;tE4&_=Pe}hnX~>kX#^9w0<0{R_(fhPkzJ? zj>=M|eb0V;L6Yx3c9hSAAYL1T)=|aa98f!K0=VatgO|@B#Qwc)oRx0>PIR=$Flq<+ z*G`G=U{&;sd?c^rIoEp1_c9e7s3UVwJ^i)MoRzNsS_e#k3mj#fOzCSZ+<>~{rh-V| znzE*T$oK$WQd|5DwzcUlth)P52V))u#9z?3&s`~{ZP(i@5H(YAAWzny=*Q980{&x` zPvv{dpm2~$hD`e$tJOeID+ExR^g%mp49H&&mH6p@@qIv-^~AdT(n+sTA0q?J+O7Br zJJR^6qK1iP-QSpI*$*0{JL1de?nh>&Q#smcsko#eprxc(+ZTe|DNORoA~BZeNIg5R z$yz2kAFD?wX%&=^vQK$(y1~@AxwHhL!Ee2lsZv=ySS+jCP{PsiG3q^49MkkxH$LpvGnC+p|!wgDCCBw7hVr z&{A^pk7>W0AT>e1tNGBqm8gjq%zJNs0PiYxWR`KL0wkLFY;Ox#4o$;kd=D`WnP7ah zE?>ScE_Mi%wtZO`L(v!8rf)DYm8bob@fD)<@z%Nde&fI)7q>}^5^zdw;s>|SE{>)> z^BShKactCI+cXS{`DS`gH{+#y4dR>2D9&== zANl%<_A*ZN2$49rHn_ab33CK>UBafV*-l*XN@kTHym{TFN5}OH4Hh#OJKXO;bp)o+ z5c&j#{nIkPFYw;f?KWwL@}u5k_ubdeOt`c_`Ftahd1-U!rF}Ps?9!f^qWx@}MY}2u zby6)4Ie)1(P_!&kh(g$v1Xpm(!K`&tvGXhFH%QoXS3tA=^$j~_v;~A;8$Ls_Qk{7e zZjq+dhWX&K`kL&mpcL@vOjUqF|L&Y&=EhQ|&xM_tGrhuCeKzuJ3N!!}C*597Q!Ta( z241BBUp+$y!M1joipLtLslXiw?KUcI;=UOp2dxgS^xSmzCkJi{=`Oa0%NWV)fl0bD zORLj|V>RqJ8$L9AIciv^Y*o~g+|e;JB!DwPIbUgcdgK{6(J=FG$~k-iMK@X_*aZyp zp4{4czdWJ5hayo!T(knNF8KbC7@EhE6gbIyyU@^$R2kg)Oe58TugN74eRN_TIi1!L zGWk`Z(x6s=*aeQatbHJ6SyKN)EGDYw#2Ap{f4zx*Wg}kPZo5gegafa%Z+5%BhmA|7 z!Do(@g4o0wn9V@YoS3hK0d?H&Cw%P*dkcmOd9mxnoFkazc2^_Z8)rw(hQRC!Pv&Z% zl)`JDscx=cTO;sy;om3XVQD+|@FUs)>Mgq&ax3wNd;$kfIVT^JKtv~&gv|L`SA&Q` za;>V6Dx$StbL!Bu|G=32JV@WYsy~Ix+DKB^l@$f@)`r* z;Lo=l6AONpN9{(-vaUO@jlT`T%>6zq{6C%#*ejKjywS%rEM`^z;b{M(Gyj7GJ7B#3 z$NT^~M&7~n$AFUmCr$ew8TtREAG8N2F|6$FUGiU4GYTP~wsd5vmAQ>Ft|$ajM zlH*W2luhK^&e)}I-v~I~vuYLhR)=p+vmfh~7j(a&FMl?p)NwNW-4{ zW@}1W>CTPa<$_{D%eq$cZF|}OjY8{(@@-hgzy!XAk5Voxo&rqr{HvSMzZi^f`Mdnw zd}JMZmk2-Op{E$i_MjD3`;ztf|BX}}K_1)K_TJoHf(NPm^uE6HY&9UJ@oWu%s}g7= zOUnlwTYc>{R#j=Ef}V=|0}Mg_5|Et#bAbR__W)sz2(MEbiJN$FIrSJQv>lr#VQ&PV zoEUb(vdWA-(-Yxn1jjWJ-rsU>%o2$Tc}Usk^1S7t3<1*jLoky6CxQ-0-+)7EiOZB3 z2{Zcg9l1VaS7r^I2si4NmEr5Jtx?-`H|OR;j|?_i0n$4JO>Sw;pBS_mJL{y3nAuh( zD1&W!k-vu?7jmSrm{M)mIMT2`)9t>XGI5wwfrByZ-P~<5Qmew}JJNP8 zz&hy5?L*m5Vn3^5XEIE}2zg>TAt@*2MY!v(aO1=X{o+WUGZGCR<>)?e4AqA_1-yXs z7*%xiU~u}GL#?C;p0r*v;{)h^UB^RADgSlA0|S(|WCg9e%(tH0@*rbd57?v9@UtYV z_&#A_Yfj?$YzGU8jK3ymv%^zzXrXO;Sik6xRH;vM+JFI(fjy>oV*lDhM|bD)!fGt5 zGUGfRV8r)T*(kQ`JVDy?@u{BrVKA{d!Wrz9y5(K7(|zAv9V6EUatoXXzNU(kGaeD86h* z_03li;J5FG@9k`5>(7UiQ&UrWAG*IO89AxKqbmH*#2*OpWy3AzKcRN$xkr%M{J$Nx z%1!Y+&Im16gXJm#oV!};4-)7b6j)=?%)T_>-M12}GMrl$ za$yEA?eQxxNq2e+`ev}WZ_hcR3$(d%3Zpp@uN@%1v;1P8=PXtvDQ38I0ym@C6>hOO zGI9l?Ym3rKlFX0MV=RSs|9xoIb5pL-*W@&9*6}S`ntDd>Ec4*c`nAsX1;rm((WTau zKF(d?g{RG|fSPYm1i+fvFw+;jnNPWEMf9X4eCd65Cug`@f+baN@MJMubT=CC=XT)D zMucjI5jfhfDeH$=8``&`waNmp!|!4(Y@ho+UL%p-ZaJ0`Kfwh^ec zn;j{&aTphtjv*=)QCiNMAS-BF$}T$!NQJM-Ll5YKC;=w0occ9)AM{bujcRGe*krXz zkx-vl?OA7Co9;LvW?1;8SKSD6Ec3eGxrL{V+V4uj3M75SItJex)agE+a=is56*kS@ z#TbBuhD+DYl@i>$+U+j+kiMlw5$poZZ;MR+xXw!c97zZyvH4rME24jsJ+Cotb z9ky-SCHSWALnmM?-)QoPx}<3#BJEodkLAYEe)c(u!C4bC;rQc-=g0Z|&gh5OIa7W6 z^h(;FM06PN!wRb{W1HXwEgP8-XbAg?0;Ocrr`i1vJZjWZ!Si{BJ$26AB_K*wlpz8! zg|PXq4*@@|x(L(kZ=m012Q;j_SdNvq3%<{$z{h~+$488^i_-hj_SjQlo674#&hmnE zicP!HrEv1`GgZ&bnn{?e^Ai7ywf78Uv;EtEb=++g)m9bN(s8R2s-?tEOHr%Tik;e9 zY!a)(Xw8KkxJT^&QuBUg!9o$L|=vHcLq7 z`Ar68bR?AaS|Vgc8|*q&?7n;GHkIYCw>ALs9Embd!jh(=4KH#3z%j7DeTdY*JNu`@ zQrwYP0Eda(-RRM72ucY`bBEo&-?|j)SUxMGkp3iaZ>9m1O~pGG<`vQ>uIASp7e72` zDB*0bc$hr++|#(iQ9s|{5;c(J%JJ(+YpopK7l2JD9QxZYUGSm<_r+c#!15$!d^U@a z+<=e`Sl>$`5R%H&Eh3jxM*i+GibsUzYZtn3I_gk$6rqTctq=~Z!I1mdA<-p!qz zb5naM{AyAaY{^gWis@O`KI&M~uanKXE1|BhRl*$IHcNTm5z_KKfIB%m+D}MrLUvY) z5r%JN-yS_-Oub>zvM7Vwj&bXKa@NK{l~w!jhr|v-Pqu>eedo+SjfnB(*S~0cy7!7! z#zy(NniuA~%mJgmP0Ddoxc2QJ$*e!{^-eZ`wioDeJ3i^T;nOg3uj}0jhC;x{0$om~ zrL)55Q&DbzZuEr4yVl?NVO(~fsffM4ve9>gNhEZWz83RfB|^Yrgei!_XMbP5&F~xx z6xj33=q(FdZQDIzK(-i*`mihWs)P_D6D;krL4yPsgfE{jbZejLNrdh`4=Arr_bL2s zTadxcAZG8i7R@shJAey#KQLs8aD0fw)T~fmn85D|#HU@k(( zLvV1zBHbo%Vps<1@m4Q>Z8VXkG)I-f4y{y!B|B%%3ReJIH=MuUr6@T;`DAFEbc)xoI7hODR5nK z<8v`tshR4RZT6gQG-@%()5dv@#SVDUK)=4j()Sd4>Y;tLTYIF%_#0>`FD&^{pv@?$ z->!W=otiK4vxs%-UH1Jw+qkbu0`a;Iby8a`v0M*qLy)Ho@+Omur7I2NrskS$^DX^6 zhsKTY?hqec0i$57h>S$1MyYHC8JvPGg!6xDIi9pDGL19|o9`0b&7|Oo+X>){#?hz- zKCXJlbn;ER;4%Rsd7V9u7WQh~K1_8Yij$#N`l~GvUP7Hr zZ0gPuVXkxZ4~wfovDeE4FAkmv@?*&$6uOSq5~BP+hg995yN&f~E=NL=&B(ZV;8%@F zH!SFs$4=(XID6QD~?m8!eFYXO_?DuvLKi!np~F@t6vH&MF)Ir)5-)YZ2l@4mc||-7%wj>htO^CFQcjbKAMV%k&x&>_Ay%|n zk;U2?JokXNk_M*KR2Jpr(DsOPo!tXZ?U4Y?3XgKcCf^V2hMgDdH~$kuiCj*<13BAu zjer6VH6^ypqli#L@zyOC`wK)5-5Jm)tzp%-Nd@II$U%AiksX$x7xE+RkLtH=WvI-6L zvIN&h{N_1%q**)l&0hzB`0@nB?XO4`)S@=uD{7ZTUMvphX}HX{`C5ZyIa32Hj>nW| zW_aDan5luK@j|ECjT1Ag)d>jQp$(}cy6JNQWovE<`hCR&Yc7}XFA%x98$>}NwL^92#j56DLHYI<(RIf1HJ~7x>T?DzGMYf_K11j z+dAMG(qzhnX3){&Z{fwCZlS6Rc-2`iY@dp2-kYrmmS5fl#`as;C@Ju!Uh9T~j&fdW zlsYB-rw{8zyeB{^md&S}=k~omu+m~BHapnbE2oima6MMZ0si^gRVc`fr31*jR$7rq z8#B*SC>SK3RYrvtyvH4pT+4d;mD<1eFJlMdg>occsB68qZc(xe#m*}(m_d6SKNqQI z&VyhD?-gHy2;D2%A-TQWncW^e6(Tay6ndzKUHC3_f%x-SLRi>bv^n%(pfBo4UH75| zX%X$A?Y+Wj6?kO|9LLbjf~`Zq=C|({c`T0q~h93iO%T~!HPwI|K`VbbqiZp@#ed3a$wpJ z&rPD6Rf!JjHPBM{7C7h;*kf|(tBSKwL9FqH)}V5Ul@6I5G22~t_I>N$pZ5HxTS^bk z4{3HiA?Yo&rna+@CKuqxEA{!>6^oc*yji=Pa6Y+s{fb=i#H$87^ROzBWA`@tula zLKga;ZHj6$t~%daVZLl!>AFSb%%?=uyDv_?y?0}A#FIC(GT&bake^2Dr4^S9d88+D zC<5KnY#jN{=Jof;4KjnW=)d50;ZaO+0(a16rR-XZOwh&BWY_;0naCH;L+WzRxb}V$ zT1|VRgI7-6Cb{K)ITihnKhp7bSTrlD$QOKLq-4h{mJNn4?1bUhg9D&-YU_WA*64J3=t-kM(?t&}QGct8uATtVm{9-b;Ze6VKG zp>0}$3LQKZGQRp+%~J}=HbI{_tR7mj=`Ks+HMCW&`o0Y2TIOlRMuS>r z3Zh$QjAbnrJA1TR^C_|)BW~$fm5f%@^R{b|_;J!QojpQs1KX*LV0rBbWk$rC?4{w|kvn=dEx*qAWUi=!~rQR>;_5670dj2NgjoVke3j<^q)RYXQJY-W|YYt#B!-ar{MJzs>ONBDz{nu3V|Apg$fBXMG+!C6@ zjZwKyp}Ot-hJ{mGsiH@#Xdc$H)Wf!|P^VztJsHn{QR=I=@h%syG5*7t`xg$%Z~**k zclA&g@L&Wy&?@ExZg>OgOqEXz2NRtXz_iG(}lHeB=>^T%6j zgoI`NpC{rh@AD_|ru~UBffIx1zm$&E_-bY$#mJ@{XZ_8pAV^haS|##X^YzNFy(W^} zfQq+(nZ^JAz(B{bY*jrxoIwwFv$y(D@XuWnyflPrr3chGXtl6&E@2tva=pF{L|sLv zo%6rGKV-dT*!tYe=-sGeyqCh4gy{O;|3DscAuG@kE==<0@UAi+reuHLbIIuk6I;XM zGHQP@^lJVKD*}+pv8`rk8IO6~U$ulWc0%`oh}}ZbhcSQL9T97PqGR2eB#iMgcQS^O zDkaZVrz85(zv&0=qOIn$1hAq~{75UrXGPBdpP7@70`e4gB7{4-Th{(s()nKif(?+- z*1TD|&S_cRID($0j!$_uDvDs^k{X~Iu&XVQiXZjSZ!N!DLZ6xN5T7pr&XR0QMgZC3 z+}Im@RD#6{CnynN@=WWC@jm?hq<*cEm=u2W#`@t_m|K2q{`lvNi$M!f3I(Du26xaY z(zTMb2wLPq>5CYtmguV61~|@MZS4_&GqWR4g)}YL(R}@!=7%ggQnWtxPdV*9PnHlS z5{V(lehD7uVJ}0==a;?vzpG|nenkL+H-RMs`~Wak6i&p{7goJr!l9mV=(m!+?cu3XLX?buZb>(ZiokIL@y z9 zMAvV9WJY6vfc!siF>OFJN2pZVD0UuIaK`~jVjsG)TzIj51HADi9qk5HdxehCUEQ>W zT(I$-0lkQsX2FfWOn-7?-%G53(Xl({&9jGwEdg(c-NoX-pl%;G>sOVCECNfUV^9fnIB*{3#&1LE!pDSJM&cCv%W4<=ZR zg_>w(etOMv^uQY(lqo8y7$-?`v1Dh#>tH&U*0oCT*Tu#P8->y1-pcS$t~I?iaE=Ie z#^&Yz1u4K-o-*74P6WOuX<)$)$5kmr^U1fR{uw3Sg`YPe0A0cc(i`(w>6N?)q-ccx zNR%$dI0-nHxGO{r3S$iD=Of{zhw?qiWmsN#|8S`eSOO`2CK)}xUxM7Rgsu)G8l_jx z7&&y5?K7=crg26RN0D-=xSoG<|uolCYm3Ur5= z=L+}m0>JB{dc}&>H+FzFlf{)&Aj0Z|P)`$BM zF6*IyIDd4Xcr7;T8cv90rSFVS{K#SAGFvk-N?hqUGNnpg0<2>%wdy6k=((Oj0`{45 zK-k~}c+iMo_bmzXa2rS#^g#3#7>+XqKv3YPcj|Zx0uG3#3;-QqdgQC=UKa)=!1D4K z)yj9oFU#cn+EjbeItWW=uM))qvoRMAl|_B-HVnOv2dhScOevj%KYmOqE2AR1w1uy= zs=oE=9DIPf-7s2de-G-A_z0;U<^qR5>Fy-$3KU+xkKPmhUM7buJECn{&T}>A`44F- zqk#=!zZkJl_tkReJXbjGS@cK5QnY?X`3~lz<8PAH?h6~7Y{lMHr<*f=(v>vQHSW}} z$%ndi#&r@(saHDEOd&2Fn@=nyFG9&MH9mCtP}au*oiX|SRu(s1Nqh(UVWZnY0a z^ELTS)p{GBfjmeUH&H@j`|Dr>_40AVaWH!oQ1>MCWC3t9RWi=JEmjkJWK~WWZRvm7&mXwk0~r z0(1(B@~yD+J=mG2c1{_J`s>pZpymQf$HT&~WDa0VJjmZKEShqJEsk_M1f$HCXIjmL zymj9*PurlK&%q@3QEgvH_ePkzKUp>29z5Zl-A9=OUXDnAVcJLO56zxz*>17W5I*xG z7w{zBAu)@=qb1Y8$X(0ddGva$j%UJwG0J_#3iR1BXKQv3=mN|%gOGBF+4kf+<*}~f zNH81$vY!Z4ZJhy&jfDE9NXZaWO!t#{CX&&*SHsAr^*3cptrdq|zn7>kx{86GPK0XU z63vkUCci+l5;jW)@s;ooV|6x6puQ6fwFwP!9#8hWy*vKL>k_57U~P5nomg<4G%C53 z#?kO)$U$PIjNM)CYeTov9edn0tE&232F3S4ldqo)n1P%6(RG1@qe2|DY1gR?;~#w3 zsZJP#-0wDr2YCcZ(o{J3t%gAVupt1SaT8P#XkH?w@EWNli&;(Vy8dTTA$^F%HLE)k z&ldq*wavEUa5Y9lD}A_xH@ROvD8ez!O%|GLg_C**M1;^jxeLvRCi^-Qjp;rymOJf7 zlHbI?7yjYc5ivb`?nIU3mNNLZwPLdT)2=GwX4TVPPwCudJBPnU~^k%j18aS$x?tCX`h* z&gpgE#3FClk+=cG07W3Bi<PMIKfe-*$l+)#v zfyMcx%5BqZNrUt=;r@NKsT+k(1OqDpVDjx1jec>d`@mH_ zCc*5f_SIyv;EA$p+cY(Ov6Ce|srv7z8c&4eM$tSW5{$V>YaPq3wcWouZZCo-{9J{* zGGaQwArrPmEQ^{(mz43?M{8gpaIUvniV&Q#ZEArnJHLCslX_Mf!qfx}-j+onCw`*P zSYz79icjxNP=n?mf17Ko-UB%V3glivM(o;*I6Q13&5SqAmcq_pf6JEGep=(!onn({ z^Ju4I3swOjcoK-p>;i8Fo_+Ni(B?SxvzD*>?^HX}r``rpt#eMb;JNl>C|`?PIk0j6 zWHyg%^Z_~%A-SwiObk;`0UEOt*qPBERwqeJG5DaN4p>xp_fKx&d%m(gKcu#At#YA5 zJadcA=T6m$rUc>J-G04AHLYV-n@0T&ti2;pfgGXh_rHLx4JsW&H`+%e!b3$GxOhyr z9IIyt`h>bF4Ch`w^2vU~VizNQcg=_(qJ@wkrWi_c82=K|HCbFa+Y_3`W*d?=t1j*C zx?iQL9+uLF;{AKTakwiP46XMO8#jTfB~?ZLG1zNgyHNz`&EZuy-CkLE5L_X@hz!jNrv+=&{s{)-(4;_tvfe0FxOofdhiU0hhNxx&x_rLv zLj^K!#V;T!c5}DuQ zfowxT9X85o+m4I6Hp95|JJHSkLFU*rUi91|zmXh1+_PF~uIT9Z~fz+Tk4V zSl9#GN>PIeUV9~!!5_yS6IF6DB|2)B9&u)8vMO*^b^_Uoz zKF=uT$)^yc_ITkP>|U`oHa1DlB4tA%A{DzhbE_8v%_3LS{FBPf-ef)C(r5#L15X`fPY9& z=PFdE>;Xfl;i0976Yql*pQGNxOsfQBtASC74~K?kZJ1t_ zmvVRC-(ufws^kEs_d)rhLeKK9Vh=?05K7EyM8jMRSyAf^WJ1m(MZ@a;;_AJIe_7?aRceUF>>1}LX1ooObnn+)Eo~Tx zHN)H!^Qy>hhb;x`r0fMaK-S|jlB+ym(t$v}JVW^v_#Ptu`GF{@b6TuEe<@b3#KOKr z*3pid!~6v1YA=MVCeE@4r92EuTLJOJ_7~jI>*)dUpoZAwi-0k1%?qN+o^^Eky{NnY zZ~?=*?Y}tj&iD}O++1Qv3us+*|Muvo+dnz9`M}P@u;f}xkg=TNHfiL6CIo2<+Z?7m z;-}J_jukYcdv@x#~hk8ae%+3(Tb+Ia_o-=T{Spy!K~_ba(S2Krps! z?=`LQu(4fev9(hx$USuAe_O#_De+-hNYNPdQP;}#>DP2(3%4RX);Mqh>#-Y|vDlg2 z+%VKRn5w(4VL(aNTq5!j*3KFvWS(Sr#0w0XRd;98s1Py7QrxPa`>XVxLk1_Ve5ho` zNG=*A!8lv;yTuCU7m}VkBe=eekhm)1go5ISfwmSWVCJ0D{dNeN;)hiZkPqbz57r8a z9<$EVOzT5EJR~<2k8am+T#M)3eeCdx@V~^+xea&HjQ5l|We|w{RZr_P>~mdd{pY9X z%j5+oJo;fCgOC4t_cs;%JCSa)qf#At*gM#LGqJiH8S7=bry-w9LoB4dH znJ!%4?IHo)+s&*hAnx9l2==$xxo{$Dq*6L>XgZqDN+0E4ajlxW(>$r$)^NgcQaO8X zFUwicJ=Q#MfwrEg9trZL;;k^!-WK~i-#MC2?V2-Rj1y-#Rt(-8e z{p#C1Z+Wc>V5uCDA4lv0tHB3{C&bv*LN6 z$}#BkU@vWc>@#z-ob;V|^4!5#!K9XHouuj3bb%Yi69}01K+!xmjlEVfw<=)5E;WpY zK-gPogKS-Iy1|ZPkC|?kG8`9S4kuoC&5jw59NC-JH=aZ8Q!68|``v$GpP%Rv?}=7WKu}F+y^Edv{PWz)al)wp}j^ zK-W9&N)-F>T=R_}9y) zH-GaTuNKIq=>u2&M}+^*(LWVj1yIs0LaL7H7FnuqwED7-y4w&;Yv=% zPT!-gv!I|C73;4w%#sp~5l+fE20z-rM@LU6e>W6`!h+?fZWFE&a9j!(w&b-*tXKCH zyS)BKS!7_Dq(BB6yx$ryI3;s1yn6kvOP&jfdm6irKN=K?udPC4y|P zH*L9C4rn7HoJI|2IB%!r9#&PiLrvgs4iTybIki~x>tLR@F)mTG>-(O02dzM$;FqBK zxYh>#ph+KR`ybdqS)YvZq5M6mi`U{0ADmG;#SH$p_aVJ~qr_ssgg{RLi?~dt$%-5e z@G+GZUXu^fbim_u&pU>-f!Js4v$wos3mFT`pU5~7yP3ji9;yz1uPdHGN2yJyKp{nU zmQ*)+ta5W+x<(mTl($$ zTUQ+%QkD8$zrIfm$7W|UA!bS*9pBQYe$%zRs^tAO&#cJsN)AU$|M_Ec!}t-vN!YUy zqGfo6$7l!Xx6{l*cz<_gn?oX``TbBYH`rBZQx_#?d$vwou`teE2>7NB8M|}bn6gq{ zl>mI~qFu0WdhNzRG5$B7h245xDjDKlZdav2P&n{~n_R7|XK$1%gR#)@M%hR43s+az z8^o-SA{>}t#|g!pC;$A6h7Pp=Fo75Uzs-x({g@9H`{~c)lGp%8PXj_D!eVEQM4&>< zwN0su9mxd>_}u%<{-zc8e)v@!pq?bV5)Ho8_eare(HgioTDrmn-C&8*kV-R+AL3Rv zlY(@b(v%%?LTj)zB%){17Cl<1WGwt+s7Z0VwPU>NckA6S%EBa6O+4K+>-dNcP7~ud zaj)xh5fBA#pw!jRD-ElS2ZTEiforw&`N1(l3;#%HL&Iax5l4s_+E8!*`Z)dESN5XA z-3}w!Y+^nCC@(p>|z|AJ%;?Px7U`urM?4Eq!)ZC(g zzh&YQq(ay{0{(z)Fqz#udmxRKf2u4~n_RfIxqHYz5++wz;^>_M zpVATZcAdeC-POVReiNz~LPR+1LUKj8;_LZUaCj>E9?`SCQ3&=Q1ibv}zkWr)-fypj zs|v0YI^tg+LQy41O&YoPyQ9(>W6>z3=Q=rq~g(R8Ae#=>iwq@MzxBk7n5!!;*z<(@FZ93)2doCOo zU~gETLk|kK+PubHDId7HwQhpFG2GBMj)`&-MbhiW&clD04CyPQ0ZgO3(NGz^Qy#qeSy;)cmJ7`x8NO z*8?d#ThuzJbucAgA9MIxym^01>uQJ$62=%DgxyE29VRmHf&x@zA8ZaeQ^HTdk+&wr za(n7qjkC>aM6{bbAjJSRb;&dG<&?n3%LOE>Xg6~UUO9bkx zUwYdv;R30%gqGdhkjU72@f45VXu?*k8E{-u_qRIKa}ykWML3?eHbT=V&vHf%u%KuAyB36(OZ6u z;eJ%lc@^45wsQmgxL4p6aSSr8`8m>q_4}L$wS*zo1|T3lIk7oeqHZ5D7zTJ{5>LSy z9!LU6{f49?`S^w}dQU)EP(qo|~fG*d9+dX*?Q&he?6J5@skE-gG7VV9=Qu zlgdRKIq+j$zr}e6dIK%GSy_i^WhswDC=nNGK)vz^j^|qTBdaIR0A8 zny1-5E)5#mGUL@ds0kMN81=I)@HRHLEAn#=ajMRM= z7-gD|fR~rNe|<*_a(`)NYbdz(-atd(Qk>gH1O9RleRrAA%@{ksvX?G@ZJ}qWzD<(Q z$4E%EYY;PiM9Qq(d?5=Ao*c5(DHb;XyBC06$kjAo21>lB{@9Y`su3c@7VA4vx|NvQ zeV*I{qG1BIa}=T!AoSTasia#%$ESZ3bXWxm|wmxzf(nk zRbeCp)_H^DlIoPK$oxsb4qewhEv$AI@a~|XH>Ci`b>D^X3)Xd>JoQKns_UA@ejh?B zdO%*b$u1j`vb~?og`3>570{O%@9_gLZ9>I}IjWZ%UVjNFgJr+Dn@0A@5K1b>e1w21 z!-xP$mqnGP9S_!zvk{cr=pYwB0?;L5i%^ka^X=<^hXo&%RC*<)Q-3A{Z1v*_26(9= z6Br$rBqf3iW1^siCnehE#Ir^c1$t&D3}Ka-h7KUHQ%2~|cHWkXW8)NQ1#h9A6;vS_ zRZ@UWx4YhxJXU}%=S?IfHnwh)2Gi&L*<|$bEsL#yoYS*^KeVEZtmu$>mY()|ab*0w z6aT?33xnnZB;87t#?cxbi)IeTi$a8q>PkN8wgKMYmc-0UcmAvEFm8IKyxGe`O(_MTP^HolZ0TN9p)M(W#RSN!x0K%_orR zr4_YYIo_B$Cq!i&6w;eV+yjp6yKJlNrwboAvyV4@2)N{ZtI~bev zx=}-;DUk~hOZ|C3Bz3txV(ae@OG;39_@gsQUyM!IB`0gfNF#&O-HbcFA8@&Zvys=M z)w8JpLpwt>G8>v;B9y(Wp*?RR7aPm}<2ml2#7bratFcf18QZ#YfIa#$Qv$-1mwOl) z^Z=h=GFS_J;jG3Nc0kg0Z=f!iXe~*>@mchBBtW(v_+|Mje9?a==A=;YBk9-&$XNx# zsJX;qF+b?$T=2tX7j4E?Mu>U-Sv+T^2JYI~{a?-IyJ0|~C*xx)xlUux9>3*0N;DX? zIeOE(OHImH{5^xih`I(psOc~ND(fG(#I>uD-j{6LXVN=ye!aneqQBU-=_lo))BM&l zS(r809XMJm3%#VVHqpI0aIQZa8;$0{aOFnX2 z`2`N;?n%CUH;wxEJEe&PIaD;cyn8|O&2ZdfDp)wQ`wbi_HIJfWj15$gQ1E$josna>xEm*g(G*%wt46k1PM zef{-_OXK0o2hHqJE;SaDbwfgJ9?-HMmh4sJ-oDCMlAzGp}p#ouQGlZ%tt zIZY4rQl?Sz>7NrnW3SW-mijCja~9R&VDZAg+@Vr-w zK2*-n&zIWu@N9aI6ixeI6SlFjF*gffglTFuV-3p4x~n6 zw8&|%3O8dG!M=aFtku81(J|A_IDLJDIzCGNWw|;dIqaz<*NL}mjq-toe7N!sw-R=0 zr#EGNC&8klF_S}zIu>0T1fApYb$O4=>wl1QRQ~#0;fEA#kj=VkP6Y1JS4) zYCB5s>+h}1lP*S>D?GcsC3ai<_Z_6CnlY`;#?iIVnq`WU`RV2d7V}gzKAm4YT3D3D z1QbBUJx)IrwMM#xfjy=WlIKTe)vZb0E!wNY&iSsNJz==ZUj}RsV#9alXW!InzlWqP z-7pYpvfcO)I5eMjoyb<0nra<^>(KIhN;^XF&+p+OB30Clnm;LfDU*m6CY@4&9g#AM z>pZA-%1+O#XQkIY1vPy@K)>#Hb)p{=UO&pLElu^sz|T7SmXc%h!dWW-JEA zMPff#!N3yWQ*>dYDy!5%gAt@92g-|@X7OV>Fr3&7szhpMM&PuITQ?Gw7djw#-J2?M zhMay~tfpi#kNecm zIj{;^vV%?*KGOT~(knZUy*OeoR`hz^6WEPQ7*5z~&9~?q>Xe^feA4#hr%GO#@Sp+S zgRaX2!-}#Vc5gH1npC!1f5&}~#Jwo1MCkbX{4%fEF6`Q& zhC@5%DiU_qa_i=S#vaSi`Tm6n+`Tw9(TDQOYJJJ|(_H0LAsHp&nSPP2Gd70>uz$bg z6^tUdeT1hoo+U%%!^yX{3Gb^SMj~*JdBPHA`pell>LP?m?)83NzVinT<$x5O zJ{hkT?Su9|=s!^90HqkjH0S2H3W68W&oOfIwMzH3WwT(XE|Z;}Cb|*LVIR^4-JnGT z)bCm47v=34EFa1>_`Ru2NJvN%a&=~=Sv9^^EKw3Mz$3YgFP<})J!4K?`D+|T9ln6D z3oNWsdYk>N)F|g{kPk#KFVX0LBwFW|{^$mCk*Rro_LGg2YXSo2ZvBq6_jti~@3f`R zuXlUf5$5IY9iL*}nOAB2s$ZLQ`$3wlF}$^cV=E^0g$u*giGoYKfhztNO44Fe(=#_u zS+-VrQ^zfs1B>o89SPr4kzrb?qh9PEP<_ADd>+@sYIysU{N(K#8y1g7{IK6h3^v#% z&)|cE5;{F^J(;aK69slbJZo9_Ci}Cpe;O5+e4~JE4EFsdvuo(La?{vbNX6Z@C!sGv z=wPB#Peo9N_w)}1(xHyRxOi!b?d!iU-Gr_%NV-6G)&@i)jQlOZtRxPTjNOFos$SFO*ip7qh`L3 zpCfW4E0YtqH85ge5{5A9|YY}=H&DynF>5}KPhcEcm%!VO5 z$5Sva{1^NhSu=YQUZtiQ`V(mf99kWk4amE07uqNxrqtPzoe|yE4(bR-xJe|~tmt6b zux2OZYM=kMFYjj+xWCrbz-rZn=jJ1VQkzAIUkm==1yT9V8Z}F)A7vDS4)#Sb?6u@> zEVNP|9{-mT)7*Y|Y@$f)erdq+hDh64ZphZkeO##_ZL6Pm4yHmw9SKmhN+0>=e_9vZ z4fI)Ov4&K7jN&KfqS%;)(Q z=bIrShivTqT9fuSi}hw<+yY31RTcCsKY^`$XXytt`L9S5d- z#*!uraO~W@)rHVMP5Rz$rMRnqDVjy(q#go*d>5|5u zrfTz(iR$z1;8S#AaUs0PvJ;<&Y~Ioh_Hh^|rCfpNZEa&OI0Hh`cLiz)i&`Bs`H22W zzsETLJ+Yb{6|4CO-l&fpDqVXUc7$;5`SEnQjlwnkKL3MQo{!1TgVYZ9kgA6%2bNEZ zT@)M2%52nK=OPLM{gdZwuDDET<4r}t*uX4)mmb9S^<|0$>;TPAQ%B0-)zj!}h?-rD z%zV{s5D1J#WCf(qOD5^t)SUs9;Fb5lLa2WJ+;8sm!Qmft$AD+r^RH{oS1+*7+w@=A z#~-ZXp1)bb)IGf)dw?D<7>FEK?r=rbMhr<$wJSQ4__G(TJR#^$=S)o@ALq6P8<-lC5 zGwfRBM`|H0&44|~?sYo}(tK)@m@@v(eAedt*;qV8gd_G(Xw(i^BY;x3RhDD{f|dVKC@=k20sqbmoH%J(N{Lq-jth^X(xHql>S%UL0D(9eXmY5 z?&G)>_|ZBN_mWrm_lnG?y$JJj2I|k8l@mS{K1E}~B{uLMYYNYiBZJ=TcW3D1^EO4$ zca+dFMTkAZ;>9u6@v|bRtqLm@UXF~V=-7p~;eS`j?%T+Ff?0thllr~V!On2cQawC| zp@u05P=lLCf{-V^Kd5~6jt^PE9Jr$PvWho_k}&4uV_qr$Q{4Z8*Xo((qGy%loRN!D z3nyCb(WA6W&6~IfF_viE5VFQSq^_bypNw<-b$7C|QE7!zIkv z^Ua=lo`}No3LqngD~=rLt?(Zo)nkL7mToe>$K@9)Z8o<;{?=kiSzw$@*p*PRZl3(< z^!(%C$Z7J&z}b%nJ8K`3cGjk2=bA6^`uE%&lf_QEAFL?CNLa&lUKMIqK+x9We2jqa zA9vOpi|=7;O-vb-;BXr3V5eB5gX3)Q3)eKSvXuSN_pj4qg$^3OiTI)MflGC&Q;iwU zWY@7ut3*?hvcvSED6CX@n)hnu=^)%Gy2;{tYx0QklCt_66YxLY5g!KQF885?#&q~Y z*`sw{;CDD~st06N*<{Z4uWb-(i{wCX8b@}8JDUa0*%eXD8{#qYfT%2(}w3ZDz4 zv<8|BzyFk1vRXlv)ij=hJ)lva@BUwo;kR0-0 z#|qlJ7tI>>ak~WV1qs{dl3gjMzo7T#ami_?ppW9y7?t3T?*xz^pRCRw%=;@;DD6GE z{%N-L%9$#-cHcyB>e0c}dd9UUI3w5{qrm7nm+&RcGeMcqu_}1_r-THAiPTL)0{4}o zWu}(j*NfZzT4Pdoh(8@X)*1J2S6=NisSm2UZZCf?ob|@%KyOWt%^`T(enwfn{}vl2 zF@5WuDS5YbUW6|9+*Nw4O?7teGaX?N0X{vy4Px#5h}is*c({`D$z^KmR~YMed^pv* zm-s|%GRvBNaoQ(b5V>k`z1s3*2g=flr7JZbZog1Q34Wb!Q=9O~y`?x=4|7!U3 zR1GWfG&hzr>SBR;19D@KB^n5;(6)SUwm3QUmBEuSUC}SBwW($-tEwpz{HQWkJ6eNC ziSl#J@SK3H$|0@fwAjdq(Egntw->LtO#WGnOIp_GtNXTp0s(KTxP(9)I9T-6Ij>3@ zRg|@`VW@Ts?U>#3nEHA_otNdIpu4w3)>rhP#2LfCTpoQq|KmvSCbUANyi8$Cxa{`T zR)h1c(!M|?>!oDoAA5dfCFN**Ifc5xy;xl|JK=vvjuqI`dvlg<)ILFCOa*;@ErusM z&abl0sSi&_r=63x2`F(ciZ~K}2fFLR)IQ041v5SU>urB;@AP|vfF~StsPrpQ>`+`K z*ihdT3I%CKcL7cDF^_?sba;igqBjB)AT75~QQnWMfbyhp%kEvm09w)cv+^Gcro3^( z%&tCHH$PG}e<_`1J4Njbv`bw>IXpi|EO||(?5PLZU^>mf(3tDq-veBJj~LH)C^iTs zTElKn_&O)nwo8whV4-^kYb>&F9mg6-&L0cU@GES_2a!PHD4Et}! z4-fwd^c0-eLc_RUrf9PGNgv&&OmKLf2`|TDT$1O-ba|f5?#l?dFa-4 z9+r|NH{%PF7HRM=^K3!Is1hc`{;38daCKnh1UR5LUt;j~Gjl||H9GHe+p5#|A}NpH z5hXs#9FyTXx8{!Rt0nJWJQZDCUF+wqAO5U7`~LUkD;SaT%NTKe7MvQcf6_zBZ~B8! zI@=Ko8>G*_&z-ex-$E?gp6WVQ9$;eq*(H9Ged7DAa^V-!N}E81DwiKE=TkDWp@M?M z8KLYd+B=8DYzSeYI<*wpWUbo&C*9@;0;rq>z&yu9SP{<&E zkaYQAK`~U+)}n)B;Xhk*=QqVnu<>*0-C&dVICI68{b8G0wI46L>&u;g5nySIZCCp0 ze4vF#hoO8P)KA``at)@o@S+b`F$n|;0fSBK2p7Kp);JKYrVc%Aa`ewwQWT6HF*;@o zEWKOO*KUOuRfmbb@{#6mkoW2iA}@4vO;4hE`>DqR3EV22d)yl1le2q`Dpt={=1U(W z%?zUuW@E_~nN?y^ZqqOI`bZ8ra(HkYnCs2_cKj}4O6`VhIanaBG&FUdG#s@4Qyfl zY$aSWcxL$9?`9tCIVE^ z@2&cENsgSsxYL@8+pV;6<3pb@k^XjJ<&gC2!ITtmV@hxlkH#eyTX2WRoOEY=y_{Kk z1r2~g_Y_Ttp@z#$53rwa396Yndl09`*e1f%GpLkLR;-4NwF7#9`XPLo?Epu6C z0#)p@fD(=%aWh>gbYubj*6y-jh5^8)AyM zBi3LzEF`pjcIYJiAzew8D@aXPDI#^U2O|SjddBgc{5hb`O zuvNd+Nf8mB%phsH|YxW@NAM|(M(A_?Z*#v@s+aY7@A8$yoDa~U? zvZk#qnqN-1>gdo&09QQW#0oe|aieTZf)Zq9tZmf{h%>zM#cIP^#kL+d18%+Aj+F?x z7$@-9?e6WfFJMY{el=6*LOURKc2Qmb@*$(iGQ56KpunMkx2 zofkV9$I%dR;dQpcA9f|fOYfvV{7kyu`X7?khb&p-A512T?VU#7w8rfznA%yH(_H5aWeQZO@u4rT*hOA>9 zF=p(3kKXtFem=+X`^Vv!Vdik#i; zRt5uZ_#?I#fU`+$IF0ybBhucq>XuH_T6WW2xW8g)FE9%uNFg*Rke z<3q?`noP=l-Jmb;^em9*4T(vi<<>&wc(g1J9C_3e@A^YKbJJ)`fe71Ra9{ghlFg0!?f7Z;_u02$N4q?2Rq3xA2#FN_qU-hRtQ;-FxE6^hMWEPK(&V=1;pMCBnS>pbp9cc0)2PM?#~5gSO>U zB!j{uCXi&=#|!0WC>}y8FY4N@med=h6fV(B%wCu0H2rL8Xp@^+QmW){q?1FBD(}Cz z^suwUaW2ZS<4*s&@5J$$-WqUoj@iLi?+5DPBEGU7{djiYF??C}=0&dcpq%9AgIE9Z&3Ol+=RjUu?v**n+f_6r=P;1ZgDE*=>gLOxV^F;~ z|7$I}xM^ed#$VrgO>)SybAc9}NliX!r6xLq{PwKMer(vBq6WQ&+^07Q+u3W=S(An( zQ_95I0l7Lm0P^-6 z84e3k*S`zD$Q2gTKR~_Kck%J?2F!&&6Ey_4PX)=YEcZ;QoJ_#Ay0e}oq8Lsoj%ipWA~SZIqfb# z)F#!b5@GvC7?H!O(fMJFAhttCm_=Z`jmpkF&*gpPS z(!me&O852jb+7O;`z0C|hb8;f2`lBShMOpg#l`+mhATiA`oCd$ilG&zZJ6Wpdvt{u zCL_m%5VZ+@w&ceK%{G&Z6SI;)50cm7HVQN)eb)4Z9YB-1{mZx>gB6we$T^pZPnZ> z-qS6<*^E-j{j6wgV>^jvdH&0>q8?=GF2Z9}{ZGhLoTyEZ*7a(6m1*gYgPhvd%ttV% z!(n|K;JkmlZKr( z7&>$P-KWG)rXg@(G2PW)F4Qx~b;W2WnPTj*&mBB(pjqiwnyj907+5g<*)N^Iz z=eoKP==#Tt4{kgZu((#MUZt|2IYQlyi`jeeLAsN_LBF{>)oHv#~W;-OQB}m zSsA7)x;?ePqH^B+X$+kl@K7UiR zE1dt`zOE=-;mXL77zdcU9v%$VXR{)m=o5Cht+=-ptlQw){{Bgv7`Jwn3NwXhm3&nQ zRX>++yda#=n+$?sxeroP6|1?z-e!%jgu};EEw~W9s|pa&^Q9{tvS^0Se*P>XG7Q7J zt}6dY3v2&9pobscj;RkL(^p^p22#hLFEi-to)$Vj;sGeV-c@^2ukUv6LNwAj@x-r1 zspVw%#a{A(+XD4_9HLHbq?^0vI_#FoH`gO&WMZ=su`!Od!7alq-TYQQ)>F-U+^ML% zJgKu&r95-(#j~_4N8_F+($=mOSJ))q6z<#^++`Fjvx}okJh0X`3>tc{-8b=`Zj7og_dAOCPw9ChFqgdH zo%SO59o2;uTgo2ijV9A5PM11BbmqK}=IaM${eYGkLXbaYw;1tzP(g%{f|Ei7Ay8sv z*3~|ac!@p=FmpFDt3Upv-XEYXe*!mIEp*rafHI0;%*TqpdJitf;K+aB$jC6R{Gw6S z+(2wgtMTs$E9#N{5B*d~W8Doh{iU(Y=aZ@Cne#`h>9ZB{!;?wXhWkqi-EkSU*(`ri zUtTpn27g3|*N&qVs=xL7)D*#WOTBe#J?(iT^glbQT^6HQtyopzv;5{3>&8o2TQz2X zCT~d+!mtYvGK_1Q-KYG9cUO_?{CYvJuIHgqi}w;?0SesU^oCpGjj0o@hWVbBY*?f0 zZWTXsHEy+^L$3u9YLK{|kV>Og!x0Bbov!ql{yfDTG9V>OF8cLZ^+%0Ky!FZl zvQHmyWSgS5*w5qT*8{txANQvOucF_@DB(*7-meeaROP+QXhzRkdAsaKxGIvU_kMgE z?IDL|@^#TYQjKC8(~M&C@wgQ*_t>@v6R@3oKs89X1Uv$EjjUfmIjHFr*F|8u#{ICj z@xz6C*uXYKOq#^x9#r>JK%viUUMMRo8*lAXD=CL_MYxNbIZ`0tXB;Y5J-d?kQNhqIou)^ z;s?>$H~+&*HZI5DeLZ^-B9;>(4bLY!Nj58#4aeW;MBcbYH6QjWO}DUpZu-wgma)V3 znh-j-FxGSeX%Rg%+v~6RvKr$SXzBUW#Pz#*fczTw`4~|&b5l$MKRnt)**R;*9f{0d z*Lrqyn$xFT%R*G{K~ujX863syooiF@!gg~Q8O7cWdxs#!z0s?4Zas7yCw-bf%Ik*q zM2vcFmEca8iYgm6ZOM&aWY~7^B7v3|~ zj?a4{o<)Q-u_kJG2X!x235!S&E}0m3RXz}B?H$T7b!Mj;YVuGCFcY?_HWQYy_W1a#}% z(UQ$UBCn_~PXRZ_+%At4B#&Ele=jkA{W!(?-QSc17)fE`6dwRs)S1u$TYZ@uoGa)f zqOVmAADsS%)^V@Vns&IaMx_R;o*@LY&l{WA%n`YfKQT6TE%o)H?x>FrO)tB3+ah|4 zv_j+kX2WA|b)LInGvCbak^C>L;h5epp4+U>kulBGN*gj+PKl{6Vr!uXeQS{g-F#`s zs>Ni%cyB1#d%^#>@c!ZSZVcKhBLVspqfM=T?Y#-4KU3m`D!vfNd#@o+f zsA?XJ3j9{}xw&Py7gwi5xG?aVWM|Hz?>8a;&PeU4MQas_M_(EID>1Gohjp3~W^9Zv zv%dY#ngxIweoX^xTk%{=eLG;B;Hb1*d!OL|T@qCR`c|69)gGIHOVk#sz)zY71T!OJ2Ly2<#2xEhv-4s|J02 zeZBMjWR3a2HR-*Dv0B&3^PmNbP{G83odbg^%JR6xWl=(@rlWxq|7%45XELn%NI}U;fCJihxRP^o<+f?m zfsq-;zN-e5hEYN0(sAY4#=pTJ4$>{X3qB1VxP~-9$-N^Jcbub4s!pm z1$;|ET1!8PbcCMik+RWkE1nT&%wC0!|NOEv7&NNk?dZ}MXdMA7jq2bx@hLKGL|Llw z#9+LhbQPaU+~O=Sce4^ldZbp1t=9t0UHhbzT|2ka<_i&!?h`g*ijUunZQSsQe$Vv^ zE#RqMYA&Zv2Pc;L1+4m>A0?QrEpVQ5kNc6W7}|W2F{s1sksb+TNiSzwr;d0Cg4Ge$ zYBQ~K)4j+TZHtOTD6 zFXT=QKYh>GjRdMOqw79~uGNa}+y|?yFNdmLJ*;OA9CVcl!PEWVqhby$Yd*#xWk&Y} zM+0!7>zWs9X~S?AKbz0{WE>2LlV_-B-&dlS82KZvjixZg*H{PQJBp{$oEm$WnsQYl zw|I~=u;xs^{bk(hyNgT;g4A3YPaurw=@riqpAy0{%m+fmvH2)Bxt#MgKYY{r3Ez>7 z96zBf>?aeo+X7V|2Wn~9^MM!R+VcuZF%6+U=3&pO%x$O z`6n?>?5>NfOOZ9*+j-?AH5MZRcTAHntEN$wjx?r8O_uZF#?i=&hV3a!Z_DGS<6+ZB z+Go1W9Dv$QJO~6r+h&)fS5B!oI+m?XJ73}nOFK<^e+CM=LZ8}!dLq!?p@IU5J$s2F z_L{ZQ9J|hN2g0XkwHxdW%p&;}B8_VqFudc!eZL<-^1P5(reg%%DY(@z2^V67LtaADN z^}TK2OzP}t2!ke|{PHItQhko1rUCt`_bX$fU&+a`>!i7DUv$VJrcc-p1V7)!7u{%< znJKE?>GuHBXT^!;j!*XGftGmmf2(IjqQQAqt39YHqC za97b`8SVQqrmP77#Eb&NY!l}`YTNdVg7AemPVClIHCQR0P7}t7L3DBNna%8u6g63I zb}hJW>9a4?Q$czVtEPT}VmB)~D6Pw#D$imfY_r?Ms4X1OXBodUxBRK*-<)1Ozm(j) zzKz<~z%fQ>EKCJzq%`DDFx8HI&3>lcSG+|fhrNTMTxqE*GQ&-q@kR?xZ}@U;=j%g{ z5u6R&E_5ISr$xgz>~Ro~2Yl6dDtFjks$l?fXGYMu-%_0d*bC;O_ZiVxs{Y3aWpKY> zomo~uM$q)f!raljJ{P%w{RVa6IG3Ts+mJXbM{P)q0!pz=sad<8HfLaGaOvXz|w;35AGAZPQ0{%;7%feXdpp zAD>&JfnA7WwTX^weeAmE&y$~@k8?lFZ$QhhnTCzW18%L!$v|BzwERNbcEwj{2eQ{Z z95%?3avMGnN4Nk(w8FC{4}N_b9%_IpXhsdqMMb_(;98cRZ=&M7@asr z4nK(u#7EYKR`nv@TfBef^ms^I(_;i~k>CS;sgf7VeE#I5vT*?1AwlPei8 z4xiuGNn3`S6ppOTSs>KFy|^&;zUKXyh8Q%fZww!+|3|)Ltsk_jGag&+DSysl0r%CK?5ieYt0XDyK-{pEdi@j5F9`X%^ zhQSc@j+3po-JpyctVb9AWSoyrc=H+GVd3STMkxUth0vcaDdLR*&;Ranj!RF!L3mW> z{E}dikQ)Z;)++TfB>J4{<*q5a{3xTkrCocea!iDnKXz>P=T+tU>(ZHP+ZCV$tFyWh zjb84njXA6@PtNSzgR2XOC=+jTTtSPDP@H_Q8lEqrx8OVR0D#lL!GPE3mmTn=;B@_5 z%}9JI!(4oU&yE#e7w39Iqz4l_vIhGyo|c(YrOzyy5ny^c?|E@*791gXJjG zWl^M(QyQv+souCy*z0WF%Q28!op|qj;7Y~3@2lab&cjXw`OSR$;3$0Xcl4WAnWOa9 z;JVizE3(aSf`#!#$_MiS10Hn_OK%g=)!{ZoJpfy&VdYKt!*tHDy|O6>N$S3!0QJ|F zCDyH<^(_R>SIGs-MDuzC$aIxtgigx<_crM$hpAY<$R!gGY&t*OMQInpm-ospAn(Xx zuvH?pJH?~c;V=~X?*d$BM3>jlMU0I=4c^5+z_5EyAHU9GjsFge8wYRzI;aiNCx}u5 zs5cuxG!~bfymS$_jzy_bKd%7_K}yKP42CLxn?E#|qCtJ-qHyiRFElGyze66Oa;ROz zez`Q|HN~Z~4l>MN9?_>`8-+ZCjAjF0=0LKTl)-ipTBg8x7H13kItGaGzCYSpW2B`B zf_rT-+dQ7On_yGX5uunls?6KdE8B=^8Mu@_ab$SdJLkbj@z3&WrIsBY6vlh0mzL9B zVQ4%3P4ApMd{Z2l=0$8-79#S34_+6MDI9_7 z3kySfO!9T}bsB?&SEcoaDJ7L4cz}jsq@ez!tU*oVXj!u(65k>DuAF2;XbXouTeUMM z%dTy6y(4M?N(i}KmttutLDidwE|njAY|AhCBc3*G;n_JmdEr4-4>tYFtZfj=$lC^J zzn#e<_UoeX>Fo9^P5s_|Ml@|z?`7azHs9ucPXgZB&oS)RrE)l1+v~MIe*+{g2#hkT zvKnlyD?;$PxYH!w=I5u!uB&CJI@6s9kM*+(OtX4c%B31mGlEYKUeg-~k@?TGm{CsV5Ms_Gjy3AH53Qo7IYDxNapY(>>yEZ>6 zshfvGg>Oi|QM-IY`Y!5;k9<_~k--VriuQ)*$Ov8Nx$STMit;@BVQu9sAce|VLyCK_ zN}9UcHTU3#Yox~t3am(BC1exd>Tyu0&BmhX-uia%R#q;g?*N2SU&kc@g5;yUDSzp< zgN&7<59TW0C-_YiM>GhY;npjiGF!O)Vi(+Wg{my|ysz7nZCzNc*`vx^o6V4(>IlzZ zxF&~aIJr9`YlGBY#s`nNI z0f5QWL;%j91L>pfvt#HDpA_Ai*Vk5^FDmGg3=B?Z0gE zG!*_DdC|+C)4p!8fV}^(E_0Eyggj5yuR1dmI6NmAxxB<)j@~TYB%as%MPtx(#Yp!z z&7K`ak!6>>Inl0zzkdoSzFq4Y(NrQfaAJ#nqw#LQo zr4w=9$=ph(y?+48)phxyX3G@$jRhvczSVz|GNszC=(`!Jo3<{%*r*v+yh83JiqU=@ zja{~9^#b{((XWyB3GeZa4i0No2V0#0<7EYG_LxAWC3M)IGXfJd{mnVjS_<4}c zy5bOVjn{_pmr`K-{#0j6<3^d$+01CMNGh8#)u#DF2ukg_P5gs3xx2oylXp%;EE9d| zn~nKB_=G&B3h#Gn{C*?sIwB6l0HZqs_9hL?;M-hbp-X-B%0;Yb{Rom;7Vm;!jwk7b zw^Xaj3Ol6tFmQW5m+pkOMDswy@z>1;`?itISFj5Rq6!ofF|!-=LrhOs%J4<$NvxJR zqPcWhR>xyVtmg+Ot2b2o9c1U}K(eXRv^@24+bI;+dmO6dTywbS?` zoLM&)4wtRrY;~1$V1GNOqy(NY2`j<094b{%xc^;!Plz{tA>cjITTvOdk_YR~Nqw1Y zx2EyiO`qm>pcsVh=FZ0ew5jW;iR+(VM}V;Mk|bZ1!2Lyn%85y@J=x3ZjS_KWXz0x{ z+8fJM8Xi2;Zp0kMU`Qk#7(Dg&i(f0J4fpJWmVg#$&nNAM;|8dr5Us7|$Je`&UCv2a zGL3+8OBizO-tOxCyLu)WkUtg}x3~|8m{b)R0D7^6D)S!3ma)N{#>NS3-?frP{m5|K zl5FH;+{{e&x2^qNng(yzSn1s!kMYq-xR5d6{j2FM;_o)kN}C@jwy@*cl|H+9+`KCr ziIAPTGaPx4m*bJ@AcW8$iI>Kpp8)n_IeOX`O7Lh`c+0#|?V+MVdpNFWZ>!+TpM*eu zNuT}jd+^LP>cn!`m+p3F5S?cDTku^v31WUYU~(Y$$*V# z9~wr6gRFF?wd)nyU&)}JJkU{HI$N6V7>(ayswMdWW2M^j07EN@u+Ez9)uc5&=6X5M za((a56l*tXKZM5_(1OUo6@9%f5)l}EUu)gAl^J8W48!|rea=Q9KsMCc87+AE(E6F@ zr48mPR!|By0*U9?O*u55*;!zIEDY84mv>6j<1UbC(%tvf;YLwKo#3>Ilv`w9*1q@j zsP3O+P3yre*YOsK`r7uwW#z5f*ph{#9Yiy8tDN_nNS~#a1=ruS_BIo}mg8tzsKM^E zMKfldlSn(Yvbq{y75(}_P8e={53!&bbbdfsCrQjDIDO~v4uKZy0^vIBZ_PeJ_c@y4LlUxoLjALjIE|W%Fe3?7bq!d-l|_1^M<>athY6oX!U0N#-7<)J4K)6}k zlH>X1%HBjFbDaKFAFvUjkRGV)5-2ji%WxkCJCrE{;NqnR`MHdbTHX4jpq!s|q#4n* zaF}sRijI|2_}c%Zylv7a-bIawT%*~cwcwr2C}mK z?$_GVfW8Hb4cKBBkijqV1suNlZc1QY;yZMVgm@0ALKV?Gth*biEq>`X+VO~WwhCAB zNDPsqrG6;B`IDD(?uWxSC46q+!&|6Ce219NpRE%1pc95OpIHg9CAL?)6Mxap56Vkj zh$Ht#YvlPqRc|NJ|1Fg8c=J3V;#`h)1`m>OuPNM*_Md&+NY`RS?`ndLc8bS<9=tWwK8J2=v%%&|x=IJ*LZ$WKL<=$U@8*e9`J z&KnVKuLyx&dt_B|*d^0vjp@|8r#;((-evp3I zPHUtjLIe@)^co8-=NZR;xqNAYF+c1)aG4A$X*NU`ozs2>z%MltCF=FtO~Yj=>)$Nn z2F#OlUYbwh`Cbknob}(~IV$Uipctups0GctvSVhv5UtS3AGFBX+Bnu(Ad&&=A}=Jt zVd~3$tySxAJcaRc@H+d8{`BRed{p%(Ja3+(2#~1pPTz28OqW~)>lrZ}PF|BEI5zkk znpYtu?>gN)!5i*%W)yfsAJj}Vasjz`x**@QXMXw{0yE4{az$_XsMohD$2#}WVYdUA z%U~54ce0$+&x0Isc?|aWSszU|j#T-R_I^)~bl(=;&XZO@*m?CP7yw6V;{0$66I@l5;kf_( zO*i9T0z1MjJ_4xH$Ko4Kv4wKi(2w$SASp)8d|y#m^A`c`Z^gjFP-csvDXqCp>r8?@!q zlOVJ^mk+BGefgzn-BAIt&S91{yDb>~jYk4VM}Bh42Rf*_IN}?(W$I7;%gefe}Zd)0Fl`D9!B5LbhyFyLZ=tYLTD# zi!$q}mis~&5LMd3*2UF@yaI7`NBvdi@@*RXdCj2Y-uZ#>Jnk^4#LJ37Hao&`hodlj zBkD`FDO)$qDKtF3L?dlq!(J~P!@T8rl=R@_)1r4!*NzrS-0hy4}A81 zSIG5NdS(c!(yuL);g9;iRL^&=#fsp|DqUAW(Q#vc~Mn-B4&>A-GdVHe`QZ2wq6DfM4#1rCJTRrS}L4u{!bNY|G| z!Sz+Fu8{n7WzPAYUjc{`XERAqrMhEVwWT+_5;h254gFj39BnTHgH}4 zuAog+QNx*Zh`^UU#@Vf!XaF`lBpk;SdTxnH02uNpD!ZpdUbMVth) zLQ+n0%m%$O_*>q+zoqK#8$h=SI&+f4>+=nnTQLBD>%}<}_cCs{FWKSh===cI=mN1TpZ!is4^P&KBR1+z62OJ+c(P4R0Mg(N9T3L1YDsV~h8b z$WGp`gMGF$nSs+&1#x>7Y|w=QtC3#a+sQqS&Q3A4!BG7w$Mb<%p=d-9LardfLZ(Hi ze$aEql}%bu&$Jl`BK>f`cC+#z%VpJ4w8cBF}ld5W?jgR^QkRSY)BcN)N5Q$+m z$8T3s(92g-Dvwn~e5dGn{6~YTs{L9Icj9N0e7y@;y%1ZnjXq67I$MA$l766WPb_i^qg?#poh`F} zmjA05e^RX+)QO;;6+p7V0_8fYe<_uIDLK1DPtC|S@xx`7v2rnf-E7A>0Fn#&;)bw zu!8rLUxSUSuFhI9poTpq+?&3vUi62~sZRpuEAyhLR-VEPxu%;ugAZRT1OVZ2Yon2W zZ`NFMKESS_s@$an6)s^I2O$e~4k+=Rt+1_XM%K{nx#z6iSr*s1`oc#L2U`E7MB;OXn@i&w9CG6c3_uTVPno_CczS@2<#MjPDxF@Db%2aYAk zG@Dj!7z>#A%F*T3;*aExWD~3Qg9cr6=8v9;x%SqZ?REOvTMu=$wZ84gn%!2NYBJr| z!to^91ng~0+>`R%;AI=HwNFOb3L6nWrv`?rG~C@Rt+4r)93rqYztMV0HK`90LIFF! zG?sd{1ku$$eecA1(>8Mx)@q+sItdgL>qdG|8_<*y;Q5aju5FW+(XK|ZX5xa=(8`1M zUh^vS#;2hN!)osO@dCKY0j94EXowa#0rbKl>5sjKdUcaI71z5OruXX1F-GaLkLG#$ z%j5yw=uPO)ej1(izCe#hLNLR9{yMWZv-_VdmuT0d&3`6mlxUbMExk}K=X{Brn;YX8 z!12M>Wyb5?#cTi`DE|vGc%d9tnZ@x)i91+Y^l7Tvv4G8MDdKWT=WK~8ok}GP z%Xr=0Etv*-CeBnb<)`{(_BBjxHyyixkJ$aQv#G)!e}cISni%inHobyIW?I7 zDUeM2-uu+ysN!CYtNUb}hPD?eOYho8!c*s3L2GGihbny-Y$J?7Ds|eR9veCL)x*v% zgWTwfur_ii+3wxyWnbtCZfXYG9BjqpZ>=8~b^FV-JZ+J+1S*zOw>$^%>CEWf{cvAb zY&y3yh;r)dZ<1t(h{JCqw&qVm2UX(KGoTk&;UI6oI~tl=sRd|E9=&HqGsN@r8_srK zL1WsBL6WZ>1_D5`uRk2uWMoUWC@Wn*DG_ZUI86Hvm>&aWHx|J89e$| z(qV2$gvXtFckC@_(^K!@{3x6E3J-xu>pJty65(fQi1fRdoON|QstwH@O5X*>S%%{R z#{6z29CsI}Y^kuK=?-rD2mALVJ=**8ac66a3ZhQ3wZR=;=6rM)$T@I`gz#s~&WLxO zkJ|Wgw0Guv>;@cSegOpx(JbuUR)j|`Ip5)zI3-GGS&OEGJs*HJUhGuexCpuL+jVY$ zlE43M0_fbR#gs9wJWCANe1a3>&xkPT=^;C=pdZ%d7{2B$$f~KeGXBZ8a*${x0MZmj zdMP6jSsDVE2+1*7pp6u3)luh}6=313pf(!vueCa2+ivb09c!hUzPg@>_#PuKJzkQ7 zf;0i`h`Y(Rg<@j~k@3$7(&JImT#9^hT&Jn~rIbUjmmXo2{wIFU!!NbRkgO$;%2dx& zWo=V*^qcp%G~B(!X_-1B8jxO-wv#6p)4bgu+$R8Ylb(X9{gzE{8FxgCsyDuFM?zNT77m&@kr#Y@Cv=}c$ zfOQZ0XR7D%P3P>oZlb%$a+=4hnSJxgxk8Q*KiOGr*L7M&o3706)^y%;?FAd=t9)ur zns0dR5xmd&m5>=v{m8)B;+eCHY;<{9HK9bJzhO<8D3Bje03pK7U~%x-%(P8%5tyS9yhHCY8DA z5i5#5A_J-NS{W>FVM=cUG{%gPZ1ch8`oN4u|4Z+ial}@M$~GEt_z_QUG!V~c+bZOV zKKf|32dMwHF|FP7p+}WAwMY%XJ-UtsU4LA_cMt+!H@klQy7YL2FZ77V4nltcY$U|_ z@aOT-2QIkwP1NJ6kh0j)$C|vWLOicA2jl>I(QQ(1+#uZB@^$cyqZyzom2aGS*=5hx zHpu_N+nM1kwnOiqkZub((z+avXare}v@haZ64G3Z2MwKKAsW zE)EyDiWmbnt9*0en9|Oet5%c@wsW z7D6WHlhsGw4$NLbt4=nkMK$>6`<(n9-oh4)^hE$ny*1ae^k&C9hQ>yV+Jo(AaJ#0p z*uzr^B9KKhDEuQSZS1ve|NZ6Ucf{azvP55qWAN7RV(+bPqC>+@;=w{-7a=BI1tuiX za$nj&O7u%OLMMd20uq|IX^V z0MRZ>CP>!<9mtm6I>gH+w@p0DF5RH&KLFG9!T_Z}+X$VZ`$2x?p3clt&}>C@8x8C* zeP)S64&*1dy}SJuLjC(C_8|a1dw`yQpm@%NZk7JS>bk^%>XVA+({ySR8P^dp7hm&{ zy>^3DfY?iU`Nquf&T7!}Mz_Bb(w`i%w^QlYlD|&n$DvAi-tbm`Ss7hfd8G}1E_OT5 z4nkw}VpeR}vb<&alfzi8_9E+Ww>=0nADhmxm5h?;V;gXand3CRXVE@#`6yZ4NKS3~ zne4P} zi2b(#-=L~-?_^DNyKFP}1?CoCVJ%O(zmncvwpiJUD=Y`(jj5(lCNl=h-N8DoON$|r z3Ob4go+G_|ilOwHj=vs{n>Sf#kRSBq%wG3)`Xj`)=~tO9sZ!rZ$?k~{VM@kjPGKs zE1johwPzG?mRUICfccJMSk2$AW-jxfy!SYy*>)+ptgn>=^>`x<{p1DfTH<6C<-MX; zaqEb*{K6_-mgJ#cH_hJpM)kCXCi=1@0^P?oEM$=fy2S&zvzx^Gam_3?{KoPUr1Qt4^0*(EL{F}D zyLSJgz9AVwy4W)eY_ltdrxHLBzN=`NM;Z6vf1>Y`1{U7gEHEzJZFSOn-Y%w~_Ttpn z3s_#6y)yd6^FUK0ch!Bl-Dn*E9x)82erEw>Y>^3tOs=L%cC9z=K2z<4&yLmrF;9f& zFMtB*CV=fz)1GhtoevXWXr(@VA!fw71s{1wMdsof*iz`uZ-L)AQtLR~Ji8>~y!xGLC2!E1ve6T>zMS z;2Mb!Zn>HFtj+=vdk0DLJVvJVPL5XFHTaVQ*VWk@&v=FV-P3>F91}W~yC&XC7yx3% zouV8vU?G-Rx$W880bW8ZF{mQT3#kcx2ZMtmd&6_9bF65&)!Ug}Z&3EsJPM1Al~*1< z#jKK86I%Gy&Tn@+E`I~jj=DpLorUZZFLIPOavURmt|!N12csbp5BzEpzx2C*U|wtD zDEW|5lP{_aZr><0!G3K^=9_R!XhpT~&zj~4nAPi4RaJ5885msU=Y3bh`hR!x%&HV0=XL zHnt!E@KYdlC2uv4{>mI5yNqGi^2#q`tQ1y8PI+(%VOIH6o^U$dGR_T$@zs?_S48@8 z_RG|yYU%`H4O<1R_7a3MG67$V47RsW?7N%Vo0YWaXL>Z=Zl#?$CShrzV9^$!CyN-HqRy4NlrA(UbAs5yKdBAA3f#0 zd=1}Pb_`tlFmUM#8ym$_U|X5i9w-;}GHua8TZoJ+p$mJ31$|w6H$=(K6++xsH($EW zQ_P!$k2D%pCDwJDr00Sorg!0jH522!lm40(S6fnIfeT);UGnx<5%YseJWF)mZo5a8hR{{s~UaTLe)IqlYoPxkJ5@-vQ>v`eR(uqY^S+$qFDlHPQBMNU&da+M`E-RAQ!`+Ej7{v7OF#~S8+8n4;>N;Z6i2Jx4^ko?y`Ax2o2|O$!7hByyrRZ#3HruY?%Q zEne_NhB8+$lCQjEy(!1@K1fM6=u?an*BIv_PN6TkRX8IBn0=ydC1}Iu>kdDj4d~+P zTg?C+!R_s>{siSFFaq(HEzgW-+F&0) zHbVEO{k@SzcHjMsc&(|%?7ZS0ER_8LM`6O�|+e_2W{`;FzLDo4^hY)3;XdpPPG^ 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 diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-3.PNG b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-3.PNG index f861bc50174ff321217be4677aa878d606027040..3c4b8bd8e7517cf84ed534f944b1c7f88188674f 100644 GIT binary patch literal 63736 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 literal 68199 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�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

TW)XF6AnNsW@225?vf-Xx-xNip4|8&Pje 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

b?`qI!>qzw8Vmye(mAlxMrC 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 Change Kernel > Python 3.6 from the menus. + +## **Option 2: Use your own notebook server** + +### Quick installation +We recommend you create a Python virtual environment ([Miniconda](https://conda.io/miniconda.html) preferred but [virtualenv](https://virtualenv.pypa.io/en/latest/) works too) and install the SDK in it. +```sh +# install just the base SDK +pip install azureml-sdk + +# clone the sample repoistory +git clone https://github.com/Azure/MachineLearningNotebooks.git + +# below steps are optional +# install the base SDK, Jupyter notebook server and tensorboard +pip install azureml-sdk[notebooks,tensorboard] + +# install model explainability component +pip install azureml-sdk[explain] + +# install automated ml components +pip install azureml-sdk[automl] + +# install experimental features (not ready for production use) +pip install azureml-sdk[contrib] +``` + +Note the _extras_ (the keywords inside the square brackets) can be combined. For example: +```sh +# install base SDK, Jupyter notebook and automated ml components +pip install azureml-sdk[notebooks,automl] +``` + +### Full instructions +[Install the Azure Machine Learning SDK](https://docs.microsoft.com/en-us/azure/machine-learning/service/quickstart-create-workspace-with-python) + +Please make sure you start with the [Configuration](configuration.ipynb) notebook to create and connect to a workspace. + + +### Video walkthrough: + +[!VIDEO https://youtu.be/VIsXeTuW3FU] + +## **Option 3: Use Docker** + +You need to have Docker engine installed locally and running. Open a command line window and type the following command. + +__Note:__ We use version `1.0.10` below as an exmaple, but you can replace that with any available version number you like. + +```sh +# clone the sample repoistory +git clone https://github.com/Azure/MachineLearningNotebooks.git + +# change current directory to the folder +# where Dockerfile of the specific SDK version is located. +cd MachineLearningNotebooks/Dockerfiles/1.0.10 + +# build a Docker image with the a name (azuremlsdk for example) +# and a version number tag (1.0.10 for example). +# this can take several minutes depending on your computer speed and network bandwidth. +docker build . -t azuremlsdk:1.0.10 + +# launch the built Docker container which also automatically starts +# a Jupyter server instance listening on port 8887 of the host machine +docker run -it -p 8887:8887 azuremlsdk:1.0.10 +``` + +Now you can point your browser to http://localhost:8887. We recommend that you start from the `configuration.ipynb` notebook at the root directory. + +If you need additional Azure ML SDK components, you can either modify the Docker files before you build the Docker images to add additional steps, or install them through command line in the live container after you build the Docker image. For example: + +```sh +# install the core SDK and automated ml components +pip install azureml-sdk[automl] + +# install the core SDK and model explainability component +pip install azureml-sdk[explain] + +# install the core SDK and experimental components +pip install azureml-sdk[contrib] +``` +Drag and Drop +The image will be downloaded by Fatkun \ No newline at end of file diff --git a/setup-environment/configuration.ipynb b/setup-environment/configuration.ipynb new file mode 100644 index 00000000..159e82d5 --- /dev/null +++ b/setup-environment/configuration.ipynb @@ -0,0 +1,291 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Configuration\n", + "\n", + "_**Setting up your Azure Machine Learning services workspace and configuring your notebook library**_\n", + "\n", + "---\n", + "---\n", + "\n", + "## Table of Contents\n", + "\n", + "1. [Introduction](#Introduction)\n", + " 1. What is an Azure Machine Learning workspace\n", + "1. [Setup](#Setup)\n", + " 1. Azure subscription\n", + " 1. Azure ML SDK and other library installation\n", + " 1. Azure Container Instance registration\n", + "1. [Configure your Azure ML Workspace](#Configure%20your%20Azure%20ML%20workspace)\n", + " 1. Workspace parameters\n", + " 1. Access your workspace\n", + " 1. Create a new workspace\n", + "1. [Next steps](#Next%20steps)\n", + "\n", + "---\n", + "\n", + "## Introduction\n", + "\n", + "This notebook configures your library of notebooks to connect to an Azure Machine Learning (ML) workspace. In this case, a library contains all of the notebooks in the current folder and any nested folders. You can configure this notebook library to use an existing workspace or create a new workspace.\n", + "\n", + "Typically you will need to run this notebook only once per notebook library as all other notebooks will use connection information that is written here. If you want to redirect your notebook library to work with a different workspace, then you should re-run this notebook.\n", + "\n", + "In this notebook you will\n", + "* Learn about getting an Azure subscription\n", + "* Specify your workspace parameters\n", + "* Access or create your workspace\n", + "* Add a default compute cluster for your workspace\n", + "\n", + "### What is an Azure Machine Learning workspace\n", + "\n", + "An Azure ML Workspace is an Azure resource that organizes and coordinates the actions of many other Azure resources to assist in executing and sharing machine learning workflows. In particular, an Azure ML Workspace coordinates storage, databases, and compute resources providing added functionality for machine learning experimentation, deployment, inferencing, and the monitoring of deployed models." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "This section describes activities required before you can access any Azure ML services functionality." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Azure Subscription\n", + "\n", + "In order to create an Azure ML Workspace, first you need access to an Azure subscription. An Azure subscription allows you to manage storage, compute, and other assets in the Azure cloud. You can [create a new subscription](https://azure.microsoft.com/en-us/free/) or access existing subscription information from the [Azure portal](https://portal.azure.com). Later in this notebook you will need information such as your subscription ID in order to create and access AML workspaces.\n", + "\n", + "### 2. Azure ML SDK and other library installation\n", + "\n", + "If you are running in your own environment, follow [SDK installation instructions](https://docs.microsoft.com/azure/machine-learning/service/how-to-configure-environment). If you are running in Azure Notebooks or another Microsoft managed environment, the SDK is already installed.\n", + "\n", + "Also install following libraries to your environment. Many of the example notebooks depend on them\n", + "\n", + "```\n", + "(myenv) $ conda install -y matplotlib tqdm scikit-learn\n", + "```\n", + "\n", + "Once installation is complete, the following cell checks the Azure ML SDK version:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "install" + ] + }, + "outputs": [], + "source": [ + "import azureml.core\n", + "\n", + "print(\"This notebook was created using version of the Azure ML SDK\")\n", + "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you are using an older version of the SDK then this notebook was created using, you should upgrade your SDK.\n", + "\n", + "### 3. Azure Container Instance registration\n", + "Azure Machine Learning uses of [Azure Container Instance (ACI)](https://azure.microsoft.com/services/container-instances) to deploy dev/test web services. An Azure subscription needs to be registered to use ACI. If you or the subscription owner have not yet registered ACI on your subscription, you will need to use the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) and execute the following commands. Note that if you ran through the AML [quickstart](https://docs.microsoft.com/en-us/azure/machine-learning/service/quickstart-get-started) you have already registered ACI. \n", + "\n", + "```shell\n", + "# check to see if ACI is already registered\n", + "(myenv) $ az provider show -n Microsoft.ContainerInstance -o table\n", + "\n", + "# if ACI is not registered, run this command.\n", + "# note you need to be the subscription owner in order to execute this command successfully.\n", + "(myenv) $ az provider register -n Microsoft.ContainerInstance\n", + "```\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure your Azure ML workspace\n", + "\n", + "### Workspace parameters\n", + "\n", + "To use an AML Workspace, you will need to import the Azure ML SDK and supply the following information:\n", + "* Your subscription id\n", + "* A resource group name\n", + "* (optional) The region that will host your workspace\n", + "* A name for your workspace\n", + "\n", + "You can get your subscription ID from the [Azure portal](https://portal.azure.com).\n", + "\n", + "You will also need access to a [_resource group_](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview#resource-groups), which organizes Azure resources and provides a default region for the resources in a group. You can see what resource groups to which you have access, or create a new one in the [Azure portal](https://portal.azure.com). If you don't have a resource group, the create workspace command will create one for you using the name you provide.\n", + "\n", + "The region to host your workspace will be used if you are creating a new workspace. You do not need to specify this if you are using an existing workspace. You can find the list of supported regions [here](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=machine-learning-service). You should pick a region that is close to your location or that contains your data.\n", + "\n", + "The name for your workspace is unique within the subscription and should be descriptive enough to discern among other AML Workspaces. The subscription may be used only by you, or it may be used by your department or your entire enterprise, so choose a name that makes sense for your situation.\n", + "\n", + "The following cell allows you to specify your workspace parameters. This cell uses the python method `os.getenv` to read values from environment variables which is useful for automation. If no environment variable exists, the parameters will be set to the specified default values. \n", + "\n", + "If you ran the Azure Machine Learning [quickstart](https://docs.microsoft.com/en-us/azure/machine-learning/service/quickstart-get-started) in Azure Notebooks, you already have a configured workspace! You can go to your Azure Machine Learning Getting Started library, view *config.json* file, and copy-paste the values for subscription ID, resource group and workspace name below.\n", + "\n", + "Replace the default values in the cell below with your workspace parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "subscription_id = os.getenv(\"SUBSCRIPTION_ID\", default=\"\")\n", + "resource_group = os.getenv(\"RESOURCE_GROUP\", default=\"\")\n", + "workspace_name = os.getenv(\"WORKSPACE_NAME\", default=\"\")\n", + "workspace_region = os.getenv(\"WORKSPACE_REGION\", default=\"eastus2\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Access your workspace\n", + "\n", + "The following cell uses the Azure ML SDK to attempt to load the workspace specified by your parameters. If this cell succeeds, your notebook library will be configured to access the workspace from all notebooks using the `Workspace.from_config()` method. The cell can fail if the specified workspace doesn't exist or you don't have permissions to access it. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "\n", + "try:\n", + " ws = Workspace(subscription_id = subscription_id, resource_group = resource_group, workspace_name = workspace_name)\n", + " # write the details of the workspace to a configuration file to the notebook library\n", + " ws.write_config()\n", + " print(\"Workspace configuration succeeded. Skip the workspace creation steps below\")\n", + "except:\n", + " print(\"Workspace not accessible. Change your parameters or create a new workspace below\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a new workspace\n", + "\n", + "If you don't have an existing workspace and are the owner of the subscription or resource group, you can create a new workspace. If you don't have a resource group, the create workspace command will create one for you using the name you provide.\n", + "\n", + "**Note**: As with other Azure services, there are limits on certain resources (for example AmlCompute quota) associated with the Azure ML service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota.\n", + "\n", + "This cell will create an Azure ML workspace for you in a subscription provided you have the correct permissions.\n", + "\n", + "This will fail if:\n", + "* You do not have permission to create a workspace in the resource group\n", + "* You do not have permission to create a resource group if it's non-existing.\n", + "* You are not a subscription owner or contributor and no Azure ML workspaces have ever been created in this subscription\n", + "\n", + "If workspace creation fails, please work with your IT admin to provide you with the appropriate permissions or to provision the required resources." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "create workspace" + ] + }, + "outputs": [], + "source": [ + "from azureml.core import Workspace\n", + "\n", + "# Create the workspace using the specified parameters\n", + "ws = Workspace.create(name = workspace_name,\n", + " subscription_id = subscription_id,\n", + " resource_group = resource_group, \n", + " location = workspace_region,\n", + " create_resource_group = True,\n", + " exist_ok = True)\n", + "ws.get_details()\n", + "\n", + "# write the details of the workspace to a configuration file to the notebook library\n", + "ws.write_config()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Next steps\n", + "\n", + "In this notebook you configured this notebook library to connect easily to an Azure ML workspace. You can copy this notebook to your own libraries to connect them to you workspace, or use it to bootstrap new workspaces completely.\n", + "\n", + "If you came here from another notebook, you can return there and complete that exercise, or you can try out the [Tutorials](./tutorials) or jump into \"how-to\" notebooks and start creating and deploying models. A good place to start is the [train within notebook](./how-to-use-azureml/training/train-within-notebook) example that walks through a simplified but complete end to end machine learning process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/configuration.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "roastala" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/setup-environment/configuration.yml b/setup-environment/configuration.yml new file mode 100644 index 00000000..4b1aed29 --- /dev/null +++ b/setup-environment/configuration.yml @@ -0,0 +1,4 @@ +name: configuration +dependencies: +- pip: + - azureml-sdk diff --git a/training/README.md b/training/README.md new file mode 100644 index 00000000..e69de29b diff --git a/tutorials/img-classification-part1-training.yml b/tutorials/img-classification-part1-training.yml new file mode 100644 index 00000000..c76cf572 --- /dev/null +++ b/tutorials/img-classification-part1-training.yml @@ -0,0 +1,7 @@ +name: img-classification-part1-training +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - matplotlib + - sklearn diff --git a/tutorials/img-classification-part2-deploy.yml b/tutorials/img-classification-part2-deploy.yml new file mode 100644 index 00000000..bc88852c --- /dev/null +++ b/tutorials/img-classification-part2-deploy.yml @@ -0,0 +1,6 @@ +name: img-classification-part2-deploy +dependencies: +- pip: + - azureml-sdk + - matplotlib + - sklearn diff --git a/tutorials/regression-part1-data-prep.yml b/tutorials/regression-part1-data-prep.yml new file mode 100644 index 00000000..4cc6d99f --- /dev/null +++ b/tutorials/regression-part1-data-prep.yml @@ -0,0 +1,5 @@ +name: regression-part1-data-prep +dependencies: +- pip: + - azureml-sdk + - azureml-dataprep[pandas]>=1.1.2,<1.2.0 diff --git a/tutorials/regression-part2-automated-ml.yml b/tutorials/regression-part2-automated-ml.yml new file mode 100644 index 00000000..f6e23d59 --- /dev/null +++ b/tutorials/regression-part2-automated-ml.yml @@ -0,0 +1,10 @@ +name: regression-part2-automated-ml +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - azureml-explain-model + - matplotlib + - pandas_ml + - seaborn diff --git a/tutorials/sklearn_mnist_model.pkl b/tutorials/sklearn_mnist_model.pkl index 135dd09ec8b00338ca7f62ce23142b2fdd055ea4..cec0edfefa11a9d51f56af766d957ddd9a638d59 100644 GIT binary patch literal 63684 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

L8VUAh`-`zq_6^-f^=>D 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 literal 63684 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 diff --git a/work-with-data/README.md b/work-with-data/README.md new file mode 100644 index 00000000..cfc265b2 --- /dev/null +++ b/work-with-data/README.md @@ -0,0 +1,9 @@ +# Work With Data Using Azure Machine Learning Service + +Azure Machine Learning Datasets (preview) make it easier to access and work with your data. Datasets manage data in various scenarios such as model training and pipeline creation. Using the Azure Machine Learning SDK, you can access underlying storage, explore and prepare data, manage the life cycle of different Dataset definitions, and compare between Datasets used in training and in production. + +- For an example of using Datasets, see the [sample](datasets). +- For advanced data preparation examples, see [dataprep](dataprep). + + +![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/README..png) \ No newline at end of file diff --git a/work-with-data/dataprep/README.md b/work-with-data/dataprep/README.md new file mode 100644 index 00000000..556aae74 --- /dev/null +++ b/work-with-data/dataprep/README.md @@ -0,0 +1,255 @@ +# Azure Machine Learning Data Prep SDK + +The Azure Machine Learning Data Prep SDK helps data scientists explore, cleanse and transform data for machine learning workflows in any Python environment. + +Key benefits to the SDK: +- Cross-platform functionality. Write with a single SDK and run it on Windows, macOS, or Linux. +- Intelligent transformations powered by AI, including grouping similar values to their canonical form and deriving columns by examples without custom code. +- Capability to work with large, multiple files of different schema. +- Scalability on a single machine by streaming data during processing rather than loading into memory. +- Seamless integration with other Azure Machine Learning services. You can simply pass your prepared data file into `AutoMLConfig` object for automated machine learning training. + +You will find in this repo: +- [Getting Started Tutorial](tutorials/getting-started/getting-started.ipynb) for a quick introduction to the main features of Data Prep SDK. +- [Case Study Notebooks](case-studies/new-york-taxi) that present an end-to-end data preparation tutorial where users start with small dataset, profile data with statistics summary, cleanse and perform feature engineering. All transformation steps are saved in a dataflow object. Users can easily reapply the same steps on the full dataset, and run it on Spark. +- [How-To Guide Notebooks](how-to-guides) for more in-depth sample code at feature level. + +## Installation +Here are the [SDK installation steps](https://aka.ms/aml-data-prep-installation). + +## Documentation +Here is more information on how to use the new Data Prep SDK: +- [SDK overview and API reference docs](http://aka.ms/data-prep-sdk) that show different classes, methods, and function parameters for the SDK. +- [Tutorial: Prep NYC taxi data](https://docs.microsoft.com/azure/machine-learning/service/tutorial-data-prep) for regression modeling and then run automated machine learning to build the model. +- [How to load data](https://docs.microsoft.com/azure/machine-learning/service/how-to-load-data) is an overview guide on how to load data using the Data Prep SDK. +- [How to transform data](https://docs.microsoft.com/azure/machine-learning/service/how-to-transform-data) is an overview guide on how to transform data. +- [How to write data](https://docs.microsoft.com/azure/machine-learning/service/how-to-write-data) is an overview guide on how to write data to different storage locations. + +## Support + +If you have any questions or feedback, send us an email at: [askamldataprep@microsoft.com](mailto:askamldataprep@microsoft.com). + +## Release Notes + +### 2019-05-28 (version 1.1.4) + +New features +- You can now use the following expression language functions to extract and parse datetime values into new columns. + - `RegEx.extract_record()` extracts datetime elements into a new column. + - `create_datetime()` creates datetime objects from separate datetime elements. +- When calling `get_profile()`, you can now see that quantile columns are labeled as (est.) to clearly indicate that the values are approximations. +- You can now use ** globbing when reading from Azure Blob Storage. + - e.g. `dprep.read_csv(path='https://yourblob.blob.core.windows.net/yourcontainer/**/data/*.csv')` + +Bug fixes +- Fixed a bug related to reading a Parquet file from a remote source (Azure Blob). + +### 2019-05-08 (version 1.1.3) + +New features +- Added support to read from a PostgresSQL database, either by calling `read_postgresql` or using a Datastore. + - See examples in how-to guides: + - [Data Ingestion notebook](https://aka.ms/aml-data-prep-ingestion-nb) + - [Datastore notebook](https://aka.ms/aml-data-prep-datastore-nb) + +Bug fixes and improvements +- Fixed issues with column type conversion: + - Now correctly converts a boolean or numeric column to a boolean column. + - Now does not fail when attempting to set a date column to be date type. +- Improved JoinType types and accompanying reference documentation. When joining two dataflows, you can now specify one of these types of join: + - NONE, MATCH, INNER, UNMATCHLEFT, LEFTANTI, LEFTOUTER, UNMATCHRIGHT, RIGHTANTI, RIGHTOUTER, FULLANTI, FULL. +- Improved data type inferencing to recognize more date formats. + +### 2019-04-17 (version 1.1.2) + +Note: Data Prep Python SDK will no longer install `numpy` and `pandas` packages. See [updated installation instructions](https://aka.ms/aml-data-prep-installation). + +New features +- You can now use the Pivot transform. + - How-to guide: [Pivot notebook](https://aka.ms/aml-data-prep-pivot-nb) +- You can now use regular expressions in native functions. + - Examples: + - `dflow.filter(dprep.RegEx('pattern').is_match(dflow['column_name']))` + - `dflow.assert_value('column_name', dprep.RegEx('pattern').is_match(dprep.value))` +- You can now use `to_upper` and `to_lower` functions in expression language. +- You can now see the number of unique values of each column in a data profile. +- For some of the commonly used reader steps, you can now pass in the `infer_column_types` argument. If it is set to `True`, Data Prep will attempt to detect and automatically convert column types. + - `inference_arguments` is now deprecated. +- You can now call `Dataflow.shape`. + +Bug fixes and improvements +- `keep_columns` now accepts an additional optional argument `validate_column_exists`, which checks if the result of `keep_columns` will contain any columns. +- All reader steps (which read from a file) now accept an additional optional argument `verify_exists`. +- Improved performance of reading from pandas dataframe and getting data profiles. +- Fixed a bug where slicing a single step from a Dataflow failed with a single index. + +### 2019-04-08 (version 1.1.1) + +New features +- You can read multiple Datastore/DataPath/DataReference sources using read_* transforms. +- You can perform the following operations on columns to create a new column: division, floor, modulo, power, length. +- Data Prep is now part of the Azure ML diagnostics suite and will log diagnostic information by default. + - To turn this off, set this environment variable to true: DISABLE_DPREP_LOGGER + +Bug fixes and improvements +- Improved code documentation for commonly used classes and functions. +- Fixed a bug in auto_read_file that failed to read Excel files. +- Added option to overwrite the folder in read_pandas_dataframe. +- Improved performance of dotnetcore2 dependency installation, and added support for Fedora 27/28 and Ubuntu 1804. +- Improved the performance of reading from Azure Blobs. +- Column type detection now supports columns of type Long. +- Fixed a bug where some date values were being displayed as timestamps instead of Python datetime objects. +- Fixed a bug where some type counts were being displayed as doubles instead of integers. + +### 2019-03-25 (version 1.1.0) + +Breaking changes +- The concept of the Data Prep Package has been deprecated and is no longer supported. Instead of persisting multiple Dataflows in one Package, you can persist Dataflows individually. + - How-to guide: [Opening and Saving Dataflows notebook](https://aka.ms/aml-data-prep-open-save-dataflows-nb) + +New features +- Data Prep can now recognize columns that match a particular Semantic Type, and split accordingly. The STypes currently supported include: email address, geographic coordinates (latitude & longitude), IPv4 and IPv6 addresses, US phone number, and US zip code. + - How-to guide: [Semantic Types notebook](https://aka.ms/aml-data-prep-semantic-types-nb) +- Data Prep now supports the following operations to generate a resultant column from two numeric columns: subtract, multiply, divide, and modulo. +- You can call `verify_has_data()` on a Dataflow to check whether the Dataflow would produce records if executed. + +Bug fixes and improvements +- You can now specify the number of bins to use in a histogram for numeric column profiles. +- The `read_pandas_dataframe` transform now requires the DataFrame to have string- or byte- typed column names. +- Fixed a bug in the `fill_nulls` transform, where values were not correctly filled in if the column was missing. + +### 2019-03-11 (version 1.0.17) + +New features +- Now supports adding two numeric columns to generate a resultant column using the expression language. + +Bug fixes and improvements +- Improved the documentation and parameter checking for random_split. + +### 2019-02-27 (version 1.0.16) + +Bug fix +- Fixed a Service Principal authentication issue that was caused by an API change. + +### 2019-02-25 (version 1.0.15) + +New features +- Data Prep now supports writing file streams from a dataflow. Also provides the ability to manipulate the file stream names to create new file names. + - How-to guide: [Working With File Streams notebook](https://aka.ms/aml-data-prep-file-stream-nb) + +Bug fixes and improvements +- Improved performance of t-Digest on large data sets. +- Data Prep now supports reading data from a DataPath. +- One hot encoding now works on boolean and numeric columns. +- Other miscellaneous bug fixes. + +### 2019-02-11 (version 1.0.12) + +New features +- Data Prep now supports reading from an Azure SQL database using Datastore. + +Changes +- Significantly improved the memory performance of certain operations on large data. +- `read_pandas_dataframe()` now requires `temp_folder` to be specified. +- The `name` property on `ColumnProfile` has been deprecated - use `column_name` instead. + +### 2019-01-28 (version 1.0.8) + +Bug fixes +- Significantly improved the performance of getting data profiles. +- Fixed minor bugs related to error reporting. + +### 2019-01-14 (version 1.0.7) + +New features +- Datastore improvements (documented in [Datastore how-to-guide](https://aka.ms/aml-data-prep-datastore-nb)) + - Added ability to read from and write to Azure File Share and ADLS Datastores in scale-up. + - When using Datastores, Data Prep now supports using service principal authentication instead of interactive authentication. + - Added support for wasb and wasbs urls. + +### 2019-01-09 (version 1.0.6) + +Bug fixes +- Fixed bug with reading from public readable Azure Blob containers on Spark. + +### 2018-12-19 (version 1.0.4) + +New features +- `to_bool` function now allows mismatched values to be converted to Error values. This is the new default mismatch behavior for `to_bool` and `set_column_types`, whereas the previous default behavior was to convert mismatched values to False. +- When calling `to_pandas_dataframe`, there is a new option to interpret null/missing values in numeric columns as NaN. +- Added ability to check the return type of some expressions to ensure type consistency and fail early. +- You can now call `parse_json` to parse values in a column as JSON objects and expand them into multiple columns. + +Bug fixes +- Fixed a bug that crashed `set_column_types` in Python 3.5.2. +- Fixed a bug that crashed when connecting to Datastore using an AML image. + +### 2018-12-07 (version 0.5.3) + +Fixed missing dependency issue for .NET Core2 on Ubuntu 16. + +### 2018-12-03 (version 0.5.2) + +Breaking changes +- `SummaryFunction.N` was renamed to `SummaryFunction.Count`. + +Bug fixes +- Use latest AML Run Token when reading from and writing to datastores on remote runs. Previously, if the AML Run Token is updated in Python, the Data Prep runtime will not be updated with the updated AML Run Token. +- Additional clearer error messages +- to_spark_dataframe() will no longer crash when Spark uses Kryo serialization +- Value Count Inspector can now show more than 1000 unique values +- Random Split no longer fails if the original Dataflow doesn’t have a name + +### 2018-11-19 (version 0.5.0) + +New features +- Created a new DataPrep CLI to execute DataPrep packages and view the data profile for a dataset or dataflow +- Redesigned SetColumnType API to improve usability +- Renamed smart_read_file to auto_read_file +- Now includes skew and kurtosis in the Data Profile +- Can sample with stratified sampling +- Can read from zip files that contain CSV files +- Can split datasets row-wise with Random Split (e.g. into test-train sets) +- Can get all the column data types from a dataflow or a data profile by calling .dtypes +- Can get the row count from a dataflow or a data profile by calling .row_count + +Bug fixes +- Fixed long to double conversion +- Fixed assert after any add column +- Fixed an issue with FuzzyGrouping, where it would not detect groups in some cases +- Fixed sort function to respect multi-column sort order +- Fixed and/or expressions to be similar to how Pandas handles them +- Fixed reading from dbfs path. +- Made error messages more understandable +- Now no longer fails when reading on remote compute target using AML token +- Now no longer fails on Linux DSVM +- Now no longer crashes when non-string values are in string predicates +- Now handles assertion errors when Dataflow should fail correctly +- Now supports dbutils mounted storage locations on Azure Databricks + +### 2018-11-05 (version 0.4.0) + +New features +- Type Count added to Data Profile +- Value Count and Histogram is now available +- More percentiles in Data Profile +- The Median is available in Summarize +- Python 3.7 is now supported +- When you save a dataflow that contains datastores to a Data Prep package, the datastore information will be persisted as part of the Data Prep package +- Writing to datastore is now supported + +Bug fixes +- 64bit unsigned integer overflows are now handled properly on Linux +- Fixed incorrect text label for plain text files in smart_read +- String column type now shows up in metrics view +- Type count now is fixed to show ValueKinds mapped to single FieldType instead of individual ones +- Write_to_csv no longer fails when path is provided as a string +- When using Replace, leaving “find” blank will no longer fail + +## Datasets License Information + +IMPORTANT: Please read the notice and find out more about this NYC Taxi and Limousine Commission dataset here: http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml + +IMPORTANT: Please read the notice and find out more about this Chicago Police Department dataset here: https://catalog.data.gov/dataset/crimes-2001-to-present-398a4 + +![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/README.png) diff --git a/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb new file mode 100644 index 00000000..cdfb6d70 --- /dev/null +++ b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb @@ -0,0 +1,514 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cleaning up New York Taxi Cab data\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's use DataPrep to clean and featurize the data which can then be used to predict taxi trip duration. We will not use the For Hire Vehicle (FHV) datasets as they are not really taxi rides and they don't provide drop-off time and geo-coordinates." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "from os import path\n", + "from tempfile import mkdtemp\n", + "\n", + "import pandas as pd\n", + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a quick peek at yellow cab data and green cab data to see what the data looks like. DataPrep supports globing, so you will notice below that we have added a `*` in the path.\n", + "\n", + "*We are using a small sample of the taxi data for this demo. You can find a bigger sample ~6GB by changing \"green-small\" to \"green-sample\" and \"yellow-small\" to \"yellow-sample\" in the paths below.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.set_option('display.max_columns', None)\n", + "\n", + "cache_location = mkdtemp()\n", + "dataset_root = \"https://dprepdata.blob.core.windows.net/demo\"\n", + "\n", + "green_path = \"/\".join([dataset_root, \"green-small/*\"])\n", + "yellow_path = \"/\".join([dataset_root, \"yellow-small/*\"])\n", + "\n", + "print(\"Retrieving data from the following two sources:\")\n", + "print(green_path)\n", + "print(yellow_path)\n", + "\n", + "green_df = dprep.read_csv(path=green_path, header=dprep.PromoteHeadersMode.GROUPED)\n", + "yellow_df = dprep.auto_read_file(path=yellow_path)\n", + "\n", + "display(green_df.head(5))\n", + "display(yellow_df.head(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data Cleanup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's define some shortcut transforms that will apply to all Dataflows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all_columns = dprep.ColumnSelector(term=\".*\", use_regex=True)\n", + "drop_if_all_null = [all_columns, dprep.ColumnRelationship(dprep.ColumnRelationship.ALL)]\n", + "useful_columns = [\n", + " \"cost\", \"distance\"\"distance\", \"dropoff_datetime\", \"dropoff_latitude\", \"dropoff_longitude\",\n", + " \"passengers\", \"pickup_datetime\", \"pickup_latitude\", \"pickup_longitude\", \"store_forward\", \"vendor\"\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's first work with the green taxi data and get it into a good shape that then can be combined with the yellow taxi data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (green_df\n", + " .replace_na(columns=all_columns)\n", + " .drop_nulls(*drop_if_all_null)\n", + " .rename_columns(column_pairs={\n", + " \"VendorID\": \"vendor\",\n", + " \"lpep_pickup_datetime\": \"pickup_datetime\",\n", + " \"Lpep_dropoff_datetime\": \"dropoff_datetime\",\n", + " \"lpep_dropoff_datetime\": \"dropoff_datetime\",\n", + " \"Store_and_fwd_flag\": \"store_forward\",\n", + " \"store_and_fwd_flag\": \"store_forward\",\n", + " \"Pickup_longitude\": \"pickup_longitude\",\n", + " \"Pickup_latitude\": \"pickup_latitude\",\n", + " \"Dropoff_longitude\": \"dropoff_longitude\",\n", + " \"Dropoff_latitude\": \"dropoff_latitude\",\n", + " \"Passenger_count\": \"passengers\",\n", + " \"Fare_amount\": \"cost\",\n", + " \"Trip_distance\": \"distance\"\n", + " })\n", + " .keep_columns(columns=useful_columns))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "green_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's do the same thing to yellow taxi data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (yellow_df\n", + " .replace_na(columns=all_columns)\n", + " .drop_nulls(*drop_if_all_null)\n", + " .rename_columns(column_pairs={\n", + " \"vendor_name\": \"vendor\",\n", + " \"VendorID\": \"vendor\",\n", + " \"vendor_id\": \"vendor\",\n", + " \"Trip_Pickup_DateTime\": \"pickup_datetime\",\n", + " \"tpep_pickup_datetime\": \"pickup_datetime\",\n", + " \"Trip_Dropoff_DateTime\": \"dropoff_datetime\",\n", + " \"tpep_dropoff_datetime\": \"dropoff_datetime\",\n", + " \"store_and_forward\": \"store_forward\",\n", + " \"store_and_fwd_flag\": \"store_forward\",\n", + " \"Start_Lon\": \"pickup_longitude\",\n", + " \"Start_Lat\": \"pickup_latitude\",\n", + " \"End_Lon\": \"dropoff_longitude\",\n", + " \"End_Lat\": \"dropoff_latitude\",\n", + " \"Passenger_Count\": \"passengers\",\n", + " \"passenger_count\": \"passengers\",\n", + " \"Fare_Amt\": \"cost\",\n", + " \"fare_amount\": \"cost\",\n", + " \"Trip_Distance\": \"distance\",\n", + " \"trip_distance\": \"distance\"\n", + " })\n", + " .keep_columns(columns=useful_columns))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "yellow_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now append the rows from the `yellow_df` to `green_df`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = green_df.append_rows(dataflows=[yellow_df])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a look at the pickup and drop-off coordinates' data profile to see how the data is distributed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "decimal_type = dprep.TypeConverter(data_type=dprep.FieldType.DECIMAL)\n", + "combined_df = combined_df.set_column_types(type_conversions={\n", + " \"pickup_longitude\": decimal_type,\n", + " \"pickup_latitude\": decimal_type,\n", + " \"dropoff_longitude\": decimal_type,\n", + " \"dropoff_latitude\": decimal_type\n", + "})\n", + "combined_df.keep_columns(columns=[\n", + " \"pickup_longitude\", \"pickup_latitude\", \n", + " \"dropoff_longitude\", \"dropoff_latitude\"\n", + "]).get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the data profile, we can see that there are coordinates that are missing and coordinates that are not in New York. Let's filter out coordinates not in the [city border](https://mapmakerapp.com?map=5b60a055a191245990310739f658)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .drop_nulls(\n", + " columns=[\"pickup_longitude\", \"pickup_latitude\", \"dropoff_longitude\", \"dropoff_latitude\"],\n", + " column_relationship=dprep.ColumnRelationship(dprep.ColumnRelationship.ANY)\n", + " ) \n", + " .filter(dprep.f_and(\n", + " dprep.col(\"pickup_longitude\") <= -73.72,\n", + " dprep.col(\"pickup_longitude\") >= -74.09,\n", + " dprep.col(\"pickup_latitude\") <= 40.88,\n", + " dprep.col(\"pickup_latitude\") >= 40.53,\n", + " dprep.col(\"dropoff_longitude\") <= -73.72,\n", + " dprep.col(\"dropoff_longitude\") >= -74.09,\n", + " dprep.col(\"dropoff_latitude\") <= 40.88,\n", + " dprep.col(\"dropoff_latitude\") >= 40.53\n", + " )))\n", + "tmp_df.keep_columns(columns=[\n", + " \"pickup_longitude\", \"pickup_latitude\", \n", + " \"dropoff_longitude\", \"dropoff_latitude\"\n", + "]).get_profile()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a look at the data profile for the `store_forward` column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df.keep_columns(columns='store_forward').get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the data profile of `store_forward` above, we can see that the data is inconsistent and there are missing values. Let's fix them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = combined_df.replace(columns=\"store_forward\", find=\"0\", replace_with=\"N\").fill_nulls(\"store_forward\", \"N\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now split the pick up and drop off datetimes into a date column and a time column. We will use `split_column_by_example` to perform the split. If the `example` parameter of `split_column_by_example` is omitted, we will automatically try to figure out where to split based on the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .split_column_by_example(source_column=\"pickup_datetime\")\n", + " .split_column_by_example(source_column=\"dropoff_datetime\"))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's rename the columns generated by `split_column_by_example` into meaningful names." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .rename_columns(column_pairs={\n", + " \"pickup_datetime_1\": \"pickup_date\",\n", + " \"pickup_datetime_2\": \"pickup_time\",\n", + " \"dropoff_datetime_1\": \"dropoff_date\",\n", + " \"dropoff_datetime_2\": \"dropoff_time\"\n", + " }))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Feature Engineering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Datetime features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's split the pickup and drop-off date further into day of week, day of month, and month. For pickup and drop-off time columns, we will split it into hour, minute, and second." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .derive_column_by_example(\n", + " source_columns=\"pickup_date\", \n", + " new_column_name=\"pickup_weekday\", \n", + " example_data=[(\"2009-01-04\", \"Sunday\"), (\"2013-08-22\", \"Thursday\")]\n", + " )\n", + " .derive_column_by_example(\n", + " source_columns=\"dropoff_date\",\n", + " new_column_name=\"dropoff_weekday\",\n", + " example_data=[(\"2013-08-22\", \"Thursday\"), (\"2013-11-03\", \"Sunday\")]\n", + " )\n", + " .split_column_by_example(source_column=\"pickup_date\")\n", + " .split_column_by_example(source_column=\"pickup_time\")\n", + " .split_column_by_example(source_column=\"dropoff_date\")\n", + " .split_column_by_example(source_column=\"dropoff_time\")\n", + " .split_column_by_example(source_column=\"pickup_time_1\")\n", + " .split_column_by_example(source_column=\"dropoff_time_1\")\n", + " .drop_columns(columns=[\n", + " \"pickup_date\", \"pickup_time\", \"dropoff_date\", \"dropoff_time\", \n", + " \"pickup_date_1\", \"dropoff_date_1\", \"pickup_time_1\", \"dropoff_time_1\"\n", + " ])\n", + " .rename_columns(column_pairs={\n", + " \"pickup_date_2\": \"pickup_month\",\n", + " \"pickup_date_3\": \"pickup_monthday\",\n", + " \"pickup_time_1_1\": \"pickup_hour\",\n", + " \"pickup_time_1_2\": \"pickup_minute\",\n", + " \"pickup_time_2\": \"pickup_second\",\n", + " \"dropoff_date_2\": \"dropoff_month\",\n", + " \"dropoff_date_3\": \"dropoff_monthday\",\n", + " \"dropoff_time_1_1\": \"dropoff_hour\",\n", + " \"dropoff_time_1_2\": \"dropoff_minute\",\n", + " \"dropoff_time_2\": \"dropoff_second\"\n", + " }))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the data above, we can see that the pickup and drop-off date and time components produced from the transforms above looks good. Let's drop the `pickup_datetime` and `dropoff_datetime` columns as they are no longer needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = combined_df.drop_columns(columns=[\"pickup_datetime\", \"dropoff_datetime\"])\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now save the transformation steps into a DataPrep package so we can use it to to run on spark." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_path = path.join(mkdtemp(), \"new_york_taxi.dprep\")\n", + "combined_df.save(file_path=dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb new file mode 100644 index 00000000..fd69f736 --- /dev/null +++ b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Scale-Out Data Preparation\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once we are done with preparing and featurizing the data locally, we can run the same steps on the full dataset in scale-out mode. The new york taxi cab data is about 300GB in total, which is perfect for scale-out. Let's start by downloading the package we saved earlier to disk. Feel free to run the `new_york_taxi_cab.ipynb` notebook to generate the package yourself, in which case you may comment out the download code and set the `package_path` to where the package is saved." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from tempfile import mkdtemp\n", + "from os import path\n", + "from urllib.request import urlretrieve\n", + "\n", + "dflow_root = mkdtemp()\n", + "dflow_path = path.join(dflow_root, \"new_york_taxi.dprep\")\n", + "print(\"Downloading Dataflow to: {}\".format(dflow_path))\n", + "urlretrieve(\"https://dprepdata.blob.core.windows.net/demo/new_york_taxi_v2.dprep\", dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's load the package we just downloaded." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "df = dprep.Dataflow.open(dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's replace the datasources with the full dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from uuid import uuid4\n", + "\n", + "other_step = df._get_steps()[7].arguments['dataflows'][0]['anonymousSteps'][0]\n", + "other_step['id'] = str(uuid4())\n", + "other_step['arguments']['path']['target'] = 1\n", + "other_step['arguments']['path']['resourceDetails'][0]['path'] = 'https://wranglewestus.blob.core.windows.net/nyctaxi/yellow_tripdata*'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "green_dsource = dprep.BlobDataSource(\"https://wranglewestus.blob.core.windows.net/nyctaxi/green_tripdata*\")\n", + "df = df.replace_datasource(green_dsource)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once we have replaced the datasource, we can now run the same steps on the full dataset. We will print the first 5 rows of the spark DataFrame. Since we are running on the full dataset, this might take a little while depending on your spark cluster's size." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "spark_df = df.to_spark_dataframe()\n", + "spark_df.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License.", + "skip_execute_as_test": true + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/data/adls-dpreptestfiles.crt b/work-with-data/dataprep/data/adls-dpreptestfiles.crt new file mode 100644 index 00000000..98498f95 --- /dev/null +++ b/work-with-data/dataprep/data/adls-dpreptestfiles.crt @@ -0,0 +1,45 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDmkkyF0BwipZow +Wd1AMkRkySx0y079JPxpsYhv4i1xXKdoa9bpFqwoXmJpeQM1JWnU4UeZzFeM86qK +AhQvL4KV4kibcP2ENvu2NKFEdotO3uxPJ+6GlcYwMYzy+tUj008KnnRZfTrR78sJ +tIl3C6lnVL0ICihksG59P1sskRq3PvOjXLAdEZalwDjZ4ZPoNDZdj6nUjB2l8zqu +pKAt5mR+bJ9Sox4yrDuNhMmFt5QsRDRe3wUqdV+C9OCWHmjlmsjrYw7p9YmjBDvC +5U7mF0Mk/XeYFzj0pkXKQVqBL6xqig+q5ob0szYfg19iDeFhS3iIsRcJGEnRVW/A +NpsBZyKrAgMBAAECggEBANlvP8C1F8NInhZYuIAwpzTQTh86Fxw8g9h8dijkh2wv +LyQXBk07d1B+aZoDZ5X32UzKwcX04N9obfvFqBkzWZdVFJmZvUmwvEEActBoZkkT +io+/HX5HweVy5PPCvbsSK6jc8uXtZcnSs4tMeJIOKkvqqnTpd1w00Y1FcQqfMC16 +4p7o8wbt6OFoFAYqcxeVYVwDzCTLZD3+iJaqmntkBkoDndJy52yXQmMq5z1wbQVp +BL6+L9nTvmouy64jiHVSKOx8nnWThYfHsXoPv+rYywjeuK/v3hyaTAwogs36ooEn +SnuTBRvJcumN9Q0XIVlxKMVBcGyyAP+0yNKGz5NQgdECgYEA/I/Uq1E3epPJgEWR +Bub+LpCgwtrw/lgKncb/Q/AiE9qoXobUe4KNU8aGaNMb7uVNLckY7cOluLS6SQb3 +Mzwk2Jl0G3vk8rW46tZWvSYB8+zAR2Rz7seUOT9SE5OmvwpnHrnp3nRr1vvVd2bp +Q/ypwMLrwWQN51Kr+oTS74bUbrkCgYEA6bXVIUyao7z2Q3qAr6h+6JEWDbkJA7hJ +BjHIOXvxd1tMoJJX+X9+IE/2XoJaUkGCb0vrM/hi1cyQFmS4Or/J6IWSZu8oBpDr +EBmIK3PF1nrzNvWD28wM46c6ScehyWSm/u4bJWSm9liTX3dv5Kpa6ym7yLKc3c0B +ECpSJM+5SoMCgYEAq585Tukzn/IJPUcIk/4nv5C8DW0l0lAVdr2g/JOTNJajTwik +HwHJ86G1+Elsc9wRpAlBDWCjnm4BIFrBZGl8SEuOoJaCL4PZEotwCbxoG09IIbtb +JGkuifBDX9Y3ux3gkPqYt3e5SC99EVQ3MuHgoIJUHehVolmFUAkuJWIjvNECgYEA +5pU0VspRuELzZdgzpxvDOooLDDcHodfslGQBfFXBA1Xc4IACtHMJaa/7D3vkyUtA ++bYZtQjX2sEdWDq/WZdoCjXfIBfNkczhXt0R8G0lQFvGIu9QzUchYGrZo3mHMkBQ +Uy1xMw9/e4YgwQwCJcW+Nk7Sq00uX9enuN9IdHFOCykCgYAqAGMK6CH1tlpjvHrf +k+ZhigYxTXBlsVVvK1BIGGaiwzDpn65zeQp4aLOjSZkI1LuRi3tfTiZ321jRd64J +4lGk5Jurqv5grDmxROX/U50wEYbI9ncu/thU7syUdxDiqxHPI2RMG50mRcm3a55p +ZCNSqkMlcXyA0U1z8C1ILNUsbA== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICoTCCAYkCAgPoMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNVBAMMCUNMSS1Mb2dp +bjAiGA8yMDE4MDcxMzIzMjA0N1oYDzIwMTkwNzEzMjMyMDQ5WjAUMRIwEAYDVQQD +DAlDTEktTG9naW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDmkkyF +0BwipZowWd1AMkRkySx0y079JPxpsYhv4i1xXKdoa9bpFqwoXmJpeQM1JWnU4UeZ +zFeM86qKAhQvL4KV4kibcP2ENvu2NKFEdotO3uxPJ+6GlcYwMYzy+tUj008KnnRZ +fTrR78sJtIl3C6lnVL0ICihksG59P1sskRq3PvOjXLAdEZalwDjZ4ZPoNDZdj6nU +jB2l8zqupKAt5mR+bJ9Sox4yrDuNhMmFt5QsRDRe3wUqdV+C9OCWHmjlmsjrYw7p +9YmjBDvC5U7mF0Mk/XeYFzj0pkXKQVqBL6xqig+q5ob0szYfg19iDeFhS3iIsRcJ +GEnRVW/ANpsBZyKrAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAI4VlaFb9NsXMLdT +Cw5/pk0Xo2Qi6483RGTy8vzrw88IE7f3juB/JWG+rayjtW5bBRx2fae4/ZIdZ4zg +N2FDKn2PQPAc9m9pcKyUKUvWOC8ixSkrUmeQew0l1AXU0hsPSlJ7/7ZK4efoyB47 +hj71fsyKdyKbisZDcUFBq/S8PazdPF0YOD1W/4A2tW0cSMg+jmFWynuUTdWt3SU8 +CwBGqdiSKT5faJuYwIWnRXDEQS3ObRn1OFEfFdd4d2sxjxydWKRgnINnGlBdiFAT +KzCozVr+75cO2ErH6x5C0hLQGG5BxXbaijyxyvaRNokTMVVv6OaDEnjzCGfJ72Yf +2wgitNc= +-----END CERTIFICATE----- diff --git a/work-with-data/dataprep/data/chicago-aldermen-2015.csv b/work-with-data/dataprep/data/chicago-aldermen-2015.csv new file mode 100644 index 00000000..a0cae0ba --- /dev/null +++ b/work-with-data/dataprep/data/chicago-aldermen-2015.csv @@ -0,0 +1,54 @@ +"Retrieved from https://en.wikipedia.org/wiki/Chicago_City_Council on November 6, 2018" + + +Ward,Name,Took Office,Party +1,Proco Joe Moreno,2010*,Dem +2,Brian Hopkins,2015,Dem +3,Pat Dowell,2007,Dem +4,Sophia King,2016*,Dem +5,Leslie Hairston,1999,Dem +6,Roderick Sawyer,2011,Dem +7,Gregory Mitchell,2015,Dem +8,Michelle A. Harris,2006*,Dem +9,Anthony Beale,1999,Dem +10,Susie Sadlowski Garza,2015,Dem +11,Patrick Daley Thompson,2015,Dem +12,George Cardenas,2003,Dem +13,Marty Quinn,2011,Dem +14,Edward M. Burke,1969,Dem +15,Raymond Lopez,2015,Dem +16,Toni Foulkes,2007,Dem +17,David H. Moore,2015,Dem +18,Derrick Curtis,2015,Dem +19,Matthew O'Shea,2011,Dem +20,Willie Cochran,2007,Dem +21,Howard Brookins Jr.,2003,Dem +22,Ricardo Muñoz,1993*,Dem +23,Silvana Tabares,2018*,Dem +24,"Michael Scott, Jr.",2015,Dem +25,Daniel Solis,1996*,Dem +26,Roberto Maldonado,2009*,Dem +27,"Walter Burnett, Jr.",1995,Dem +28,Jason Ervin,2011*,Dem +29,Chris Taliaferro,2015,Dem +30,Ariel Reboyras,2003,Dem +31,Milly Santiago,2015,Dem +32,Scott Waguespack,2007,Dem +33,Deb Mell,2013*,Dem +34,Carrie Austin,1994*,Dem +35,Carlos Ramirez-Rosa,2015,Dem +36,Gilbert Villegas,2015,Dem +37,Emma Mitts,2000*,Dem +38,Nicholas Sposato,2011,Ind +39,Margaret Laurino,1994*,Dem +40,Patrick J. O'Connor,1983,Dem +41,Anthony Napolitano,2015,Rep +42,Brendan Reilly,2007,Dem +43,Michele Smith,2011,Dem +44,Thomas M. Tunney,2002*,Dem +45,John Arena,2011,Dem +46,James Cappleman,2011,Dem +47,Ameya Pawar,2011,Dem +48,Harry Osterman,2011,Dem +49,Joe Moore,1991,Dem +50,Debra Silverstein,2011,Dem diff --git a/work-with-data/dataprep/data/crime-dirty.csv b/work-with-data/dataprep/data/crime-dirty.csv new file mode 100644 index 00000000..ef7beb0b --- /dev/null +++ b/work-with-data/dataprep/data/crime-dirty.csv @@ -0,0 +1,15 @@ +File updated 11/2/2018 + + + +ID|Case Number|Date|Block|IUCR|Primary Type|Description|Location Description|Arrest|Domestic|Beat|District|Ward|Community Area|FBI Code|X Coordinate|Y Coordinate|Year|Updated On|Latitude|Longitude|Location +10140490|HY329907|07/05/2015 11:50:00 PM|050XX N NEWLAND AVE|0820|THEFT|$500 AND UNDER|STREET|false|false|1613|016|41|10|06|1129230|1933315|2015|07/12/2015 12:42:46 PM|41.973309466|-87.800174996|(41.973309466, -87.800174996) +10139776|HY329265|07/05/2015 11:30:00 PM|011XX W MORSE AVE|0460|BATTERY|SIMPLE|STREET|false|true|2431|024|49|1|08B|1167370|1946271|2015|07/12/2015 12:42:46 PM|42.008124017|-87.65955018|(42.008124017, -87.65955018) +10140270|HY329253|07/05/2015 11:20:00 PM|121XX S FRONT AVE|0486|BATTERY|DOMESTIC BATTERY SIMPLE|STREET|false|true|0532||9|53|08B|||2015|07/12/2015 12:42:46 PM||| +10139885|HY329308|07/05/2015 11:19:00 PM|051XX W DIVISION ST|0610|BURGLARY|FORCIBLE ENTRY|SMALL RETAIL STORE|false|false|1531|015|37|25|05|1141721|1907465|2015|07/12/2015 12:42:46 PM|41.902152027|-87.754883404|(41.902152027, -87.754883404) +10140379|HY329556|07/05/2015 11:00:00 PM|012XX W LAKE ST|0930|MOTOR VEHICLE THEFT|THEFT/RECOVERY: AUTOMOBILE|STREET|false|false|1215|012|27|28|07|1168413|1901632|2015|07/12/2015 12:42:46 PM|41.885610142|-87.657008701|(41.885610142, -87.657008701) +10140868|HY330421|07/05/2015 10:54:00 PM|118XX S PEORIA ST|1320|CRIMINAL DAMAGE|TO VEHICLE|VEHICLE NON-COMMERCIAL|false|false|0524|005|34|53|14|1172409|1826485|2015|07/12/2015 12:42:46 PM|41.6793109|-87.644545209|(41.6793109, -87.644545209) +10139762|HY329232|07/05/2015 10:42:00 PM|026XX W 37TH PL|1020|ARSON|BY FIRE|VACANT LOT/LAND|false|false|0911|009|12|58|09|1159436|1879658|2015|07/12/2015 12:42:46 PM|41.825500607|-87.690578042|(41.825500607, -87.690578042) +10139722|HY329228|07/05/2015 10:30:00 PM|016XX S CENTRAL PARK AVE|1811|NARCOTICS|POSS: CANNABIS 30GMS OR LESS|ALLEY|true|false|1021|010|24|29|18|1152687|1891389|2015|07/12/2015 12:42:46 PM|41.857827814|-87.715028789|(41.857827814, -87.715028789) +10139774|HY329209|07/05/2015 10:15:00 PM|048XX N ASHLAND AVE|1310|CRIMINAL DAMAGE|TO PROPERTY|APARTMENT|false|false|2032|020|46|3|14|1164821|1932394|2015|07/12/2015 12:42:46 PM|41.970099796|-87.669324377|(41.970099796, -87.669324377) +10139697|HY329177|07/05/2015 10:10:00 PM|058XX S ARTESIAN AVE|1320|CRIMINAL DAMAGE|TO VEHICLE|ALLEY|false|false|0824|008|16|63|14|1160997|1865851|2015|07/12/2015 12:42:46 PM|41.787580282|-87.685233078|(41.787580282, -87.685233078) diff --git a/work-with-data/dataprep/data/crime-full.csv b/work-with-data/dataprep/data/crime-full.csv new file mode 100644 index 00000000..e46bda18 --- /dev/null +++ b/work-with-data/dataprep/data/crime-full.csv @@ -0,0 +1,1001 @@ +ID,Case Number,Date,Block,IUCR,Primary Type,Description,Location Description,Damage Cost,Arrest,Domestic,Beat,District,Ward,Community Area,FBI Code,X Coordinate,Y Coordinate,Year,Updated On,Latitude,Longitude,Location +2705685,HJ329710,2003-04-26 00:00:00,105XX S WOOD ST,0460,BATTERY,SIMPLE,RESIDENCE,4615,False,False,2212,NA,19,72,08B,1166261,1834693,2003,ERROR,41.701967722,-87.666817049,"(41.701967722, -87.666817049)" +8367900,HT600798,2011-11-22 15:15:00,063XX S PULASKI RD,0890,THEFT,FROM BUILDING,LIBRARY,3882,False,False,813,8,13,65,06,1150746,1862490,2011,ERROR,41.778563069,-87.722907063,"(41.778563069, -87.722907063)" +8726660,HV402430,2012-07-26 19:30:00,019XX W BELMONT AVE,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,4566,False,False,1931,19,32,5,05,1162975,1921223,2012,ERROR,41.939485082,-87.676427052,"(41.939485082, -87.676427052)" +9103375,HW247768,2013-04-25 14:20:00,047XX S ASHLAND AVE,0460,BATTERY,SIMPLE,SIDEWALK,2469,False,False,931,9,20,61,08B,1166425,1873469,2013,ERROR,41.808370972,-87.665113707,"(41.808370972, -87.665113707)" +3807148,HL176623,2005-02-12 01:24:03,078XX S EAST END AVE,0920,MOTOR VEHICLE THEFT,ATT: AUTOMOBILE,ALLEY,989,False,False,414,4,8,43,07,1188944,1853066,2005,ERROR,41.751873397,-87.583173082,"(41.751873397, -87.583173082)" +4241820,HL534761,2005-08-07 21:12:26,068XX S MARSHFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3807,False,False,725,7,17,67,08B,1166478,1859475,2005,ERROR,41.769968592,-87.665318117,"(41.769968592, -87.665318117)" +4824796,HM433465,2006-06-23 22:15:00,105XX S STATE ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1504,False,False,512,5,34,49,14,1178075,1835093,2006,ERROR,41.702806335,-87.623545729,"(41.702806335, -87.623545729)" +8172753,HT407165,2011-07-20 17:50:00,021XX W 21ST PL,0820,THEFT,$500 AND UNDER,RESIDENCE,262,False,False,1223,12,25,31,06,1162541,1889761,2011,2011-03-08 09:56:44,41.853160001,-87.678904133,"(41.853160001, -87.678904133)" +8154927,HT390078,2011-07-10 15:50:00,046XX W NORTH AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,2972,True,False,2533,25,37,25,06,1144852,1910260,2011,2011-11-07 10:34:01,41.909763298,-87.743312036,"(41.909763298, -87.743312036)" +6944181,HR349635,2009-05-30 18:13:00,015XX W 63RD ST,031A,ROBBERY,ARMED: HANDGUN,ALLEY,1296,False,False,713,7,16,67,03,1167116,1862990,2009,2009-12-06 18:29:17,41.779600581,-87.662879029,"(41.779600581, -87.662879029)" +5836733,HN643782,2007-10-12 14:00:00,027XX W SUMMERDALE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,3105,False,False,2011,20,40,4,14,1157009,1935459,2007,ERROR,41.978672864,-87.697965904,"(41.978672864, -87.697965904)" +7740163,HS548309,2010-10-04 21:30:00,057XX N WASHTENAW AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,APARTMENT,594,True,False,2011,20,40,2,18,1157257,1938221,2010,2010-04-10 23:26:24,41.986246871,-87.696978408,"(41.986246871, -87.696978408)" +4984650,HM597309,2006-09-11 18:30:00,052XX S STATE ST,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1591,False,False,232,2,3,40,06,1177212,1869954,2006,ERROR,41.798488523,-87.625655865,"(41.798488523, -87.625655865)" +3941717,HL312501,2005-04-22 08:00:00,069XX S EBERHART AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1397,False,True,322,3,6,69,08B,1180740,1858964,2005,ERROR,41.76825049,-87.613055702,"(41.76825049, -87.613055702)" +5084563,HM687682,2006-10-28 14:30:00,022XX S PULASKI RD,0560,ASSAULT,SIMPLE,STREET,2224,False,False,1013,10,22,29,08A,1150013,1889063,2006,ERROR,41.851497405,-87.724904562,"(41.851497405, -87.724904562)" +8136835,HT371007,2011-06-28 23:20:00,011XX N CHRISTIANA AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,538,True,False,1121,11,26,23,18,1153742,1907328,2011,ERROR,41.901545171,-87.710731851,"(41.901545171, -87.710731851)" +1815636,G631826,2001-10-20 18:30:00,046XX S HALSTED ST,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,STREET,2501,True,False,921,NA,NA,NA,26,1171705,1874218,2001,ERROR,41.810312019,-87.645725963,"(41.810312019, -87.645725963)" +9276577,HW420770,2013-08-23 16:40:00,007XX S ALBANY AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,658,True,False,1134,11,24,27,18,1155766,1896834,2013,ERROR,41.872708075,-87.703580324,"(41.872708075, -87.703580324)" +1603336,G368431,2001-06-24 19:52:14,001XX W CHICAGO AV,5001,OTHER OFFENSE,OTHER CRIME INVOLVING PROPERTY,PARKING LOT/GARAGE(NON.RESID.),3948,True,False,1832,NA,NA,NA,26,1175257,1905662,2001,ERROR,41.896517917,-87.631755414,"(41.896517917, -87.631755414)" +7210353,HR608426,2009-10-26 08:40:00,015XX S PULASKI RD,2024,NARCOTICS,POSS: HEROIN(WHITE),ABANDONED BUILDING,1124,True,False,1012,10,24,29,18,1149936,1891869,2009,2009-04-11 09:53:16,41.859198911,-87.725114223,"(41.859198911, -87.725114223)" +4471848,HL301576,2005-04-17 20:50:03,006XX E GRAND AVE,1330,CRIMINAL TRESPASS,TO LAND,OTHER,3257,False,False,1834,18,42,8,26,1180772,1904096,2005,2007-11-06 15:52:33,41.892095212,-87.611548469,"(41.892095212, -87.611548469)" +7637700,HS441827,2010-08-02 12:45:00,028XX W 22ND PL,1570,SEX OFFENSE,PUBLIC INDECENCY,SIDEWALK,2297,False,False,1033,10,12,30,17,1157857,1888970,2010,ERROR,41.851086084,-87.696117577,"(41.851086084, -87.696117577)" +4703044,HM309199,2006-04-24 00:00:00,012XX N CLARK ST,0820,THEFT,$500 AND UNDER,STREET,20,False,False,1821,18,42,8,06,1175274,1908508,2006,2014-04-12 12:43:35,41.904327102,-87.631607477,"(41.904327102, -87.631607477)" +3961270,HL324634,2005-04-29 08:30:00,005XX W DIVISION ST,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,CHA HALLWAY/STAIRWELL/ELEVATOR,4726,False,False,1821,18,27,8,04B,1172300,1908305,2005,ERROR,41.903836297,-87.642537685,"(41.903836297, -87.642537685)" +2281333,HH546276,2002-07-30 13:30:00,069XX S EMERALD AVE,2014,NARCOTICS,MANU/DELIVER: HEROIN (WHITE),SIDEWALK,864,True,False,732,NA,6,68,18,1172538,1859044,2002,ERROR,41.76865457,-87.643117449,"(41.76865457, -87.643117449)" +1837655,G672065,2001-11-07 21:45:40,062XX S ST LAWRENCE AV,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4001,True,False,313,NA,NA,NA,14,1181266,1863792,2001,ERROR,41.78148689,-87.610979035,"(41.78148689, -87.610979035)" +1842160,G679483,2001-11-11 03:30:00,021XX N CAMPBELL AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1986,False,False,1431,NA,NA,NA,07,1159571,1914405,2001,ERROR,41.920846924,-87.689126006,"(41.920846924, -87.689126006)" +2636288,HJ216645,2003-03-03 20:48:00,045XX W WASHINGTON BLVD,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,1393,True,False,1113,NA,28,26,16,1146373,1900156,2003,ERROR,41.882007967,-87.737982025,"(41.882007967, -87.737982025)" +1496183,G233892,2001-04-24 09:00:00,004XX E MC FETRIDGE DR,0820,THEFT,$500 AND UNDER,PARK PROPERTY,187,False,False,133,NA,NA,NA,06,1179031,1894246,2001,2014-04-12 12:43:35,41.865106307,-87.618243723,"(41.865106307, -87.618243723)" +3931945,HL305935,2005-04-19 16:00:00,015XX N ARTESIAN AVE,0810,THEFT,OVER $500,RESIDENTIAL YARD (FRONT/BACK),1386,False,False,1423,14,1,24,06,1159834,1910187,2005,2014-04-12 12:43:35,41.909266991,-87.688276249,"(41.909266991, -87.688276249)" +5621453,HN427508,2007-06-25 13:25:00,040XX W DICKENS AVE,2022,NARCOTICS,POSS: COCAINE,STREET,2783,True,False,2525,25,30,20,18,1149318,1913614,2007,2007-01-07 02:00:46,41.918881522,-87.726818542,"(41.918881522, -87.726818542)" +1709847,G508926,2001-08-26 02:00:00,010XX N MONTICELLO AV,0460,BATTERY,SIMPLE,RESIDENCE,3761,False,True,1112,NA,NA,NA,08B,1151889,1906869,2001,ERROR,41.900322332,-87.717550266999993,"(41.900322332, -87.717550267)" +2306701,HH595350,2002-08-21 05:45:00,001XX W POLK ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,1890,False,False,131,NA,2,32,07,1175097,1896746,2002,ERROR,41.872055484,-87.63261045,"(41.872055484, -87.63261045)" +6581196,HP653741,2008-10-07 03:00:00,034XX W SCHOOL ST,0820,THEFT,$500 AND UNDER,RESIDENTIAL YARD (FRONT/BACK),312,False,False,1732,17,35,21,06,1152747,1921690,2008,ERROR,41.940975485,-87.714005626,"(41.940975485, -87.714005626)" +5443942,HN272087,2007-04-08 15:44:18,059XX S LAFLIN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1938,False,False,713,7,16,67,14,1167399,1865013,2007,ERROR,41.785145883,-87.661783592,"(41.785145883, -87.661783592)" +3792137,HK836619,2004-12-30 18:30:00,131XX S VERNON AVE,2027,NARCOTICS,POSS: CRACK,RESIDENCE PORCH/HALLWAY,4455,True,False,533,5,9,54,18,1181519,1818462,2004,ERROR,41.657089903,-87.61144499,"(41.657089903, -87.61144499)" +9206054,HW352076,2013-07-07 15:00:00,016XX W 38TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,975,True,True,912,9,11,59,08B,1165971,1879568,2013,2013-08-07 12:07:35,41.825116974,-87.666605306,"(41.825116974, -87.666605306)" +4720801,HM327230,2006-05-03 05:03:58,026XX E 73RD ST,0334,ROBBERY,ATTEMPT: ARMED-KNIFE/CUT INSTR,SIDEWALK,4270,False,False,334,3,7,43,03,1194873,1857481,2006,2006-07-05 03:57:33,41.763844577,-87.561301231,"(41.763844577, -87.561301231)" +7034576,HR441456,2009-07-21 21:05:00,107XX S HALSTED ST,0326,ROBBERY,AGGRAVATED VEHICULAR HIJACKING,GAS STATION,1442,True,False,2233,22,34,75,03,1172840,1833829,2009,ERROR,41.699454527,-87.642752065,"(41.699454527, -87.642752065)" +8348862,HT582100,2011-11-09 15:00:00,108XX S BENSLEY AVE,0560,ASSAULT,SIMPLE,SIDEWALK,844,False,False,434,4,10,51,08A,1194612,1833847,2011,ERROR,41.698997218,-87.563033068,"(41.698997218, -87.563033068)" +2882234,HJ552553,2003-08-08 22:00:00,109XX S WABASH AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,664,False,False,513,5,9,49,14,1178490,1832061,2003,ERROR,41.694476704,-87.622117791,"(41.694476704, -87.622117791)" +8571720,HV245210,2011-12-26 09:00:00,038XX W NORTH AVE,0842,THEFT,AGG: FINANCIAL ID THEFT,BANK,4830,False,False,2535,25,30,23,06,1150240,1910394,2011,ERROR,41.910027597,-87.723515111,"(41.910027597, -87.723515111)" +3479397,HK551539,2004-08-11 11:00:00,014XX S CHRISTIANA AVE,0810,THEFT,OVER $500,STREET,3767,False,False,1021,10,24,29,06,1154306,1892825,2004,2014-04-12 12:43:35,41.861736212,-87.709047718,"(41.861736212, -87.709047718)" +6571624,HP644036,2008-10-23 18:10:00,003XX S PLYMOUTH CT,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,STREET,3266,False,False,123,1,2,32,03,1176091,1898936,2008,2008-06-11 15:42:26,41.878042652,-87.628895109,"(41.878042652, -87.628895109)" +3425217,HK489162,2004-07-11 19:46:59,060XX S MICHIGAN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,1195,False,True,311,3,20,40,08B,1178229,1865098,2004,ERROR,41.785140156,-87.62207368,"(41.785140156, -87.62207368)" +9297900,HW443063,2013-09-08 14:30:00,073XX S MICHIGAN AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,3308,False,False,323,3,6,69,03,1178393,1856302,2013,2013-02-10 11:54:38,41.760999284,-87.621739229,"(41.760999284, -87.621739229)" +5343960,HM744635,2006-11-28 07:45:00,042XX S CALUMET AVE,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,2763,True,False,214,2,3,38,16,1179097,1876982,2006,ERROR,41.81773117,-87.61852889,"(41.81773117, -87.61852889)" +7231348,HR644530,2009-11-15 13:15:00,048XX N KENMORE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2613,True,True,2024,20,46,3,08B,1168339,1932383,2009,ERROR,41.969994063,-87.656388912,"(41.969994063, -87.656388912)" +8276922,HT511056,2011-09-23 18:15:00,076XX S CICERO AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,3184,True,False,833,8,13,65,06,1145766,1853738,2011,ERROR,41.75464162,-87.741385158,"(41.75464162, -87.741385158)" +7096630,HR505081,2009-08-26 18:00:00,081XX S PAULINA ST,0460,BATTERY,SIMPLE,RESIDENCE PORCH/HALLWAY,2842,False,False,614,6,18,71,08B,1166381,1850921,2009,ERROR,41.746497306,-87.66591694,"(41.746497306, -87.66591694)" +3509426,HK586658,2004-08-28 00:00:00,083XX S KINGSTON AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,606,False,False,423,4,7,46,07,1194581,1850155,2004,ERROR,41.743748652,-87.562611996,"(41.743748652, -87.562611996)" +9478072,HX130921,2014-01-21 17:00:00,068XX S WOLCOTT AVE,4387,OTHER OFFENSE,VIOLATE ORDER OF PROTECTION,RESIDENCE,1761,False,True,726,7,17,67,26,1164830,1859219,2014,ERROR,41.769301059,-87.671366222,"(41.769301059, -87.671366222)" +9320325,HW464485,2013-09-23 21:30:00,016XX E 77TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1715,False,True,414,4,8,43,14,1188749,1854298,2013,ERROR,41.755258781,-87.583848327,"(41.755258781, -87.583848327)" +8528788,HV205948,2012-03-20 09:50:00,111XX S EWING AVE,1305,CRIMINAL DAMAGE,CRIMINAL DEFACEMENT,RESIDENCE-GARAGE,1676,False,False,433,4,10,52,14,1202171,1831712,2012,ERROR,41.692949738,-87.535428598,"(41.692949738, -87.535428598)" +4290691,HL605721,2005-09-11 14:00:00,001XX N DEARBORN ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2621,False,False,122,1,42,32,06,1175948,1901601,2005,2014-04-12 12:43:35,41.885358785,-87.629339896,"(41.885358785, -87.629339896)" +9191309,HW336113,2013-06-25 11:28:00,013XX N ASHLAND AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,3808,True,False,1433,14,1,24,06,1165434,1909298,2013,ERROR,41.906710108,-87.66772974,"(41.906710108, -87.66772974)" +6943981,HR348351,2009-05-29 22:00:00,057XX N MC VICKER AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1332,False,False,1622,16,45,10,14,1134948,1938056,2009,ERROR,41.986219618,-87.779035389,"(41.986219618, -87.779035389)" +1432501,G153916,2001-03-18 01:15:59,023XX N HAMLIN AV,0460,BATTERY,SIMPLE,RESIDENCE,2320,True,False,2525,NA,NA,NA,08B,1150616,1915362,2001,ERROR,41.923652901,-87.722003735,"(41.923652901, -87.722003735)" +8288964,HT523191,2011-10-01 16:10:00,021XX E 70TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4671,True,False,331,3,5,43,18,1191556,1859009,2011,2011-01-10 19:38:17,41.768118581,-87.573409074,"(41.768118581, -87.573409074)" +7910721,HT140388,2011-01-28 23:30:00,029XX W ADAMS ST,0530,ASSAULT,AGGRAVATED: OTHER DANG WEAPON,APARTMENT,4639,False,False,1124,11,2,27,04A,1156613,1898917,2011,ERROR,41.878406942,-87.700414194,"(41.878406942, -87.700414194)" +2089112,HH309698,2002-04-15 20:15:00,010XX N LAWLER AV,2027,NARCOTICS,POSS: CRACK,SIDEWALK,1200,True,False,1531,NA,NA,NA,18,1142456,1906817,2002,ERROR,41.900360209,-87.752199731,"(41.900360209, -87.752199731)" +9419206,HW562896,2013-12-08 01:39:00,011XX S CANAL ST,0810,THEFT,OVER $500,RESTAURANT,4414,False,False,124,1,2,28,06,1173358,1895046,2013,ERROR,41.867429345,-87.639045471,"(41.867429345, -87.639045471)" +8846059,HV519322,2012-10-14 20:00:00,018XX W 103RD ST,0610,BURGLARY,FORCIBLE ENTRY,OTHER,4604,False,False,2213,22,19,72,05,1165953,1836420,2012,ERROR,41.70671343,-87.667895994,"(41.70671343, -87.667895994)" +6454875,HP535283,2008-08-26 03:22:00,019XX W 19TH ST,0560,ASSAULT,SIMPLE,APARTMENT,1503,False,False,1223,12,25,31,08A,1163581,1890708,2008,ERROR,41.855736847,-87.675060338,"(41.855736847, -87.675060338)" +8223778,HT457480,2011-08-20 15:15:00,007XX E 43RD ST,0560,ASSAULT,SIMPLE,CHA PARKING LOT/GROUNDS,4240,False,False,213,2,4,38,08A,1182085,1876707,2011,ERROR,41.816907818,-87.607576648,"(41.816907818, -87.607576648)" +1622655,G400595,2001-07-09 14:55:00,035XX N HERMITAGE AV,0560,ASSAULT,SIMPLE,ALLEY,2256,False,False,1923,NA,NA,NA,08A,1164066,1923605,2001,ERROR,41.945998404,-87.672349817,"(41.945998404, -87.672349817)" +7843346,HS655355,2010-12-10 22:15:00,028XX S SPAULDING AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,SIDEWALK,2971,True,False,1032,10,22,30,15,1154782,1885145,2010,ERROR,41.840651893,-87.707505856,"(41.840651893, -87.707505856)" +6043466,HP146623,2008-01-28 00:00:07,091XX S STONY ISLAND AVE,031A,ROBBERY,ARMED: HANDGUN,SMALL RETAIL STORE,3209,False,False,413,4,8,48,03,1188345,1844791,2008,2008-08-02 08:13:34,41.729180293,-87.585631578,"(41.729180293, -87.585631578)" +9633417,HX284237,2014-05-31 15:00:00,083XX S INGLESIDE AVE,0820,THEFT,$500 AND UNDER,APARTMENT,380,False,False,632,6,8,44,06,1183965,1849926,2014,2014-07-06 12:40:43,41.743374586,-87.601516477,"(41.743374586, -87.601516477)" +9050651,HW196019,2013-03-16 14:30:00,037XX N SEMINARY AVE,0810,THEFT,OVER $500,OTHER,3835,False,False,1923,19,44,6,06,1168275,1925255,2013,ERROR,41.950435964,-87.656831163,"(41.950435964, -87.656831163)" +1724326,G530529,2001-08-24 09:00:00,068XX N FRANCISCO AV,0810,THEFT,OVER $500,CONSTRUCTION SITE,4601,False,False,2411,NA,NA,NA,06,1155826,1945498,2001,2014-04-12 12:43:35,42.006244328,-87.70204429,"(42.006244328, -87.70204429)" +7313924,HS117962,2009-11-23 09:18:00,001XX W JACKSON BLVD,1110,DECEPTIVE PRACTICE,BOGUS CHECK,BANK,2873,True,False,112,1,2,32,11,1175229,1898922,2009,2011-03-01 19:34:38,41.878023606,-87.632060555,"(41.878023606, -87.632060555)" +8348182,HT581427,2011-11-08 17:45:00,049XX S WABASH AVE,0820,THEFT,$500 AND UNDER,"SCHOOL, PUBLIC, BUILDING",404,True,False,231,2,3,38,06,1177513,1872176,2011,2011-09-11 12:54:43,41.804579093,-87.624484847,"(41.804579093, -87.624484847)" +8457513,HV134404,2012-01-27 10:40:00,095XX S LAFAYETTE AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4775,False,False,511,5,21,49,07,1177575,1841925,2012,ERROR,41.721565569,-87.625170844,"(41.721565569, -87.625170844)" +3991779,HL277064,2005-04-05 22:00:00,004XX S CICERO AVE,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,4442,True,False,1533,15,24,25,16,1144422,1897554,2005,ERROR,41.874904679,-87.745211632,"(41.874904679, -87.745211632)" +8231576,HT460583,2011-08-03 11:00:00,025XX W 63RD ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,OTHER,2479,False,False,825,8,15,66,26,1160154,1862826,2011,ERROR,41.779296661,-87.688407212,"(41.779296661, -87.688407212)" +4496745,HL799743,2005-12-20 09:05:00,073XX S SACRAMENTO AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4808,False,False,835,8,18,66,05,1157607,1855700,2005,ERROR,41.75979389,-87.697937855,"(41.75979389, -87.697937855)" +5033246,HM641247,2006-10-04 20:00:00,080XX S LAFLIN ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4181,False,False,612,6,21,71,05,1167772,1851438,2006,ERROR,41.747886306,-87.660805171,"(41.747886306, -87.660805171)" +5208608,HM778355,2006-12-16 19:10:00,068XX S HALSTED ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,TAVERN/LIQUOR STORE,3007,True,False,723,7,6,68,15,1172116,1859172,2006,2007-01-01 07:32:02,41.7690151,-87.644660529,"(41.7690151, -87.644660529)" +2528826,HJ107508,2003-01-04 22:02:46,030XX W 47TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4398,False,False,912,NA,14,58,14,1157054,1873373,2003,ERROR,41.808302333,-87.699487137,"(41.808302333, -87.699487137)" +1613083,G380569,2001-06-30 03:34:50,010XX E 133 ST,0560,ASSAULT,SIMPLE,CHA APARTMENT,4421,True,False,533,NA,NA,NA,08A,1185848,1817236,2001,ERROR,41.653625,-87.595643042,"(41.653625, -87.595643042)" +5350380,HN205637,2007-03-03 10:00:00,032XX W 63RD ST,0460,BATTERY,SIMPLE,APARTMENT,1902,False,False,823,8,15,66,08B,1155671,1862624,2007,2007-08-03 21:51:06,41.77883343,-87.704847919,"(41.77883343, -87.704847919)" +5570467,HN377874,2007-04-02 12:00:00,064XX S LANGLEY AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,4605,False,True,312,3,20,42,26,1181963,1862332,2007,ERROR,41.777464417,-87.608468819,"(41.777464417, -87.608468819)" +3864729,HL237399,2005-03-16 10:00:00,046XX S HALSTED ST,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,3954,True,False,921,9,11,61,06,1171713,1873921,2005,ERROR,41.809496845,-87.645705337,"(41.809496845, -87.645705337)" +4208971,HL527767,2005-07-04 22:30:00,056XX S MICHIGAN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1863,False,True,233,2,20,40,08B,1178081,1867582,2005,ERROR,41.791959854,-87.622541015,"(41.791959854, -87.622541015)" +4616724,HM210940,2006-03-03 10:26:14,0000X W RANDOLPH ST,1330,CRIMINAL TRESPASS,TO LAND,RESTAURANT,1416,True,False,122,1,42,32,26,1175995,1901321,2006,2006-07-03 03:46:57,41.884589391,-87.629175742,"(41.884589391, -87.629175742)" +9574311,HX225021,2014-04-15 20:00:00,088XX S LOOMIS ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4758,False,False,2222,22,21,71,07,1168581,1846266,2014,2014-06-05 00:39:53,41.733676216,-87.657989464,"(41.733676216, -87.657989464)" +3138687,HK129812,2004-01-16 21:30:00,115XX S YALE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4990,False,True,522,5,34,53,08B,1176719,1828319,2004,ERROR,41.684248043,-87.628713951,"(41.684248043, -87.628713951)" +8344443,HT528932,2011-10-05 05:00:00,052XX W SCHOOL ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2954,False,False,1634,16,38,15,14,1140574,1921391,2011,2011-08-11 10:43:52,41.940387619,-87.758753823,"(41.940387619, -87.758753823)" +1796827,G622864,2001-10-15 20:30:00,030XX S GRATTEN AV,0810,THEFT,OVER $500,OTHER,2409,False,False,923,NA,NA,NA,06,1169291,1884678,2001,2014-04-12 12:43:35,41.839067958,-87.654276965,"(41.839067958, -87.654276965)" +6321465,HP407211,2008-06-21 01:00:00,013XX S KEDZIE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,1039,False,False,1022,10,24,29,08B,1155195,1893805,2008,ERROR,41.864407654,-87.705758038,"(41.864407654, -87.705758038)" +4048269,HL397796,2005-06-04 01:00:00,106XX S HALSTED ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,1253,False,True,2233,22,34,73,08B,1172820,1834491,2005,ERROR,41.701271596,-87.642805861,"(41.701271596, -87.642805861)" +3192419,HK199140,2004-02-23 08:15:00,068XX S STEWART AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",2632,True,False,722,7,6,68,08B,1174671,1859859,2004,ERROR,41.770843786,-87.635274724,"(41.770843786, -87.635274724)" +2000440,HH198576,2002-02-20 16:32:03,064XX W DIVERSEY AV,0620,BURGLARY,UNLAWFUL ENTRY,APPLIANCE STORE,4375,False,False,2512,NA,NA,NA,05,NA,NA,2002,ERROR,NA,NA, +9584763,HX234897,2014-04-23 21:50:00,026XX N RUTHERFORD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1333,False,False,2512,25,36,18,08B,1130931,1916809,2014,ERROR,41.927985963,-87.794301643,"(41.927985963, -87.794301643)" +6633395,HP703001,2008-11-26 10:25:55,001XX N STATE ST,0850,THEFT,ATTEMPT THEFT,SIDEWALK,1180,True,False,122,1,42,32,06,1176393,1900887,2008,2008-01-12 07:38:55,41.8833895,-87.627727352,"(41.8833895, -87.627727352)" +10025851,HY215334,2015-04-09 08:09:00,037XX N KILPATRICK AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4106,True,True,1731,17,38,15,08B,1144437,1924607,2015,ERROR,41.949140682,-87.744474617,"(41.949140682, -87.744474617)" +4257303,HL576564,2005-08-28 00:01:00,018XX N KEDVALE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1447,False,False,2534,25,30,20,14,1148380,1912201,2005,ERROR,41.915022267,-87.730301385,"(41.915022267, -87.730301385)" +5994905,HP103223,2008-01-02 20:45:00,029XX W WARREN BLVD,2027,NARCOTICS,POSS: CRACK,SIDEWALK,2969,True,False,1331,12,2,27,18,1156699,1900170,2008,2008-03-01 08:43:16,41.881843556,-87.700064472,"(41.881843556, -87.700064472)" +6422343,HP506170,2008-08-05 23:00:00,008XX E 101ST ST,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,111,False,False,511,5,8,50,06,1183373,1838023,2008,2008-11-08 08:34:38,41.710725164,-87.604055073,"(41.710725164, -87.604055073)" +20991,HW370238,2013-07-19 21:43:00,038XX W HIRSCH ST,0110,HOMICIDE,FIRST DEGREE MURDER,STREET,3308,False,False,2535,25,30,23,01A,1150202,1908997,2013,2013-03-10 07:24:17,41.906194835,-87.723691183,"(41.906194835, -87.723691183)" +6084521,HP178194,2008-02-16 03:00:00,066XX S ASHLAND AVE,0313,ROBBERY,ARMED: OTHER DANGEROUS WEAPON,SIDEWALK,3004,False,False,725,7,17,67,03,1166856,1860593,2008,ERROR,41.773028465,-87.663900629,"(41.773028465, -87.663900629)" +10080403,HY268040,2015-05-19 11:00:00,049XX W IRVING PARK RD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3481,False,True,1624,16,45,15,08B,1142440,1926175,2015,ERROR,41.95348083,-87.751776252,"(41.95348083, -87.751776252)" +6924204,HR316222,2009-05-12 09:00:00,0000X W 95TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,CTA GARAGE / OTHER PROPERTY,3480,True,False,634,6,21,49,18,1177743,1841988,2009,ERROR,41.721734655,-87.624553594,"(41.721734655, -87.624553594)" +6386680,HP461038,2008-07-18 22:43:31,045XX W HARRISON ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2401,True,False,1131,11,24,26,18,1146348,1896935,2008,ERROR,41.873169637,-87.738155845,"(41.873169637, -87.738155845)" +6784849,HR199128,2009-03-04 20:12:00,066XX N CLARK ST,2170,NARCOTICS,POSSESSION OF DRUG EQUIPMENT,ALLEY,3928,True,False,2432,24,40,1,18,1164013,1944171,2009,2009-04-03 20:55:02,42.002433404,-87.671961019,"(42.002433404, -87.671961019)" +7712756,HS519770,2010-09-17 12:11:00,060XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,SMALL RETAIL STORE,37,False,False,825,8,15,66,06,1161368,1864419,2010,ERROR,41.783642999,-87.683912427,"(41.783642999, -87.683912427)" +7906876,HT136770,2011-01-26 15:55:00,0000X S KOSTNER AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,4097,True,False,1113,11,28,26,26,1147028,1899356,2011,ERROR,41.87980018,-87.735597307,"(41.87980018, -87.735597307)" +7991866,HT223888,2011-03-28 19:15:00,110XX S PRINCETON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,544,True,False,513,5,34,49,18,1176291,1831621,2011,ERROR,41.693318823,-87.630182058,"(41.693318823, -87.630182058)" +7123089,HR531884,2009-09-11 18:13:00,013XX S MILLARD AVE,051A,ASSAULT,AGGRAVATED: HANDGUN,STREET,4647,True,False,1011,10,24,29,04A,1152210,1893660,2009,ERROR,41.864069113,-87.71671981,"(41.864069113, -87.71671981)" +2000324,HH200986,2002-02-21 18:47:45,014XX N AVERS AV,0460,BATTERY,SIMPLE,RESIDENCE,3701,False,True,2535,NA,NA,NA,08B,1150481,1909461,2002,ERROR,41.907462651,-87.722654169,"(41.907462651, -87.722654169)" +8240399,HT466105,2011-08-25 22:55:00,021XX N CICERO AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,2564,True,False,2522,25,31,19,16,1144003,1913897,2011,2011-02-09 09:05:11,41.919759591,-87.746339498,"(41.919759591, -87.746339498)" +1514753,G260660,2001-05-06 18:53:39,028XX W 19 ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,3078,True,False,1022,NA,NA,NA,15,1157943,1890640,2001,2010-02-06 10:34:17,41.855666993,-87.695756427,"(41.855666993, -87.695756427)" +3724031,HK768759,2004-11-12 13:00:00,012XX S ASHLAND AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,1595,False,False,1224,12,2,28,10,1165874,1894646,2004,ERROR,41.866494521,-87.666531674,"(41.866494521, -87.666531674)" +9095092,HW222941,2013-04-07 10:05:00,039XX S CALUMET AVE,2014,NARCOTICS,MANU/DELIVER: HEROIN (WHITE),SIDEWALK,3181,True,False,213,2,3,38,18,1179129,1878622,2013,2013-03-05 09:26:15,41.822230731,-87.618361446,"(41.822230731, -87.618361446)" +9606372,HX256322,2014-05-10 14:15:00,017XX S ASHLAND AVE,0860,THEFT,RETAIL THEFT,OTHER,824,False,False,1233,12,25,31,06,1166044,1891531,2014,ERROR,41.857943062,-87.665996477,"(41.857943062, -87.665996477)" +2080168,HH302067,2002-04-11 17:30:00,005XX S CENTRAL AV,0460,BATTERY,SIMPLE,RESIDENCE,830,False,False,1522,NA,NA,NA,08B,1139180,1897038,2002,ERROR,41.873585657,-87.764470903,"(41.873585657, -87.764470903)" +2538562,HJ119047,2003-01-10 17:30:00,030XX N MOBILE AVE,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1800,False,False,2511,NA,36,19,06,1133838,1919613,2003,ERROR,41.935629815,-87.783553233,"(41.935629815, -87.783553233)" +5728743,HN535876,2007-08-18 03:00:00,055XX W DAKIN ST,0460,BATTERY,SIMPLE,ALLEY,3956,False,False,1633,16,38,15,08B,1138581,1925659,2007,ERROR,41.952135858,-87.765975048,"(41.952135858, -87.765975048)" +4533362,HM121306,2006-01-05 13:00:00,112XX S PERRY AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,4073,False,True,522,5,34,49,08B,1177464,1830657,2006,ERROR,41.690647109,-87.62591646,"(41.690647109, -87.62591646)" +2940341,HJ623964,2003-09-11 22:52:01,079XX S JUSTINE ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3976,False,False,612,6,21,71,14,1167342,1852128,2003,ERROR,41.749788975,-87.662361113,"(41.749788975, -87.662361113)" +4022229,HL375934,2005-05-23 16:30:00,043XX W IRVING PARK RD,1320,CRIMINAL DAMAGE,TO VEHICLE,OTHER,547,False,False,1722,17,38,16,14,1146478,1926264,2005,ERROR,41.953648902,-87.736929761,"(41.953648902, -87.736929761)" +8856428,HV530199,2012-10-22 19:10:00,001XX N LA CROSSE AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,RESIDENCE-GARAGE,3046,False,False,1532,15,28,25,07,1144050,1900929,2012,ERROR,41.884173077,-87.746492747,"(41.884173077, -87.746492747)" +10126995,HY315210,2015-04-01 09:00:00,099XX S WINSTON AVE,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,RESIDENCE,3601,False,False,2213,22,21,73,11,1168775,1838907,2015,ERROR,41.713477841,-87.6574904,"(41.713477841, -87.6574904)" +7059731,HR465421,2009-08-04 10:00:00,050XX W ADDISON ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,3296,False,False,1634,16,38,15,05,1142074,1923423,2009,2009-03-09 12:45:23,41.945935898,-87.753190197,"(41.945935898, -87.753190197)" +4369628,HL653099,2005-10-04 14:15:00,0000X E 68TH ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4938,False,False,322,3,20,69,05,1178251,1859995,2005,ERROR,41.7711365,-87.622147753,"(41.7711365, -87.622147753)" +2085336,HH300310,2002-04-11 14:22:32,052XX S ASHLAND AV,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2024,True,False,932,NA,NA,NA,18,1166514,1870200,2002,ERROR,41.79939856,-87.664880524,"(41.79939856, -87.664880524)" +2837739,HJ500794,2003-07-17 12:00:00,064XX S ASHLAND AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),327,False,False,725,7,15,67,06,1166729,1862274,2003,2014-04-12 12:43:35,41.777644057,-87.664318242,"(41.777644057, -87.664318242)" +9359551,HW502824,2013-10-16 13:00:00,087XX S CONSTANCE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,SIDEWALK,4794,False,False,412,4,8,48,14,1189994,1847553,2013,ERROR,41.736720026,-87.579502355,"(41.736720026, -87.579502355)" +4269836,HL586435,2005-09-01 00:00:00,099XX S BEVERLY AVE,0890,THEFT,FROM BUILDING,RESIDENCE,2317,False,True,2213,22,21,72,06,1168186,1838983,2005,ERROR,41.713699063,-87.659645355,"(41.713699063, -87.659645355)" +3579880,HK667872,2004-10-05 09:30:00,053XX S COTTAGE GROVE AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,3907,False,False,2131,2,5,41,05,1182540,1869405,2004,ERROR,41.796859993,-87.606134249,"(41.796859993, -87.606134249)" +1396555,G081867,2001-02-09 10:00:00,070XX S SANGAMON ST,1812,NARCOTICS,POSS: CANNABIS MORE THAN 30GMS,STREET,2543,True,False,733,NA,NA,NA,18,1171146,1858301,2001,ERROR,41.766646226,-87.648241507,"(41.766646226, -87.648241507)" +6210516,HP298503,2008-04-23 19:00:00,088XX S COLFAX AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,3830,False,False,423,4,7,46,05,1195065,1846888,2008,2008-08-05 07:57:25,41.734771822,-87.560946053,"(41.734771822, -87.560946053)" +8335396,HT565225,2011-10-29 12:22:19,069XX S LAFAYETTE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESTAURANT,4794,False,True,731,7,6,69,08B,1176984,1859255,2011,2011-07-11 12:45:02,41.769134515,-87.626814372,"(41.769134515, -87.626814372)" +4575638,HL791694,2005-12-15 20:55:00,006XX N TRUMBULL AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,1930,True,False,1121,11,27,23,18,1153311,1903852,2005,ERROR,41.892015265,-87.712407374,"(41.892015265, -87.712407374)" +9527333,HX181612,2014-03-12 10:50:00,081XX S VINCENNES AVE,0545,ASSAULT,PRO EMP HANDS NO/MIN INJURY,"SCHOOL, PUBLIC, BUILDING",1878,True,False,622,6,21,44,08A,1174722,1850745,2014,ERROR,41.745832739,-87.635358835,"(41.745832739, -87.635358835)" +2802818,HJ444885,2003-06-21 19:30:00,029XX S STATE ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,CHA PARKING LOT/GROUNDS,4371,False,True,2113,1,3,35,08B,1176715,1885559,2003,ERROR,41.841321197,-87.627008027,"(41.841321197, -87.627008027)" +8240826,HT474337,2011-08-31 10:04:00,070XX S CLYDE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,ALLEY,3112,True,False,331,3,5,43,08B,1191389,1858691,2011,2011-01-09 06:32:18,41.767250011,-87.574031486,"(41.767250011, -87.574031486)" +8766702,HV441335,2012-08-21 22:30:00,023XX N MILWAUKEE AVE,0620,BURGLARY,UNLAWFUL ENTRY,OTHER,4672,False,False,1414,14,35,22,05,1157125,1915354,2012,2012-03-09 18:32:27,41.923501123,-87.698087331,"(41.923501123, -87.698087331)" +9497756,HX152161,2014-02-15 11:52:00,071XX S CONSTANCE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4239,False,True,324,3,5,43,08B,1189655,1857726,2014,ERROR,41.764643801,-87.580418177,"(41.764643801, -87.580418177)" +7234599,HR643869,2009-11-15 00:30:00,002XX W NORTH AVE,0890,THEFT,FROM BUILDING,OTHER,3514,False,False,1814,18,43,7,06,1174055,1911024,2009,2010-05-01 11:53:51,41.911258405,-87.636010015,"(41.911258405, -87.636010015)" +3179722,HK182024,2004-02-13 23:00:00,013XX W IRVING PARK RD,0810,THEFT,OVER $500,STREET,4624,False,False,1923,19,47,6,06,1166363,1926630,2004,2014-04-12 12:43:35,41.954250222,-87.663819997,"(41.954250222, -87.663819997)" +4222202,HL539526,2005-08-10 01:50:00,043XX S LAMON AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,CHA PARKING LOT/GROUNDS,4720,False,False,814,8,23,56,14,1144637,1875298,2005,ERROR,41.813827102,-87.744982032,"(41.813827102, -87.744982032)" +7219347,HR634999,2009-11-09 19:00:00,052XX S RACINE AVE,0560,ASSAULT,SIMPLE,APARTMENT,2784,False,True,934,9,16,61,08A,1169250,1870146,2009,ERROR,41.799191555,-87.654848486,"(41.799191555, -87.654848486)" +7793169,HS594082,2010-11-01 18:00:00,019XX W 95TH ST,1350,CRIMINAL TRESPASS,TO STATE SUP LAND,LIBRARY,553,False,False,2221,22,19,72,26,1164769,1841686,2010,2010-07-11 08:22:48,41.721189215,-87.672083692,"(41.721189215, -87.672083692)" +2103641,HH335780,2002-04-28 01:50:00,014XX W MARQUETTE RD,0560,ASSAULT,SIMPLE,STREET,4679,False,False,725,NA,17,67,08A,1167596,1860349,2002,ERROR,41.772343059,-87.661194963,"(41.772343059, -87.661194963)" +8223696,HT457644,2011-08-20 17:25:00,098XX S HALSTED ST,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,1911,True,False,2223,22,21,73,03,1172676,1839503,2011,ERROR,41.715028437,-87.643186054,"(41.715028437, -87.643186054)" +7585036,HS388608,2010-07-01 06:25:00,003XX N OAKLEY BLVD,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2283,False,False,1332,12,27,28,07,1160973,1902387,2010,2010-02-07 11:19:23,41.887839582,-87.684308839,"(41.887839582, -87.684308839)" +5931509,HN704677,2007-11-12 23:30:00,079XX S STATE ST,0320,ROBBERY,STRONGARM - NO WEAPON,CTA PLATFORM,3105,False,False,623,6,6,44,03,1177618,1852608,2007,2007-12-12 01:05:15,41.750880081,-87.624691153,"(41.750880081, -87.624691153)" +5710926,HN519237,2007-08-09 16:00:00,012XX N ROCKWELL ST,0890,THEFT,FROM BUILDING,RESIDENCE,3673,False,False,1423,14,26,24,06,1158874,1908481,2007,ERROR,41.904605346,-87.691849732,"(41.904605346, -87.691849732)" +3611691,HK705389,2004-10-23 13:30:00,010XX S WELLS ST,0810,THEFT,OVER $500,STREET,2968,False,False,131,1,2,32,06,1174825,1895931,2004,2014-04-12 12:43:35,41.86982516,-87.633633456,"(41.86982516, -87.633633456)" +4713760,HM209421,2006-03-02 13:48:00,072XX S PAULINA ST,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,STREET,2117,True,False,735,7,17,67,18,1166303,1856763,2006,ERROR,41.762530229,-87.666036716,"(41.762530229, -87.666036716)" +2607516,HJ206193,2003-02-26 14:20:00,034XX S DR MARTIN LUTHER KING JR DR,1330,CRIMINAL TRESPASS,TO LAND,DRUG STORE,3921,False,False,2122,NA,4,35,26,1179511,1882404,2003,ERROR,41.832600096,-87.616844359,"(41.832600096, -87.616844359)" +6611578,HP684171,2008-11-15 03:25:00,009XX W BELMONT AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESTAURANT,1170,True,False,1924,19,44,6,14,1169428,1921475,2008,ERROR,41.940038439,-87.652703194,"(41.940038439, -87.652703194)" +8020502,HT251222,2011-04-14 22:00:00,066XX S TALMAN AVE,0820,THEFT,$500 AND UNDER,STREET,490,False,False,831,8,15,66,06,1159896,1860350,2011,ERROR,41.772507471,-87.689421041,"(41.772507471, -87.689421041)" +3795590,HL160354,2005-01-28 23:00:00,036XX N MARSHFIELD AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2815,False,False,1923,19,47,6,14,1164641,1923969,2005,ERROR,41.946985052,-87.670225966,"(41.946985052, -87.670225966)" +4925940,HM538019,2006-08-13 16:09:24,060XX S VERNON AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,OTHER,3128,True,True,313,3,20,42,08B,1180344,1865263,2006,ERROR,41.785544659,-87.61431417,"(41.785544659, -87.61431417)" +8500347,HV177396,2012-02-29 11:20:00,001XX W LAKE ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA GARAGE / OTHER PROPERTY,4130,True,False,113,1,42,32,11,1175354,1901695,2012,2012-01-03 08:18:16,41.885630077,-87.631518326,"(41.885630077, -87.631518326)" +2617371,HJ216694,2003-03-03 18:00:00,073XX S SOUTH SHORE DR,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,1366,False,False,334,NA,7,43,26,1195387,1857193,2003,ERROR,41.76304161,-87.559426843,"(41.76304161, -87.559426843)" +7970463,HT202960,2011-02-15 17:00:00,077XX S NORMAL AVE,1110,DECEPTIVE PRACTICE,BOGUS CHECK,APARTMENT,4925,False,False,621,6,17,69,11,1174337,1853795,2011,ERROR,41.754210881,-87.636679059,"(41.754210881, -87.636679059)" +6529891,HP602686,2008-10-01 02:46:00,002XX E 35TH ST,0820,THEFT,$500 AND UNDER,SIDEWALK,496,False,False,2112,2,2,35,06,1178613,1881880,2008,ERROR,41.831182706,-87.620155187,"(41.831182706, -87.620155187)" +7957427,HT189220,2011-03-05 02:00:00,044XX N MILWAUKEE AVE,0460,BATTERY,SIMPLE,SIDEWALK,4218,False,False,1623,16,45,15,08B,1141633,1928786,2011,2011-08-03 11:36:40,41.960660635,-87.754678083,"(41.960660635, -87.754678083)" +8157874,HT373208,2011-06-30 09:40:00,038XX W MADISON ST,2027,NARCOTICS,POSS: CRACK,GROCERY FOOD STORE,4416,True,False,1122,11,28,26,18,1150456,1899690,2011,ERROR,41.880650508,-87.723001331,"(41.880650508, -87.723001331)" +3216861,HK232167,2004-03-10 18:30:00,0000X W 111TH ST,0890,THEFT,FROM BUILDING,HOSPITAL BUILDING/GROUNDS,2419,False,False,522,5,34,49,06,1177728,1831317,2004,ERROR,41.692452292,-87.624930074,"(41.692452292, -87.624930074)" +2177171,HH431013,2002-06-10 05:51:58,046XX W 82ND ST,0460,BATTERY,SIMPLE,APARTMENT,4946,False,False,834,NA,13,70,08B,1146910,1849636,2002,ERROR,41.743363318,-87.737296807,"(41.743363318, -87.737296807)" +8393994,HT626631,2011-12-10 20:40:00,076XX S NORMAL AVE,0460,BATTERY,SIMPLE,STREET,3091,False,False,621,6,17,69,08B,1174322,1854368,2011,ERROR,41.755783597,-87.636717026,"(41.755783597, -87.636717026)" +3445477,HK002362,2004-07-23 21:30:00,021XX W SUPERIOR ST,0820,THEFT,$500 AND UNDER,RESIDENCE,26,False,False,1313,12,1,24,06,1162023,1904942,2004,2014-04-12 12:43:35,41.894828854,-87.680381499,"(41.894828854, -87.680381499)" +1393385,G083313,2001-02-09 22:06:39,063XX S ALBANY AV,2027,NARCOTICS,POSS: CRACK,STREET,1258,True,False,823,NA,NA,NA,18,1156765,1862558,2001,ERROR,41.778630305,-87.700838972,"(41.778630305, -87.700838972)" +8144026,HT378531,2011-07-02 23:00:00,033XX W 63RD ST,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,2973,False,False,823,8,15,66,14,1155411,1862617,2011,2011-05-07 14:12:04,41.778819432,-87.705801296,"(41.778819432, -87.705801296)" +1971985,HH155823,2002-01-29 15:25:27,033XX W FLOURNOY ST,0460,BATTERY,SIMPLE,STREET,4324,False,False,1134,NA,NA,NA,08B,1154134,1896793,2002,ERROR,41.872628267,-87.709573266,"(41.872628267, -87.709573266)" +8586946,HV261160,2012-04-25 19:30:00,103XX S TORRENCE AVE,0810,THEFT,OVER $500,RESIDENCE,3718,False,False,434,4,10,51,06,1195470,1836864,2012,2012-10-05 12:01:25,41.707255067,-87.559792308,"(41.707255067, -87.559792308)" +4342220,HL629648,2005-09-22 17:00:00,021XX N LAWLER AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3886,True,False,2522,25,31,19,08B,1142418,1913633,2005,ERROR,41.919064771,-87.752169679,"(41.919064771, -87.752169679)" +9280006,HW424844,2013-08-25 21:30:00,001XX W 95TH ST,0890,THEFT,FROM BUILDING,RESTAURANT,4034,False,False,634,6,21,49,06,1176654,1841992,2013,ERROR,41.721770166,-87.628542273,"(41.721770166, -87.628542273)" +2813480,HJ464372,2003-06-30 18:40:01,005XX S COLUMBUS DR,0820,THEFT,$500 AND UNDER,PARK PROPERTY,143,True,False,124,1,2,32,06,1178319,1898125,2003,2014-04-12 12:43:35,41.875766752,-87.620739245,"(41.875766752, -87.620739245)" +3561152,HK648531,2004-09-26 04:30:45,110XX S HALSTED ST,0560,ASSAULT,SIMPLE,OTHER,4272,True,False,2233,22,34,75,08A,1172920,1831338,2004,ERROR,41.692617079,-87.642532293,"(41.692617079, -87.642532293)" +10050076,HY238761,2015-04-28 07:55:00,082XX S MARYLAND AVE,031A,ROBBERY,ARMED: HANDGUN,ALLEY,3860,False,False,631,6,8,44,03,1183383,1850376,2015,2015-05-05 12:47:16,41.744622997,-87.603634954,"(41.744622997, -87.603634954)" +8001148,HT233085,2011-04-03 23:15:00,107XX S BENSLEY AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,638,False,True,434,4,10,51,26,1194521,1834420,2011,2011-06-04 13:32:13,41.700571822,-87.563347504,"(41.700571822, -87.563347504)" +6228780,HP311138,2008-05-01 15:18:59,0000X E GARFIELD BLVD,0560,ASSAULT,SIMPLE,RESTAURANT,1289,False,False,233,2,20,40,08A,1177260,1868390,2008,ERROR,41.794195672,-87.625527066,"(41.794195672, -87.625527066)" +6779900,HR184949,2009-02-23 16:00:00,010XX W GARFIELD BLVD,0820,THEFT,$500 AND UNDER,RESIDENCE,477,False,False,712,7,16,68,06,1170399,1868173,2009,2009-06-04 15:49:19,41.79375246,-87.65069229,"(41.79375246, -87.65069229)" +7426826,HS229492,2010-03-28 01:59:00,066XX N OLMSTED AVE,0460,BATTERY,SIMPLE,PARKING LOT/GARAGE(NON.RESID.),643,True,False,1612,16,41,9,08B,1124889,1943652,2010,ERROR,42.001748276,-87.815908566,"(42.001748276, -87.815908566)" +2466429,HH796219,2002-11-22 20:00:00,074XX S INDIANA AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4674,False,True,323,NA,6,69,26,1178843,1855836,2002,ERROR,41.759710302,-87.620104123,"(41.759710302, -87.620104123)" +4607990,HM202545,2006-02-26 12:00:00,069XX S CORNELL AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,1838,False,False,332,3,5,43,05,1188596,1859060,2006,2006-05-03 04:52:17,41.768329782,-87.584257056,"(41.768329782, -87.584257056)" +4704001,HM309457,2006-04-22 00:00:00,043XX W 59TH ST,1305,CRIMINAL DAMAGE,CRIMINAL DEFACEMENT,RESIDENTIAL YARD (FRONT/BACK),3755,False,False,813,8,13,62,14,1148566,1865172,2006,ERROR,41.785965095,-87.730830299,"(41.785965095, -87.730830299)" +4742590,HM348904,2006-05-13 16:30:00,029XX E 80TH ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,693,True,False,422,4,7,46,15,1196921,1852523,2006,ERROR,41.75018879,-87.553959645,"(41.75018879, -87.553959645)" +6917090,HR321572,2009-05-14 22:30:00,022XX N NAGLE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3547,True,True,2512,25,36,19,08B,1133001,1914445,2009,2009-03-06 14:53:51,41.921462904,-87.786750394,"(41.921462904, -87.786750394)" +6072784,HP171029,2008-02-11 20:52:00,013XX S CANAL ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,GROCERY FOOD STORE,3576,False,False,131,1,2,28,11,1173296,1893975,2008,ERROR,41.864491821,-87.639304867,"(41.864491821, -87.639304867)" +8412665,HT645859,2011-12-24 07:30:00,021XX N CALIFORNIA AVE,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,SIDEWALK,1132,False,False,1431,14,1,22,03,1157384,1914494,2011,2012-12-01 19:07:21,41.921135948,-87.69715911,"(41.921135948, -87.69715911)" +3341445,HK378381,2004-05-20 22:30:00,002XX E OHIO ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),3433,False,False,1834,18,42,8,06,1178331,1904297,2004,2014-04-12 12:43:35,41.892702762,-87.620506953,"(41.892702762, -87.620506953)" +6468782,HP546433,2008-09-01 02:01:30,037XX N HALSTED ST,0460,BATTERY,SIMPLE,SIDEWALK,4456,False,False,2324,19,46,6,08B,1170291,1925093,2008,2008-09-10 20:44:50,41.949947525,-87.649425319,"(41.949947525, -87.649425319)" +8339679,HT573293,2011-11-03 13:20:00,007XX E 60TH ST,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE,4190,False,False,313,3,20,42,26,1182232,1865350,2011,2011-04-11 10:08:19,41.785739862,-87.607389268,"(41.785739862, -87.607389268)" +1950234,HH139194,2002-01-18 17:00:00,009XX W HURON ST,0810,THEFT,OVER $500,OTHER,4875,False,False,1323,NA,NA,NA,06,1169707,1905109,2002,2014-04-12 12:43:35,41.895123178,-87.652155546,"(41.895123178, -87.652155546)" +7041908,HR449221,2009-07-26 01:00:00,015XX N OAKLEY BLVD,0810,THEFT,OVER $500,STREET,2985,False,False,1424,14,1,24,06,1160755,1910100,2009,ERROR,41.9090092,-87.684895322,"(41.9090092, -87.684895322)" +6768049,HR182077,2009-02-21 17:10:00,119XX S MICHIGAN AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1367,False,False,532,5,9,53,14,1178941,1825981,2009,ERROR,41.677782065,-87.620650719,"(41.677782065, -87.620650719)" +3758036,HL123864,2005-01-14 07:45:00,068XX S CARPENTER ST,031A,ROBBERY,ARMED: HANDGUN,STREET,3785,False,False,724,7,17,68,03,1170445,1859718,2005,ERROR,41.770549943,-87.65076972,"(41.770549943, -87.65076972)" +9421792,HW565556,2013-12-10 12:10:00,065XX S SANGAMON ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3539,False,True,723,7,17,68,08B,1171144,1861336,2013,ERROR,41.774974679,-87.648160214,"(41.774974679, -87.648160214)" +7746209,HS553647,2010-10-08 08:30:00,075XX S KINGSTON AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,1033,False,True,421,4,7,43,14,1194540,1855586,2010,ERROR,41.758652752,-87.562583957,"(41.758652752, -87.562583957)" +3024764,HJ733776,2003-11-01 00:00:00,086XX S HERMITAGE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4585,False,False,614,6,18,71,05,1166139,1847570,2003,ERROR,41.73730682,-87.666898779,"(41.73730682, -87.666898779)" +6155450,HP244652,2008-03-26 09:00:00,047XX S KEDVALE AVE,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,11,False,False,815,8,23,57,06,1149453,1872819,2008,ERROR,41.806932525,-87.727380443,"(41.806932525, -87.727380443)" +2806099,HJ459080,2003-06-28 11:01:33,052XX S MARSHFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3964,False,False,932,9,16,61,08B,1166277,1869726,2003,ERROR,41.798102902,-87.665763163,"(41.798102902, -87.665763163)" +1454319,G168841,2001-03-24 21:20:00,014XX S LOOMIS ST,2024,NARCOTICS,POSS: HEROIN(WHITE),CHA PARKING LOT/GROUNDS,4558,True,False,1231,NA,NA,NA,18,1167225,1893379,2001,ERROR,41.862988863,-87.661608429,"(41.862988863, -87.661608429)" +5328640,HN178376,2007-02-15 17:43:21,0000X W 47TH ST,1330,CRIMINAL TRESPASS,TO LAND,PARK PROPERTY,1763,True,False,231,2,3,38,26,1176599,1873813,2007,ERROR,41.809091808,-87.627787677,"(41.809091808, -87.627787677)" +5030418,HM631435,2006-09-30 04:01:00,012XX S SAWYER AVE,0560,ASSAULT,SIMPLE,RESIDENCE,1035,True,False,1022,10,24,29,08A,1154859,1893942,2006,2006-06-10 04:52:45,41.86479033,-87.706987824,"(41.86479033, -87.706987824)" +9694653,HX345023,2014-07-14 16:10:00,038XX W CHICAGO AVE,2028,NARCOTICS,POSS: SYNTHETIC DRUGS,OTHER,1780,True,False,1112,11,27,23,18,1150812,1905031,2014,ERROR,41.89529982,-87.7215543,"(41.89529982, -87.7215543)" +2506466,HH847658,2002-12-19 09:26:25,010XX S LOOMIS ST,0460,BATTERY,SIMPLE,SIDEWALK,2190,False,False,1231,NA,2,28,08B,1167166,1895406,2002,ERROR,41.868552388,-87.661766802,"(41.868552388, -87.661766802)" +3995692,HL357532,2005-05-14 23:00:00,106XX S MACKINAW AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4448,False,False,432,4,10,52,14,1200233,1834857,2005,ERROR,41.70162895,-87.542418081,"(41.70162895, -87.542418081)" +1406698,G119938,2001-03-01 16:31:26,023XX W MADISON ST,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,1768,False,False,1332,NA,NA,NA,03,1161051,1900007,2001,ERROR,41.881307035,-87.684088522,"(41.881307035, -87.684088522)" +2063190,HH283739,2002-04-03 19:45:00,040XX N LINCOLN AV,1330,CRIMINAL TRESPASS,TO LAND,DRUG STORE,622,True,False,1912,NA,NA,NA,26,1162045,1927127,2002,ERROR,41.955705485,-87.679679583,"(41.955705485, -87.679679583)" +2187834,HH444731,2002-06-15 21:22:16,004XX E 75TH ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,1461,False,False,323,NA,6,69,07,1180456,1855421,2002,ERROR,41.758534647,-87.61420526,"(41.758534647, -87.61420526)" +4856312,HM458067,2006-07-06 11:34:00,037XX S WESTERN AVE,0460,BATTERY,SIMPLE,CTA BUS,2800,False,False,913,9,12,59,08B,1161018,1879907,2006,ERROR,41.826151267,-87.684767164,"(41.826151267, -87.684767164)" +4948851,HM562638,2006-08-25 20:00:00,069XX S LOOMIS BLVD,0890,THEFT,FROM BUILDING,RESIDENCE,3312,False,False,734,7,17,67,06,1168156,1858673,2006,ERROR,41.767731872,-87.65919032,"(41.767731872, -87.65919032)" +6687849,HR102460,2009-01-02 15:29:00,105XX S EGGLESTON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1562,False,False,2233,22,34,49,18,1175109,1835065,2009,2009-02-01 16:25:12,41.702796067,-87.634407275,"(41.702796067, -87.634407275)" +7550137,HS346624,2010-06-07 05:46:00,007XX W GARFIELD BLVD,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,4436,False,False,934,9,3,61,05,1172153,1868433,2010,ERROR,41.794427528,-87.644252869,"(41.794427528, -87.644252869)" +8453368,HV131074,2012-01-23 22:00:00,068XX N SHERIDAN RD,0820,THEFT,$500 AND UNDER,STREET,44,False,False,2431,24,49,1,06,1167006,1945516,2012,ERROR,42.006060138,-87.660911233,"(42.006060138, -87.660911233)" +1754134,G565506,2001-09-20 22:05:00,007XX E 111 ST,0460,BATTERY,SIMPLE,POLICE FACILITY/VEH PARKING LOT,715,False,False,531,NA,NA,NA,08B,1183305,1831462,2001,ERROR,41.692722515,-87.604507439,"(41.692722515, -87.604507439)" +2522777,HJ100697,2002-12-31 23:30:00,116XX S VINCENNES AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2478,False,False,2234,NA,34,75,14,1165846,1827777,2002,ERROR,41.682997842,-87.668532113,"(41.682997842, -87.668532113)" +1864439,G708189,2001-11-25 13:30:00,007XX N LARAMIE AV,0325,ROBBERY,VEHICULAR HIJACKING,ALLEY,1350,False,False,1524,NA,NA,NA,03,1141513,1904551,2001,ERROR,41.894159515,-87.755719492,"(41.894159515, -87.755719492)" +6934825,HR340739,2009-05-25 21:45:00,061XX S RACINE AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,1582,True,False,713,7,16,67,18,1169326,1864268,2009,ERROR,41.783059997,-87.654739922,"(41.783059997, -87.654739922)" +7981176,HT213299,2011-03-21 13:59:00,001XX W 35TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,CTA TRAIN,4539,True,False,924,9,11,34,18,1175731,1881812,2011,ERROR,41.831061278,-87.630731407,"(41.831061278, -87.630731407)" +4065862,HL408235,2005-06-08 22:48:24,002XX W 87TH ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,WAREHOUSE,3963,False,False,622,6,21,44,06,1176403,1847255,2005,ERROR,41.736218161,-87.629303966,"(41.736218161, -87.629303966)" +2034759,HH247819,2002-03-16 10:00:00,018XX N DRAKE AV,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,776,False,False,1422,NA,NA,NA,07,1152570,1911949,2002,ERROR,41.914248882,-87.714914388,"(41.914248882, -87.714914388)" +5544920,HN356358,2007-05-21 17:00:00,090XX S LAFLIN ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,3133,False,False,2222,22,21,73,26,1167966,1844587,2007,ERROR,41.729082013,-87.660290623,"(41.729082013, -87.660290623)" +4959301,HM571754,2006-08-29 22:30:00,015XX E 87TH ST,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,1634,True,False,412,4,8,48,05,1187761,1847553,2006,2008-02-05 01:04:48,41.736773407,-87.587683232,"(41.736773407, -87.587683232)" +7521660,HS325090,2010-05-25 00:00:00,030XX N PARKSIDE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3579,False,False,2514,25,31,19,14,1138129,1919810,2010,ERROR,41.93609381,-87.767778496,"(41.93609381, -87.767778496)" +4982342,HM596076,2006-09-12 00:00:00,014XX N DAMEN AVE,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,35,False,False,1424,14,1,24,06,1162762,1909616,2006,2014-04-12 12:43:35,41.907639198,-87.677536132,"(41.907639198, -87.677536132)" +7403488,HS205438,2010-03-13 04:25:00,080XX S MAY ST,0810,THEFT,OVER $500,STREET,2170,False,False,612,6,21,71,06,1170015,1851280,2010,ERROR,41.74740433,-87.652590689,"(41.74740433, -87.652590689)" +3140084,HK132250,2004-01-18 11:06:46,024XX N RACINE AVE,0460,BATTERY,SIMPLE,STREET,2504,False,False,1933,19,32,7,08B,1167856,1916386,2004,ERROR,41.926108075,-87.658627954,"(41.926108075, -87.658627954)" +5902358,HN700127,2007-11-10 11:00:00,003XX S WACKER DR,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),749,False,False,112,1,2,32,06,1174015,1898799,2007,2014-04-12 12:43:35,41.877713219,-87.636521697,"(41.877713219, -87.636521697)" +4572931,HM163096,2006-02-04 14:00:00,045XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),42,False,False,914,9,12,61,06,1161174,1874190,2006,2014-04-12 12:43:35,41.810459905,-87.684353246,"(41.810459905, -87.684353246)" +10012442,HY202116,2015-03-28 21:12:00,050XX N FRANCISCO AVE,2093,NARCOTICS,FOUND SUSPECT NARCOTICS,APARTMENT,1022,True,False,2031,20,40,4,26,1156167,1933292,2015,2015-04-04 12:43:24,41.972743598,-87.701121216,"(41.972743598, -87.701121216)" +6554328,HN700877,2007-11-11 04:00:00,076XX S RHODES AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,4899,False,False,624,6,6,69,04B,1181189,1854379,2007,ERROR,41.755658442,-87.611550936,"(41.755658442, -87.611550936)" +4398984,HL638310,2005-10-05 11:25:00,011XX N MONTICELLO AVE,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,STREET,3766,True,False,1112,11,27,23,18,1151799,1907151,2005,ERROR,41.901097939,-87.717873412,"(41.901097939, -87.717873412)" +2797787,HJ448611,2003-06-23 07:00:00,057XX S ABERDEEN ST,0890,THEFT,FROM BUILDING,STREET,1540,False,False,712,7,16,68,06,1169915,1867003,2003,ERROR,41.79055238,-87.652501059,"(41.79055238, -87.652501059)" +4471342,HL761655,2005-11-29 09:57:17,071XX S LAFAYETTE AVE,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,3232,True,False,731,7,6,69,20,1177037,1857432,2005,ERROR,41.764130805,-87.626675,"(41.764130805, -87.626675)" +2751617,HJ389272,2003-05-27 08:16:40,028XX W 24TH BLVD,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",2758,False,False,1033,NA,12,30,08B,1157546,1887831,2003,ERROR,41.847966869,-87.697289982,"(41.847966869, -87.697289982)" +8965579,HW113481,2013-01-11 03:00:00,023XX N LOWELL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3640,False,False,2522,25,31,20,26,1146957,1915184,2013,2013-05-02 14:37:42,41.923235246,-87.735452966,"(41.923235246, -87.735452966)" +5171598,HM762872,2006-12-08 13:45:00,044XX W GLADYS AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1177,False,True,1131,11,24,26,08B,1146659,1898021,2006,ERROR,41.876143827,-87.736986299,"(41.876143827, -87.736986299)" +5510052,HN329067,2007-05-08 00:30:00,049XX W BELDEN AVE,0890,THEFT,FROM BUILDING,APARTMENT,2656,False,True,2522,25,31,19,06,1142729,1914878,2007,ERROR,41.922475392,-87.750995953,"(41.922475392, -87.750995953)" +6808651,HR218704,2009-03-16 14:17:54,004XX N CENTRAL AVE,0460,BATTERY,SIMPLE,SIDEWALK,2744,False,False,1512,15,29,25,08B,1138919,1902473,2009,ERROR,41.888504748,-87.765297105,"(41.888504748, -87.765297105)" +4991519,HM449098,2006-07-01 22:02:41,071XX S MERRILL AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,4231,True,False,333,3,5,43,18,1191746,1858246,2006,ERROR,41.766020241,-87.572737379,"(41.766020241, -87.572737379)" +4052767,HL403567,2005-06-06 19:30:00,025XX S WASHTENAW AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,3538,False,False,1034,10,12,30,04B,1158738,1886739,2005,ERROR,41.844945991,-87.692945166,"(41.844945991, -87.692945166)" +8032674,HT264580,2011-04-21 13:30:00,098XX S ELLIS AVE,0890,THEFT,FROM BUILDING,RESIDENCE,4298,False,False,511,5,8,50,06,1184583,1839875,2011,ERROR,41.715779068,-87.599566057,"(41.715779068, -87.599566057)" +6241707,HP331800,2008-05-12 08:00:00,047XX W HARRISON ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,3322,False,False,1131,11,24,25,26,1144898,1896892,2008,ERROR,41.873079106,-87.743480642,"(41.873079106, -87.743480642)" +3134450,HK125197,2004-01-14 12:00:00,064XX S WINCHESTER AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3991,True,False,726,7,15,67,07,1164415,1862214,2004,ERROR,41.777528496,-87.672803106,"(41.777528496, -87.672803106)" +1329888,G024689,2001-01-12 11:00:00,109XX S EDBROOKE AV,4650,OTHER OFFENSE,SEX OFFENDER: FAIL TO REGISTER,RESIDENCE,3264,True,False,513,NA,NA,NA,26,1179220,1832688,2001,ERROR,41.696180708,-87.619426047,"(41.696180708, -87.619426047)" +5756722,HN564951,2007-09-02 06:30:00,014XX W 77TH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2012,False,False,612,6,17,71,14,1167956,1853723,2007,2007-06-09 01:56:11,41.754152713,-87.660065416,"(41.754152713, -87.660065416)" +8280908,HT515268,2011-09-26 15:06:00,057XX S SACRAMENTO AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4992,False,False,824,8,14,63,05,1157396,1866517,2011,2011-01-10 09:51:22,41.789481609,-87.69841849,"(41.789481609, -87.69841849)" +7835980,HS637015,2010-11-28 22:35:00,103XX S ALBANY AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4609,False,True,2211,22,19,74,08B,1157431,1835903,2010,2010-08-12 16:32:38,41.705471163,-87.699117536,"(41.705471163, -87.699117536)" +3607193,HK688263,2004-10-15 11:56:00,081XX S WOODLAWN AVE,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,756,False,False,411,4,8,45,07,1185584,1851347,2004,ERROR,41.74723602,-87.595539795,"(41.74723602, -87.595539795)" +9468000,HX121120,2014-01-18 23:00:00,015XX S KEDZIE AVE,0820,THEFT,$500 AND UNDER,RESIDENCE,155,False,False,1022,10,24,29,06,1155311,1892547,2014,ERROR,41.860953237,-87.705365981,"(41.860953237, -87.705365981)" +6576153,HP648649,2008-10-25 10:30:00,041XX W POTOMAC AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4740,False,False,2534,25,37,23,14,1148653,1908301,2008,ERROR,41.904315009,-87.729399290999993,"(41.904315009, -87.729399291)" +3864612,HL239886,2005-01-21 00:01:00,100XX W OHARE ST,0890,THEFT,FROM BUILDING,AIRPORT/AIRCRAFT,4122,False,False,1651,16,41,76,06,1100635,1934208,2005,ERROR,41.976200173,-87.905312411,"(41.976200173, -87.905312411)" +6466118,HP543667,2008-08-30 13:24:00,001XX N STATE ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,3056,True,False,122,1,42,32,06,1176390,1900949,2008,2008-02-09 08:07:10,41.883559699,-87.627736496,"(41.883559699, -87.627736496)" +5519425,HL290196,2005-04-12 11:30:00,010XX N RIDGEWAY AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,ALLEY,2680,True,False,1112,NA,27,23,26,NA,NA,2005,2007-08-08 02:59:32,NA,NA, +2939383,HJ625230,2003-09-11 10:00:00,021XX N DAMEN AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3838,False,False,1432,14,32,22,26,1162713,1914372,2003,ERROR,41.92069102,-87.677582552,"(41.92069102, -87.677582552)" +2424495,HH744202,2001-10-19 13:00:00,073XX S HALSTED ST,1120,DECEPTIVE PRACTICE,FORGERY,CURRENCY EXCHANGE,4149,False,False,733,NA,17,68,10,1172207,1855826,2001,ERROR,41.759831266,-87.644425197,"(41.759831266, -87.644425197)" +9212260,HW358008,2013-07-11 15:00:00,052XX W LE MOYNE ST,0810,THEFT,OVER $500,APARTMENT,4849,False,True,2532,25,37,25,06,1141354,1909518,2013,2013-12-07 09:06:47,41.907792476,-87.756180729,"(41.907792476, -87.756180729)" +4506402,HL736000,2005-11-15 03:42:50,0000X E BURTON PL,2022,NARCOTICS,POSS: COCAINE,STREET,2650,True,False,1824,18,43,8,18,1176556,1910448,2005,ERROR,41.909621673,-87.626839707,"(41.909621673, -87.626839707)" +1344183,G043772,2001-01-21 16:50:00,042XX S WENTWORTH AV,1330,CRIMINAL TRESPASS,TO LAND,GAS STATION,3960,False,False,935,NA,NA,NA,26,1175608,1876581,2001,ERROR,41.816709722,-87.631339516,"(41.816709722, -87.631339516)" +1563501,G320369,2001-06-03 04:40:00,035XX S FEDERAL ST,0460,BATTERY,SIMPLE,CHA APARTMENT,791,False,True,211,NA,NA,NA,08B,1176255,1881268,2001,ERROR,41.829556721,-87.628825201,"(41.829556721, -87.628825201)" +3340028,HK377408,2004-05-20 20:54:54,007XX S WESTERN AVE,1330,CRIMINAL TRESPASS,TO LAND,GROCERY FOOD STORE,555,True,False,1224,12,2,28,26,1160541,1896784,2004,ERROR,41.872473399,-87.68605047,"(41.872473399, -87.68605047)" +9985418,HY175099,2015-03-06 23:40:00,015XX N WELLS ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESTAURANT,3210,False,False,1821,18,43,8,11,1174460,1910637,2015,ERROR,41.910187414,-87.634533777,"(41.910187414, -87.634533777)" +5212650,HM793429,2006-12-24 11:45:00,041XX W VAN BUREN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1567,False,False,1132,11,24,26,14,1148616,1897735,2006,2007-02-01 06:08:05,41.875321473,-87.729808188,"(41.875321473, -87.729808188)" +10002890,HY192231,2015-03-20 16:27:00,033XX W 60TH ST,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,509,False,False,822,8,16,66,26,1154876,1864671,2015,ERROR,41.784466611,-87.70770788,"(41.784466611, -87.70770788)" +6237879,HP323989,2008-05-08 10:35:00,053XX S WESTERN BLVD,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,PARKING LOT/GARAGE(NON.RESID.),2025,False,False,915,9,16,63,07,1161471,1869210,2008,2008-11-05 13:01:30,41.796788001,-87.683402001,"(41.796788001, -87.683402001)" +9198183,HW343691,2013-07-01 12:30:00,078XX S LAFLIN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1113,False,False,612,6,17,71,14,1167515,1852777,2013,2013-02-07 08:31:12,41.751566217,-87.661708602,"(41.751566217, -87.661708602)" +9914993,HY104705,2015-01-05 05:00:00,045XX S DREXEL BLVD,0820,THEFT,$500 AND UNDER,APARTMENT,127,False,False,221,2,4,39,06,1182916,1875077,2015,2015-12-01 12:48:04,41.812415676,-87.604579073,"(41.812415676, -87.604579073)" +5812687,HN606869,2007-06-14 15:00:00,056XX S DORCHESTER AVE,0890,THEFT,FROM BUILDING,GAS STATION,2970,False,False,2133,2,5,41,06,1186553,1867773,2007,2007-10-10 01:49:09,41.792287561,-87.591469992,"(41.792287561, -87.591469992)" +1758189,G568662,2001-09-22 11:17:43,031XX W 26 ST,5001,OTHER OFFENSE,OTHER CRIME INVOLVING PROPERTY,PARKING LOT/GARAGE(NON.RESID.),992,True,False,1033,NA,NA,NA,26,1156100,1886534,2001,ERROR,41.844437017,-87.70263184,"(41.844437017, -87.70263184)" +6402809,HP474280,2008-07-23 16:00:00,089XX S YATES BLVD,0842,THEFT,AGG: FINANCIAL ID THEFT,RESIDENCE,1990,False,False,413,4,7,48,06,1193691,1845865,2008,2008-08-08 18:07:56,41.731998345,-87.566013112,"(41.731998345, -87.566013112)" +5475427,HN298043,2007-04-22 08:35:00,053XX S KEDZIE AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,1380,True,False,911,9,14,63,16,1155993,1869052,2007,ERROR,41.796466324,-87.703494798,"(41.796466324, -87.703494798)" +1523992,G255343,2001-05-04 09:55:00,056XX S ROCKWELL ST,141C,WEAPONS VIOLATION,UNLAWFUL USE OTHER DANG WEAPON,"SCHOOL, PUBLIC, BUILDING",3922,True,False,824,NA,NA,NA,15,1159962,1867094,2001,ERROR,41.791012589,-87.688993852,"(41.791012589, -87.688993852)" +2921705,HJ600586,2003-09-01 01:00:00,023XX N KARLOV AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3929,False,True,2525,25,31,20,08B,1148703,1915194,2003,ERROR,41.923229102,-87.729037228,"(41.923229102, -87.729037228)" +1854395,G695770,2001-11-19 10:10:00,066XX S PULASKI RD,0810,THEFT,OVER $500,COMMERCIAL / BUSINESS OFFICE,3532,False,False,833,NA,NA,NA,06,1150890,1860070,2001,2014-04-12 12:43:35,41.771919396,-87.722442165,"(41.771919396, -87.722442165)" +1908796,G765379,2001-12-23 04:20:00,055XX W VAN BUREN ST,0460,BATTERY,SIMPLE,RESIDENCE,1688,False,False,1522,NA,NA,NA,08B,1139450,1897421,2001,ERROR,41.874631745,-87.763470245,"(41.874631745, -87.763470245)" +8164922,HT399379,2011-07-15 23:40:00,076XX S CICERO AVE,0560,ASSAULT,SIMPLE,STREET,1389,False,False,833,8,13,65,08A,1145766,1853738,2011,ERROR,41.75464162,-87.741385158,"(41.75464162, -87.741385158)" +3254937,HK279665,2004-04-03 10:00:00,065XX N BOSWORTH AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,3491,False,False,2432,24,40,1,05,1164802,1943333,2004,ERROR,42.000117152,-87.669082287,"(42.000117152, -87.669082287)" +5390304,HN233940,2007-03-18 19:50:00,006XX N ALBANY AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,1362,False,True,1313,12,27,23,08B,1155550,1904304,2007,ERROR,41.893210839,-87.704172295,"(41.893210839, -87.704172295)" +8413638,HT647149,2011-12-25 17:30:00,069XX S CAMPBELL AVE,0330,ROBBERY,AGGRAVATED,SIDEWALK,1377,False,False,832,8,15,66,03,1160874,1858299,2011,ERROR,41.766859084,-87.685892542,"(41.766859084, -87.685892542)" +9527581,HX181802,2014-03-12 12:15:00,054XX S WINCHESTER AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1147,False,True,932,9,16,61,08B,1164316,1868765,2014,ERROR,41.795507366,-87.672981591,"(41.795507366, -87.672981591)" +5133690,HM731231,2006-11-20 14:00:00,073XX N HARLEM AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2303,False,False,1611,16,41,9,26,1127349,1948389,2006,ERROR,42.014705871,-87.806751157,"(42.014705871, -87.806751157)" +1995039,HH153002,2002-01-28 07:00:00,074XX N DAMEN AV,0460,BATTERY,SIMPLE,RESIDENCE,794,False,True,2424,NA,NA,NA,08B,1161699,1949553,2002,ERROR,42.01725048,-87.680322966,"(42.01725048, -87.680322966)" +4296516,HL531043,2005-08-05 23:09:43,006XX N CENTRAL PARK AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,2630,True,False,1122,11,27,23,18,1152231,1903962,2005,ERROR,41.892338494,-87.716370862,"(41.892338494, -87.716370862)" +5458813,HN287100,2007-04-16 17:45:00,051XX S ASHLAND AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,PARKING LOT/GARAGE(NON.RESID.),4687,True,True,932,9,16,61,08B,1166579,1870797,2007,ERROR,41.801035411,-87.66462512,"(41.801035411, -87.66462512)" +1556046,G312776,2001-05-30 16:45:00,014XX E 69 ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE PORCH/HALLWAY,1587,False,False,332,NA,NA,NA,14,1187298,1859575,2001,ERROR,41.769773909,-87.588998442,"(41.769773909, -87.588998442)" +6713807,HR129777,2009-01-19 21:00:00,020XX N MOZART ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4049,False,False,1414,14,35,22,14,1157024,1913723,2009,ERROR,41.919027585,-87.698502808,"(41.919027585, -87.698502808)" +1940740,HH119927,2002-01-11 12:00:00,047XX S UNION AV,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",3229,True,False,935,NA,NA,NA,08B,1172475,1873203,2002,ERROR,41.807509816,-87.642931616,"(41.807509816, -87.642931616)" +3362955,HK409648,2004-06-04 17:00:00,055XX W QUINCY ST,051A,ASSAULT,AGGRAVATED: HANDGUN,STREET,3449,False,False,1522,15,29,25,04A,1139492,1898492,2004,ERROR,41.877569945,-87.763289921,"(41.877569945, -87.763289921)" +1892993,G744161,2001-12-12 11:40:00,077XX W HOWARD ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,2824,False,False,1611,NA,NA,NA,06,1123943,1949779,2001,ERROR,42.018576967,-87.819253498,"(42.018576967, -87.819253498)" +4987543,HM599912,2006-09-13 23:41:57,032XX S LITUANICA AVE,1305,CRIMINAL DAMAGE,CRIMINAL DEFACEMENT,PARKING LOT/GARAGE(NON.RESID.),2748,True,False,924,9,11,60,14,1170787,1883697,2006,ERROR,41.836343419,-87.648816066,"(41.836343419, -87.648816066)" +8978243,HW122178,2013-01-04 11:00:00,017XX N LOTUS AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,2463,False,False,2532,25,37,25,26,1139764,1910834,2013,2013-12-02 12:25:23,41.91143294,-87.761989425,"(41.91143294, -87.761989425)" +9287934,HW432629,2013-09-01 03:30:00,023XX S HOMAN AVE,0460,BATTERY,SIMPLE,SIDEWALK,2341,False,False,1024,10,22,30,08B,1154021,1888439,2013,2013-02-09 12:27:03,41.849706206,-87.710210751,"(41.849706206, -87.710210751)" +3601383,HK694936,2004-09-18 00:00:00,002XX N KILBOURN AVE,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,3628,False,False,1113,11,28,26,11,1146400,1900624,2004,ERROR,41.883291699,-87.737870956,"(41.883291699, -87.737870956)" +7719567,HS527233,2010-09-21 20:50:00,051XX W WELLINGTON AVE,4387,OTHER OFFENSE,VIOLATE ORDER OF PROTECTION,RESIDENCE,3751,False,True,2521,25,31,19,26,1141674,1919503,2010,2010-11-10 15:54:50,41.935186459,-87.754757729,"(41.935186459, -87.754757729)" +6550412,HP624139,2008-10-13 00:00:37,052XX S MARSHFIELD AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,2524,True,False,932,9,16,61,15,1166269,1870015,2008,ERROR,41.798896123,-87.66578427,"(41.798896123, -87.66578427)" +2902949,HJ579061,2003-07-16 15:58:00,046XX W 55TH ST,1206,DECEPTIVE PRACTICE,"THEFT BY LESSEE,MOTOR VEH",OTHER,4441,True,False,813,8,23,56,11,1146337,1867704,2003,ERROR,41.792955883,-87.738938839,"(41.792955883, -87.738938839)" +3505136,HK581743,2004-08-25 18:04:00,071XX W 64TH PL,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,4733,False,False,812,8,23,64,26,1129743,1860965,2004,ERROR,41.774761494,-87.799941841,"(41.774761494, -87.799941841)" +8081212,HT310002,2011-05-23 11:30:00,046XX N SHERIDAN RD,0460,BATTERY,SIMPLE,SIDEWALK,3485,False,False,2312,19,46,3,08B,1168819,1931361,2011,ERROR,41.967179243,-87.654653712,"(41.967179243, -87.654653712)" +1652291,G430211,2001-07-22 12:15:00,012XX N PARKSIDE AV,2230,LIQUOR LAW VIOLATION,ILLEGAL CONSUMPTION BY MINOR,STREET,1369,True,False,2531,NA,NA,NA,22,1138416,1907506,2001,ERROR,41.902325047,-87.767022333,"(41.902325047, -87.767022333)" +7784284,HS576050,2010-10-21 19:27:56,008XX E 89TH ST,2027,NARCOTICS,POSS: CRACK,APARTMENT,4100,True,False,632,6,8,44,18,1183562,1846188,2010,2010-10-11 15:04:15,41.733126496,-87.603109289,"(41.733126496, -87.603109289)" +4392495,HL685535,2005-10-18 07:00:00,008XX N LATROBE AVE,0810,THEFT,OVER $500,STREET,4453,False,False,1524,15,37,25,06,1141162,1905112,2005,2014-04-12 12:43:35,41.895705443,-87.756994782,"(41.895705443, -87.756994782)" +8322510,HT548001,2011-10-17 17:30:00,067XX S CLYDE AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,4763,False,False,331,3,5,43,26,1191419,1860438,2011,ERROR,41.772043187,-87.573864982,"(41.772043187, -87.573864982)" +5784220,HN581658,2007-06-08 23:40:00,003XX S WASHTENAW AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,2376,False,True,1125,11,2,27,08B,1158395,1898272,2007,ERROR,41.876600753,-87.693888703,"(41.876600753, -87.693888703)" +1473650,G192572,2001-04-05 10:10:00,028XX W ARTHINGTON ST,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,1925,True,False,1135,NA,NA,NA,18,1157618,1895874,2001,ERROR,41.870036252,-87.6968069,"(41.870036252, -87.6968069)" +7157534,HR565848,2009-10-01 15:40:00,079XX S DAMEN AVE,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,531,False,False,611,6,18,71,03,1164362,1852231,2009,2009-10-10 16:21:59,41.750134873,-87.673278237,"(41.750134873, -87.673278237)" +3712271,HK684159,2004-10-13 13:15:00,072XX S SOUTH CHICAGO AVE,5011,OTHER OFFENSE,LICENSE VIOLATION,RESIDENCE-GARAGE,824,False,False,323,3,5,69,26,1183382,1857501,2004,2007-11-06 15:52:33,41.764174754,-87.603417126,"(41.764174754, -87.603417126)" +1741181,G550640,2001-09-12 14:00:00,021XX W RANDOLPH ST,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,4543,False,True,1332,NA,NA,NA,20,1162226,1901155,2001,ERROR,41.884432772,-87.679741878,"(41.884432772, -87.679741878)" +6882076,HR288440,2009-04-25 18:00:00,046XX S RICHMOND ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4485,False,False,912,9,14,58,14,1157528,1873541,2009,ERROR,41.80875374,-87.697744048,"(41.80875374, -87.697744048)" +8303183,HT520944,2011-09-30 03:15:00,002XX E 121ST PL,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2521,False,False,532,5,9,53,08B,1180264,1824414,2011,ERROR,41.673451854,-87.615855891,"(41.673451854, -87.615855891)" +9010374,HW157593,2013-02-15 12:30:00,051XX S CALIFORNIA AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),94,False,False,923,9,14,63,06,1158620,1870342,2013,ERROR,41.799953035,-87.693826098,"(41.799953035, -87.693826098)" +7689122,HS495203,2010-08-31 19:00:00,003XX E OHIO ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,4329,False,False,1834,18,42,8,11,1178952,1904236,2010,ERROR,41.892521195,-87.618228164,"(41.892521195, -87.618228164)" +1438982,G153765,2001-03-17 23:38:07,062XX S ST LAWRENCE AV,1821,NARCOTICS,MANU/DEL:CANNABIS 10GM OR LESS,STREET,4128,True,False,313,NA,NA,NA,18,1181276,1863401,2001,ERROR,41.780413719,-87.610954415,"(41.780413719, -87.610954415)" +9340354,HW484281,2013-10-07 23:00:00,096XX S WENTWORTH AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,2824,False,False,511,5,21,49,05,1176693,1840729,2013,ERROR,41.718303448,-87.628437301,"(41.718303448, -87.628437301)" +3044554,HJ746479,2003-11-08 10:25:00,018XX W 51ST ST,2017,NARCOTICS,MANU/DELIVER:CRACK,SIDEWALK,1194,True,False,932,9,16,61,18,1165175,1870833,2003,ERROR,41.801164057,-87.669773064,"(41.801164057, -87.669773064)" +6080834,HP159980,2008-02-05 10:10:00,010XX N HUDSON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,964,True,False,1823,18,27,8,18,1172947,1907120,2008,ERROR,41.900570272,-87.640196289,"(41.900570272, -87.640196289)" +4594393,HM187557,2006-02-18 03:50:00,027XX N MILWAUKEE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,4685,False,False,1412,14,35,22,14,1153678,1918136,2006,2014-04-12 12:43:35,41.931204528,-87.710678708,"(41.931204528, -87.710678708)" +9681055,HX331130,2014-07-04 15:00:00,021XX W PETERSON AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,4484,True,False,2413,24,40,2,06,1161031,1939882,2014,2014-11-07 12:39:31,41.990726931,-87.683051337,"(41.990726931, -87.683051337)" +6696343,HR110836,2009-01-07 19:30:00,021XX W SHAKESPEARE AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,3344,False,False,1432,14,32,22,03,1161425,1914413,2009,2009-03-02 19:02:28,41.920830456,-87.682313785,"(41.920830456, -87.682313785)" +7313029,HS117588,2010-01-13 00:21:00,032XX N CLARK ST,2022,NARCOTICS,POSS: COCAINE,STREET,4814,True,False,1924,19,44,6,18,1169902,1921495,2010,ERROR,41.94008298,-87.650960519,"(41.94008298, -87.650960519)" +8914447,HV587762,2012-12-03 19:40:00,077XX S ASHLAND AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,4861,True,False,612,6,17,71,18,1167067,1853091,2012,2012-03-12 20:28:11,41.752437462,-87.663341352,"(41.752437462, -87.663341352)" +8813683,HV486788,2012-09-22 09:00:00,017XX E 56TH ST,0890,THEFT,FROM BUILDING,APARTMENT,3006,False,False,235,2,5,41,06,1188630,1868244,2012,ERROR,41.793530583,-87.583839014,"(41.793530583, -87.583839014)" +2915956,HJ592169,2003-08-28 08:00:00,078XX S GREENWOOD AVE,0560,ASSAULT,SIMPLE,RESIDENCE,3119,False,False,624,6,8,69,08A,1184870,1853297,2003,2007-11-06 15:52:33,41.752603788,-87.598095,"(41.752603788, -87.598095)" +5426156,HN256370,2007-03-29 18:30:00,0000X W CERMAK RD,0810,THEFT,OVER $500,STREET,1448,False,False,134,1,3,33,06,1176378,1889737,2007,2014-04-12 12:43:35,41.852793534,-87.628118788,"(41.852793534, -87.628118788)" +1331639,G028143,2001-01-12 21:00:00,034XX S KING DR,0820,THEFT,$500 AND UNDER,DRUG STORE,282,True,False,2122,NA,NA,NA,06,1179512,1882402,2001,2014-04-12 12:43:35,41.832594585,-87.616840751,"(41.832594585, -87.616840751)" +1947050,HH132691,2002-01-17 18:49:57,005XX E 44 PL,0460,BATTERY,SIMPLE,RESIDENCE,4529,False,False,222,NA,NA,NA,08B,1180563,1875665,2002,ERROR,41.814083626,-87.613191703,"(41.814083626, -87.613191703)" +2490949,HH828905,2002-12-07 18:00:00,008XX W 53RD ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,2258,False,False,934,NA,3,61,11,1171449,1869703,2002,ERROR,41.797928002,-87.646797215,"(41.797928002, -87.646797215)" +5985175,HN780490,2007-12-27 17:30:00,019XX E 79TH ST,031A,ROBBERY,ARMED: HANDGUN,OTHER,2491,True,False,414,4,8,43,03,1190626,1853018,2007,ERROR,41.751701272,-87.577010969,"(41.751701272, -87.577010969)" +2817048,HJ473101,2003-07-04 23:55:30,050XX N WESTERN AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,1995,False,False,2031,20,47,4,05,1159496,1933143,2003,ERROR,41.972266669,-87.688883926,"(41.972266669, -87.688883926)" +9456119,HX108965,2014-01-10 01:04:00,075XX S LAFAYETTE AVE,2028,NARCOTICS,POSS: SYNTHETIC DRUGS,STREET,1608,True,False,623,6,6,69,18,1177099,1855191,2014,2014-12-01 00:40:28,41.757979849,-87.626515246,"(41.757979849, -87.626515246)" +2798720,HJ444156,2003-06-21 15:58:37,001XX W LAKE ST,0312,ROBBERY,ARMED:KNIFE/CUTTING INSTRUMENT,CTA PLATFORM,1268,False,False,113,1,42,32,03,1175497,1901776,2003,ERROR,41.885849135,-87.630990773,"(41.885849135, -87.630990773)" +7499449,HS302738,2010-05-11 17:56:00,045XX N BROADWAY,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,2688,False,False,2311,19,46,3,08B,1168079,1930569,2010,2010-12-05 14:32:35,41.965022021,-87.65739756,"(41.965022021, -87.65739756)" +3967996,HL332690,2005-05-03 13:20:00,014XX S CHRISTIANA AVE,0545,ASSAULT,PRO EMP HANDS NO/MIN INJURY,"SCHOOL, PUBLIC, BUILDING",2513,True,False,1021,10,24,29,08A,1154231,1892646,2005,ERROR,41.861246512,-87.709327807,"(41.861246512, -87.709327807)" +8209042,HT442926,2011-08-11 14:41:00,068XX S WOLCOTT AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1871,True,False,726,7,17,67,18,1164911,1859184,2011,2011-11-08 15:42:15,41.769203304,-87.6710703,"(41.769203304, -87.6710703)" +4973670,HM585353,2006-09-06 07:50:00,094XX S LAFAYETTE AVE,0810,THEFT,OVER $500,STREET,4120,False,False,634,6,21,49,06,1177555,1842613,2006,2014-04-12 12:43:35,41.723453982,-87.625223371,"(41.723453982, -87.625223371)" +1393322,G107984,2001-02-22 22:15:00,087XX S PEORIA ST,0271,CRIM SEXUAL ASSAULT,ATTEMPT AGG: HANDGUN,STREET,1517,False,False,2222,NA,NA,NA,02,1171783,1847121,2001,ERROR,41.735952909,-87.646233916,"(41.735952909, -87.646233916)" +9297259,HW442278,2013-09-07 15:30:00,059XX S ELIZABETH ST,0560,ASSAULT,SIMPLE,RESIDENCE,1658,False,False,713,7,16,67,08A,1168974,1865056,2013,2013-10-09 09:12:36,41.785229979,-87.656007708,"(41.785229979, -87.656007708)" +8764812,HV435925,2012-08-17 15:00:00,053XX S MONITOR AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2173,False,False,811,8,23,56,07,1138309,1868694,2012,ERROR,41.795821159,-87.768353304,"(41.795821159, -87.768353304)" +6872272,HR278952,2009-04-21 02:08:00,083XX S KEDZIE AVE,0610,BURGLARY,FORCIBLE ENTRY,BARBERSHOP,2575,False,False,834,8,18,70,05,1156456,1849198,2009,ERROR,41.741974607,-87.702331067,"(41.741974607, -87.702331067)" +3886460,HL262763,2005-03-29 08:00:00,062XX W DICKENS AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,926,False,False,2512,25,29,19,07,1134702,1913339,2005,ERROR,41.918397987,-87.780526574,"(41.918397987, -87.780526574)" +9247581,HW393661,2013-08-04 23:33:00,083XX S DORCHESTER AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,2059,True,True,412,4,8,45,08B,1187024,1850166,2013,2013-05-08 05:38:09,41.743961235,-87.590300673,"(41.743961235, -87.590300673)" +6240467,HP328601,2008-05-09 00:00:00,038XX N PACIFIC AVE,0890,THEFT,FROM BUILDING,RESIDENCE,957,False,False,1631,16,36,17,06,1122068,1924371,2008,2008-11-05 08:02:48,41.948885353,-87.82670641,"(41.948885353, -87.82670641)" +2325815,HH620831,2002-08-29 18:30:00,009XX E 82ND ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1157,False,False,631,NA,8,44,14,1184144,1850866,2002,ERROR,41.745949866,-87.600831299,"(41.745949866, -87.600831299)" +1481452,G218910,2001-04-14 10:15:00,017XX W 43 ST,0820,THEFT,$500 AND UNDER,STREET,248,False,False,914,NA,NA,NA,06,1165712,1876148,2001,2014-04-12 12:43:35,41.815737633,-87.667652714,"(41.815737633, -87.667652714)" +2193379,HH452636,2002-06-19 11:09:24,027XX E 76TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,509,False,False,421,NA,7,43,14,1195835,1855198,2002,ERROR,41.757556115,-87.557850794,"(41.757556115, -87.557850794)" +6970254,HR375030,2009-06-14 14:15:00,052XX W WOLFRAM ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,683,False,False,2514,25,31,19,05,1140877,1918484,2009,ERROR,41.932404942,-87.757711919,"(41.932404942, -87.757711919)" +7626585,HS431129,2010-07-26 19:16:00,012XX W WASHBURNE AVE,2220,LIQUOR LAW VIOLATION,ILLEGAL POSSESSION BY MINOR,STREET,4211,True,False,1231,12,2,28,22,1168368,1894493,2010,ERROR,41.866021153,-87.65738041,"(41.866021153, -87.65738041)" +8990632,HW137651,2013-01-30 12:12:00,060XX S TALMAN AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,1914,True,False,825,8,15,66,05,1159702,1864538,2013,2013-05-02 08:59:06,41.784003919,-87.690017346,"(41.784003919, -87.690017346)" +3647659,HK742033,2004-11-10 09:15:00,062XX S COTTAGE GROVE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,3824,False,True,313,3,20,42,08B,1182595,1863841,2004,ERROR,41.781590611,-87.606105147,"(41.781590611, -87.606105147)" +6003770,HP103458,2008-01-03 00:06:00,056XX S TRIPP AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,SIDEWALK,2815,True,False,813,8,13,62,15,1148949,1867064,2008,2008-09-01 10:07:35,41.791149667,-87.729377285,"(41.791149667, -87.729377285)" +6025842,HP129074,2008-01-02 16:00:00,069XX S KIMBARK AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3731,False,False,321,3,5,69,14,1185895,1859153,2008,ERROR,41.768649107,-87.59415445,"(41.768649107, -87.59415445)" +4800609,HM411215,2006-05-17 20:00:00,114XX S DR MARTIN LUTHER KING JR DR,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,3162,False,True,531,5,9,49,26,1180987,1829335,2006,ERROR,41.686939238,-87.613059096,"(41.686939238, -87.613059096)" +1666539,G443084,2001-07-27 21:11:26,048XX W FLETCHER ST,0460,BATTERY,SIMPLE,RESIDENCE,2067,True,False,2521,NA,NA,NA,08B,1143266,1920538,2001,ERROR,41.937996967,-87.748881061,"(41.937996967, -87.748881061)" +5182497,HM771133,2006-12-13 02:45:33,037XX W ROOSEVELT RD,0460,BATTERY,SIMPLE,STREET,1387,False,False,1011,10,24,29,08B,1151547,1894410,2006,2007-11-06 15:52:33,41.866140241,-87.719133978,"(41.866140241, -87.719133978)" +2653415,HJ264288,2003-03-27 17:55:45,035XX S RHODES AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1255,False,False,212,NA,4,35,08B,1180131,1881336,2003,ERROR,41.829655213,-87.614602295,"(41.829655213, -87.614602295)" +1351172,G053225,2001-01-25 20:00:00,023XX W CULLERTON ST,0820,THEFT,$500 AND UNDER,STREET,16,False,False,1223,NA,NA,NA,06,1161315,1890317,2001,2014-04-12 12:43:35,41.854711274,-87.683388522,"(41.854711274, -87.683388522)" +10107538,HY295865,2015-06-10 18:55:00,031XX W VAN BUREN ST,2024,NARCOTICS,POSS: HEROIN(WHITE),PARK PROPERTY,515,True,False,1134,11,28,27,18,1155706,1897956,2015,ERROR,41.875788166,-87.703770398,"(41.875788166, -87.703770398)" +2379764,HH688374,2002-10-02 22:17:34,012XX E 93RD ST,0560,ASSAULT,SIMPLE,OTHER,3430,False,True,413,NA,8,47,08A,1186314,1843608,2002,ERROR,41.725982183,-87.593108809,"(41.725982183, -87.593108809)" +2267732,HH544517,2002-07-29 18:20:00,024XX N MONTICELLO AVE,0460,BATTERY,SIMPLE,APARTMENT,1972,False,False,2524,NA,35,22,08B,1151599,1915885,2002,ERROR,41.925068771,-87.718378015,"(41.925068771, -87.718378015)" +9401609,HW544488,2013-11-22 15:00:00,022XX W 47TH ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,1599,False,True,931,9,12,61,26,1161952,1873419,2013,ERROR,41.80832803,-87.681521068,"(41.80832803, -87.681521068)" +1724872,G526711,2001-09-02 23:30:00,008XX W BARRY AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1898,False,False,1932,NA,NA,NA,07,1170165,1920835,2001,ERROR,41.938266163,-87.650013252,"(41.938266163, -87.650013252)" +7236503,HR651141,2009-11-19 12:30:00,045XX N KEDZIE AVE,0843,THEFT,ATTEMPT FINANCIAL IDENTITY THEFT,RESIDENCE,1452,False,False,1724,17,33,14,06,1154268,1930085,2009,ERROR,41.963981641,-87.708190292,"(41.963981641, -87.708190292)" +4531738,HM116986,2006-01-10 17:40:00,018XX W ADDISON ST,0460,BATTERY,SIMPLE,STREET,1107,False,False,1923,19,47,5,08B,1163300,1923889,2006,ERROR,41.94679389,-87.675157351,"(41.94679389, -87.675157351)" +6323346,HP407292,2008-06-21 01:08:00,027XX W 79TH ST,1220,DECEPTIVE PRACTICE,THEFT OF LOST/MISLAID PROP,ALLEY,1728,True,False,835,8,18,70,11,1159359,1852062,2008,ERROR,41.749774969,-87.691616189,"(41.749774969, -87.691616189)" +2811252,HJ435689,2003-06-17 19:01:00,007XX N HARDING AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,3835,True,False,1112,11,27,23,18,1149954,1904935,2003,ERROR,41.895053131,-87.724708046,"(41.895053131, -87.724708046)" +10109799,HY298102,2015-06-12 13:35:00,055XX W BELMONT AVE,0560,ASSAULT,SIMPLE,ALLEY,4032,False,False,2514,25,30,19,08A,1138524,1920677,2015,ERROR,41.938465788,-87.766305737,"(41.938465788, -87.766305737)" +2122047,HH360853,2002-05-08 03:00:00,035XX W 115TH PL,0810,THEFT,OVER $500,STREET,586,False,False,2211,NA,19,74,06,1155019,1827742,2002,2014-04-12 12:43:35,41.683124226,-87.70816705,"(41.683124226, -87.70816705)" +5459179,HN286810,2007-04-14 11:00:00,017XX N SEDGWICK ST,0610,BURGLARY,FORCIBLE ENTRY,CONSTRUCTION SITE,4496,False,False,1813,18,43,7,05,1173287,1911789,2007,2007-01-05 07:13:54,41.913374701,-87.638808607,"(41.913374701, -87.638808607)" +9427739,HW571708,2013-12-15 12:19:00,038XX W 65TH PL,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,RESIDENCE,2456,True,False,833,8,13,65,15,1151877,1860935,2013,ERROR,41.774273798,-87.718801431,"(41.774273798, -87.718801431)" +9549159,HX200896,2014-03-23 14:00:00,0000X W WASHINGTON ST,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,BANK,506,False,False,111,1,42,32,11,1175824,1900858,2014,ERROR,41.883322741,-87.629817609,"(41.883322741, -87.629817609)" +3085284,HJ808984,2003-12-10 01:30:00,015XX W MADISON ST,031A,ROBBERY,ARMED: HANDGUN,STREET,1775,False,False,1211,12,2,28,03,1165905,1900059,2003,ERROR,41.881347579,-87.666263434,"(41.881347579, -87.666263434)" +4372149,HL665453,2005-10-10 19:30:00,037XX N RECREATION DR,0820,THEFT,$500 AND UNDER,PARK PROPERTY,375,False,False,2323,19,46,6,06,1171814,1925403,2005,2014-04-12 12:43:35,41.950764691,-87.643817819,"(41.950764691, -87.643817819)" +8955831,HW104841,2013-01-01 20:21:00,027XX W 56TH ST,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,4110,False,False,824,8,16,63,26,1159094,1867451,2013,2013-10-01 14:22:16,41.792010053,-87.692166871,"(41.792010053, -87.692166871)" +7846213,HS658466,2010-12-13 10:50:00,032XX W 26TH ST,031A,ROBBERY,ARMED: HANDGUN,SMALL RETAIL STORE,4184,True,False,1024,10,22,30,03,1155078,1886589,2010,2012-06-02 11:35:31,41.844608478,-87.706380961,"(41.844608478, -87.706380961)" +7357351,HS158950,2010-02-10 08:10:00,003XX S SACRAMENTO BLVD,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,STREET,3954,False,False,1124,11,28,27,03,1156394,1898275,2010,2010-06-07 20:35:42,41.876649659,-87.701235678,"(41.876649659, -87.701235678)" +8459205,HV136620,2012-01-28 22:45:00,008XX N TRIPP AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2553,False,False,1111,11,37,23,14,1147864,1905093,2012,ERROR,41.895527127,-87.732380072,"(41.895527127, -87.732380072)" +1389813,G101962,2001-02-19 18:05:00,016XX E 74 PL,0560,ASSAULT,SIMPLE,STREET,681,False,True,324,NA,NA,NA,08A,1188468,1855950,2001,ERROR,41.75979873,-87.584825427,"(41.75979873, -87.584825427)" +7865849,HS679407,2010-12-28 12:40:00,031XX W MADISON ST,0560,ASSAULT,SIMPLE,APARTMENT,1924,False,False,1124,11,28,27,08A,1155534,1899811,2010,ERROR,41.880881934,-87.704352007,"(41.880881934, -87.704352007)" +4757789,HM369541,2006-05-23 16:00:00,017XX N WOLCOTT AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2770,False,True,1434,14,32,24,26,1163450,1911711,2006,ERROR,41.913373563,-87.674949723,"(41.913373563, -87.674949723)" +3189601,HK196709,2004-02-21 18:00:00,025XX N HARLEM AVE,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),1669,False,False,2512,25,36,18,06,1127702,1916103,2004,2014-04-12 12:43:35,41.9261037,-87.806183216,"(41.9261037, -87.806183216)" +4409636,HL704892,2005-10-30 15:03:19,083XX S BOND AVE,0454,BATTERY,AGG PO HANDS NO/MIN INJURY,RESIDENCE,4370,True,False,424,4,10,46,08B,1198647,1849847,2005,ERROR,41.742802579,-87.547724486,"(41.742802579, -87.547724486)" +1505171,G248949,2001-05-01 12:00:00,002XX S MICHIGAN AV,0560,ASSAULT,SIMPLE,STREET,861,False,False,123,NA,NA,NA,08A,1177284,1899473,2001,ERROR,41.879489257,-87.624498463,"(41.879489257, -87.624498463)" +4586711,HM178303,2006-02-13 02:00:53,003XX N LARAMIE AVE,0460,BATTERY,SIMPLE,RESTAURANT,1167,False,False,1532,15,28,25,08B,1141691,1901663,2006,ERROR,41.886231203,-87.755137217,"(41.886231203, -87.755137217)" +1989552,HH189521,2002-02-15 19:50:00,075XX S SAGINAW AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1219,True,False,421,NA,NA,NA,07,1195277,1855344,2002,ERROR,41.757970535,-87.559890935,"(41.757970535, -87.559890935)" +3644751,HK740805,2004-11-08 17:00:00,018XX S KARLOV AVE,0810,THEFT,OVER $500,STREET,788,False,False,1012,10,24,29,06,1149319,1890548,2004,2014-04-12 12:43:35,41.855585896,-87.727413274,"(41.855585896, -87.727413274)" +8573990,HV248559,2012-04-18 23:00:00,004XX N CENTRAL PARK BLVD,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,602,False,False,1122,11,27,23,07,1152144,1902675,2012,ERROR,41.888808551,-87.716724345,"(41.888808551, -87.716724345)" +3524215,HK604674,2004-09-05 19:15:00,036XX W SHAKESPEARE AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,4206,True,False,2525,25,26,22,18,1151518,1913999,2004,2007-11-06 15:52:33,41.919895016,-87.718725318,"(41.919895016, -87.718725318)" +1847188,G685045,2001-11-14 09:00:00,050XX S LAWNDALE AV,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,3981,False,False,821,NA,NA,NA,05,1152515,1871081,2001,ERROR,41.802103414,-87.716195637,"(41.802103414, -87.716195637)" +7342528,HS143733,2010-01-30 11:35:00,010XX W FOSTER AVE,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,ALLEY,1187,False,False,2023,20,48,77,03,1168536,1934754,2010,2010-11-02 08:18:49,41.976495882,-87.655595568,"(41.976495882, -87.655595568)" +7778066,HS583693,2010-10-26 13:20:00,003XX W 117TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4154,False,True,522,5,34,53,08B,1176304,1827385,2010,ERROR,41.681694308,-87.630261037,"(41.681694308, -87.630261037)" +1348067,G033281,2001-01-16 13:30:00,011XX N WASHTENAW AV,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,783,True,False,1311,NA,NA,NA,18,1158142,1907777,2001,ERROR,41.902688501,-87.694557839,"(41.902688501, -87.694557839)" +9251968,HW395546,2013-05-19 00:01:00,005XX E 32ND ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,2608,False,False,211,2,4,35,06,1180472,1883635,2013,2013-08-08 11:12:43,41.835955998,-87.613280481,"(41.835955998, -87.613280481)" +1866081,G710432,2001-08-30 04:15:00,055XX W NORTH AV,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,3533,False,False,2532,NA,NA,NA,05,1138844,1910120,2001,ERROR,41.909490409,-87.765386627,"(41.909490409, -87.765386627)" +3462034,HK533284,2004-08-01 00:00:00,004XX N OAKLEY BLVD,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2427,False,False,1313,12,27,24,06,1161044,1902666,2004,2014-04-12 12:43:35,41.888603708,-87.684040351,"(41.888603708, -87.684040351)" +8200080,HT434131,2011-08-05 01:00:00,043XX S HONORE ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3797,False,False,914,9,12,61,07,1164697,1876102,2011,2011-06-08 10:43:36,41.81563292,-87.67137723,"(41.81563292, -87.67137723)" +1532170,G282968,2001-05-15 17:00:00,008XX W CUYLER AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,589,False,False,2322,NA,NA,NA,07,1169953,1927283,2001,ERROR,41.955964358,-87.650603629,"(41.955964358, -87.650603629)" +8748519,HV423660,2012-08-09 23:43:00,003XX N DEARBORN ST,0460,BATTERY,SIMPLE,BAR OR TAVERN,4895,True,False,1831,18,42,8,08B,1175921,1902673,2012,2012-10-08 09:09:49,41.888301019,-87.629406752,"(41.888301019, -87.629406752)" +3064212,HJ783363,2003-11-26 12:45:00,003XX N MICHIGAN AVE,0890,THEFT,FROM BUILDING,RESTAURANT,4952,False,False,122,1,42,32,06,1177207,1902277,2003,ERROR,41.887185329,-87.624696174,"(41.887185329, -87.624696174)" +9266858,HW411605,2013-08-16 21:00:00,026XX N MOZART ST,0820,THEFT,$500 AND UNDER,STREET,2,False,False,1411,14,35,22,06,1156956,1917741,2013,ERROR,41.930054663,-87.69864338,"(41.930054663, -87.69864338)" +2563813,HJ151075,2003-01-24 17:00:00,037XX S DR MARTIN LUTHER KING JR DR,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,995,False,False,211,NA,3,35,05,1179437,1879984,2003,ERROR,41.825961127,-87.617189893,"(41.825961127, -87.617189893)" +6272130,HP360251,2008-05-27 23:19:23,078XX S CLYDE AVE,0495,BATTERY,AGGRAVATED OF A SENIOR CITIZEN,RESIDENCE,1708,True,False,414,4,8,43,04B,1191603,1853399,2008,ERROR,41.752723145,-87.573418451,"(41.752723145, -87.573418451)" +1694291,G483134,2001-08-14 15:00:00,063XX S ELLIS AV,041A,BATTERY,AGGRAVATED: HANDGUN,STREET,1918,False,False,314,NA,NA,NA,04B,1184186,1863155,2001,ERROR,41.779671095,-87.600293674,"(41.779671095, -87.600293674)" +9180052,HW325256,2013-06-19 00:05:00,063XX S DR MARTIN LUTHER KING JR DR,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA PLATFORM,2418,True,False,312,3,20,69,11,1179964,1863307,2013,ERROR,41.780185918,-87.615767262,"(41.780185918, -87.615767262)" +9280642,HW425506,2013-08-26 22:00:00,018XX N LARRABEE ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,2483,False,False,1813,18,43,7,05,1171935,1912782,2013,ERROR,41.916129478,-87.643746195,"(41.916129478, -87.643746195)" +4593336,HM184287,2006-02-16 10:20:00,008XX E 103RD ST,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",3892,False,False,512,5,9,50,08B,1183714,1836812,2006,2006-06-03 03:53:14,41.707394098,-87.602843897,"(41.707394098, -87.602843897)" +8204247,HT438156,2011-08-08 14:10:00,092XX S CLYDE AVE,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,845,False,False,413,4,8,48,07,1191741,1844312,2011,2011-09-08 09:44:36,41.727784247,-87.573206878,"(41.727784247, -87.573206878)" +7787558,HS589500,2010-10-27 10:00:00,070XX S PERRY AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3600,False,False,731,7,6,69,07,1176581,1858104,2010,2010-05-11 08:26:29,41.765985117,-87.628326152,"(41.765985117, -87.628326152)" +5607127,HN408670,2007-06-16 08:15:00,002XX W 106TH PL,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4526,False,True,512,5,34,49,14,1176238,1834270,2007,ERROR,41.700589255,-87.630296938,"(41.700589255, -87.630296938)" +8287357,HT521174,2011-09-30 11:00:00,052XX W JACKSON BLVD,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,2963,False,False,1522,15,29,25,14,1141368,1898125,2011,2011-01-10 10:34:37,41.876528452,-87.756410705,"(41.876528452, -87.756410705)" +2090773,HH320445,2002-04-20 19:30:00,054XX W JACKSON BL,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,3689,False,False,1522,NA,NA,NA,26,1140018,1898171,2002,ERROR,41.876679476,-87.761366414,"(41.876679476, -87.761366414)" +9718774,HX368460,2014-07-31 11:30:00,042XX S CALIFORNIA AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4785,False,False,921,9,12,58,05,1158367,1876251,2014,2014-07-08 12:40:12,41.816173258,-87.694592874,"(41.816173258, -87.694592874)" +5349618,HN204723,2007-03-02 20:20:00,036XX W CORTLAND ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3550,True,False,2535,25,26,22,08B,1151444,1912332,2007,2007-08-03 21:51:06,41.915322073,-87.719041079,"(41.915322073, -87.719041079)" +9883576,HX534835,2014-12-08 06:30:00,026XX N OAK PARK AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1739,False,False,2512,25,36,18,14,1130669,1917134,2014,ERROR,41.928882318,-87.79525692,"(41.928882318, -87.79525692)" +2130685,HH371576,2002-05-14 14:00:00,034XX N NARRAGANSETT AVE,0810,THEFT,OVER $500,"SCHOOL, PUBLIC, BUILDING",2624,False,False,1632,NA,36,17,06,1133059,1921942,2002,2014-04-12 12:43:35,41.942034525,-87.786361549,"(41.942034525, -87.786361549)" +1450362,G178877,2001-03-29 17:50:47,003XX W 52 PL,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,3979,False,False,934,NA,NA,NA,04B,1174942,1870105,2001,ERROR,41.798953863,-87.633975858,"(41.798953863, -87.633975858)" +3802924,HL107794,2005-01-05 11:00:00,051XX S CICERO AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,4516,True,False,814,8,23,56,16,1145182,1870371,2005,ERROR,41.800296394,-87.743107012,"(41.800296394, -87.743107012)" +6423969,HP506680,2008-08-08 17:00:00,105XX S VINCENNES AVE,0890,THEFT,FROM BUILDING,CONSTRUCTION SITE,3187,False,False,2212,22,19,72,06,1168786,1835112,2008,2008-06-09 10:53:12,41.703063528,-87.657559187,"(41.703063528, -87.657559187)" +4619517,HM216542,2006-03-06 13:00:00,032XX S KEDZIE AVE,0890,THEFT,FROM BUILDING,RESTAURANT,4455,False,False,1033,10,12,30,06,1155585,1883125,2006,2006-08-03 04:01:50,41.835092676,-87.704613393,"(41.835092676, -87.704613393)" +3896698,HL272683,2005-04-03 19:00:00,028XX N NARRAGANSETT AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),40,False,False,2511,25,36,19,06,1133225,1917988,2005,2014-04-12 12:43:35,41.931181393,-87.785844226,"(41.931181393, -87.785844226)" +8760257,HV434693,2012-08-17 15:30:00,051XX N MILWAUKEE AVE,1345,CRIMINAL DAMAGE,TO CITY OF CHICAGO PROPERTY,POLICE FACILITY/VEH PARKING LOT,653,True,False,1623,16,45,11,14,1138481,1933660,2012,ERROR,41.974093149,-87.766147965,"(41.974093149, -87.766147965)" +5284889,HN142213,2007-01-24 07:45:00,003XX W 83RD ST,0810,THEFT,OVER $500,CONSTRUCTION SITE,3850,False,False,622,6,21,44,06,1175483,1849872,2007,2014-04-12 12:43:35,41.743420145,-87.632596441,"(41.743420145, -87.632596441)" +6146157,HP237155,2008-03-21 16:00:00,055XX W NORTH AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),2698,False,False,2532,25,37,25,14,1138898,1910041,2008,ERROR,41.909272642,-87.765190174,"(41.909272642, -87.765190174)" +5828373,HN638214,2007-10-09 16:00:00,013XX E 62ND ST,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,STREET,4548,True,False,314,3,20,42,26,1185883,1864191,2007,ERROR,41.782474101,-87.594039684,"(41.782474101, -87.594039684)" +9962492,HY151390,2015-02-13 20:00:00,019XX W MORSE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,OTHER,581,False,False,2424,24,49,1,14,1161994,1946107,2015,ERROR,42.007788395,-87.679334315,"(42.007788395, -87.679334315)" +6576639,HP649030,2008-10-26 14:44:24,026XX W CHICAGO AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,1628,False,False,1313,12,26,24,05,1158778,1905190,2008,2009-01-08 20:33:55,41.895576534999996,-87.692292699,"(41.895576535, -87.692292699)" +2773697,HJ394838,2003-05-29 17:10:00,008XX N AVERS AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,1426,True,False,1112,NA,27,23,18,1150597,1905710,2003,ERROR,41.897167269,-87.722326187,"(41.897167269, -87.722326187)" +3455350,HK523596,2004-07-29 00:00:00,068XX W SHAKESPEARE AVE,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,876,False,False,2512,25,36,25,07,1130189,1913566,2004,ERROR,41.919099535,-87.797102792,"(41.919099535, -87.797102792)" +1510568,G238792,2001-04-26 20:58:13,047XX W LAKE ST,2024,NARCOTICS,POSS: HEROIN(WHITE),ALLEY,1889,True,False,1113,NA,NA,NA,18,1144354,1901858,2001,ERROR,41.886716653,-87.745353037,"(41.886716653, -87.745353037)" +10002833,HY192117,2015-03-20 16:50:00,109XX S MICHIGAN AVE,0560,ASSAULT,SIMPLE,STREET,2360,False,False,513,5,9,49,08A,1178806,1832645,2015,ERROR,41.696072117,-87.620943149,"(41.696072117, -87.620943149)" +9628747,HX278675,2014-05-27 15:00:00,027XX N CLYBOURN AVE,0890,THEFT,FROM BUILDING,GROCERY FOOD STORE,2623,False,False,1931,19,32,7,06,1162878,1918140,2014,2014-03-06 12:44:29,41.931027188,-87.676870318,"(41.931027188, -87.676870318)" +9380277,HW523315,2013-11-06 16:00:00,072XX S MAPLEWOOD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3549,False,False,832,8,18,66,08B,1160671,1856428,2013,ERROR,41.761728977,-87.686688178,"(41.761728977, -87.686688178)" +3098367,HJ826189,2003-12-19 03:20:00,087XX S SAGINAW AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,RESIDENCE,1560,True,False,423,4,7,46,15,1195358,1847409,2003,ERROR,41.736194269,-87.559855496,"(41.736194269, -87.559855496)" +8700475,HV376946,2012-07-11 02:30:00,013XX W LOYOLA AVE,0820,THEFT,$500 AND UNDER,RESIDENCE,179,False,False,2432,24,40,1,06,1165965,1943761,2012,ERROR,42.001266756,-87.664791595,"(42.001266756, -87.664791595)" +6309518,HP399117,2008-06-16 06:00:00,047XX S WOLCOTT AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,2208,False,False,931,9,20,61,05,1164528,1873118,2008,ERROR,41.80744805,-87.672081377,"(41.80744805, -87.672081377)" +2246591,HH520136,2002-07-19 00:30:00,032XX W BELLE PLAINE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1923,False,False,1724,NA,33,16,14,1153950,1927112,2002,ERROR,41.955829898,-87.709439072,"(41.955829898, -87.709439072)" +1703748,G500124,2001-08-22 02:45:00,059XX W FULTON ST,0460,BATTERY,SIMPLE,APARTMENT,1254,False,True,1512,NA,NA,NA,08B,1136489,1901353,2001,ERROR,41.885475115,-87.774247889,"(41.885475115, -87.774247889)" +2097320,HH313066,2002-04-17 12:39:00,044XX S FEDERAL ST,1350,CRIMINAL TRESPASS,TO STATE SUP LAND,CHA PARKING LOT/GROUNDS,4334,True,False,221,NA,NA,NA,26,1176474,1875517,2002,ERROR,41.813770553,-87.628194868,"(41.813770553, -87.628194868)" +2304088,HH586111,2002-08-17 09:18:40,014XX S SPRINGFIELD AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,3059,False,False,1011,NA,24,29,14,1150654,1892710,2002,ERROR,41.861492727,-87.722456686,"(41.861492727, -87.722456686)" +2503528,HH844467,2002-12-17 17:20:00,086XX S COMMERCIAL AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,2687,False,False,424,NA,10,46,08B,1197693,1847869,2002,ERROR,41.737398641,-87.551285747,"(41.737398641, -87.551285747)" +7702717,HS509807,2010-09-11 03:30:00,057XX S DR MARTIN LUTHER KING JR DR,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2786,False,False,234,2,20,40,14,1179868,1866840,2010,2010-11-09 10:33:16,41.789883007,-87.616011151,"(41.789883007, -87.616011151)" +4497359,HL799972,2005-12-20 14:10:00,0000X N STATE ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,687,True,False,122,1,42,32,06,1176321,1900593,2005,ERROR,41.882584372,-87.628000611,"(41.882584372, -87.628000611)" +1913830,G774488,2001-11-21 17:00:00,004XX S DEARBORN ST,0810,THEFT,OVER $500,COMMERCIAL / BUSINESS OFFICE,4896,False,False,132,NA,NA,NA,06,1176036,1898446,2001,2014-04-12 12:43:35,41.876699301,-87.629111816,"(41.876699301, -87.629111816)" +7752781,HS561321,2010-10-12 19:00:00,023XX N ORCHARD ST,0810,THEFT,OVER $500,STREET,3795,False,False,1812,18,43,7,06,1171165,1916071,2010,ERROR,41.925171608,-87.646478328,"(41.925171608, -87.646478328)" +1495352,G236482,2001-04-25 08:00:00,054XX N SHERIDAN RD,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,1411,False,False,2023,NA,NA,NA,26,1168688,1936590,2001,ERROR,41.981530612,-87.654983135,"(41.981530612, -87.654983135)" +4269646,HL579828,2005-08-29 19:47:25,029XX N MONITOR AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1280,False,True,2514,25,30,19,08B,1136910,1919097,2005,ERROR,41.934159241,-87.772275653,"(41.934159241, -87.772275653)" +8734967,HV410309,2012-06-06 09:00:00,030XX N ELSTON AVE,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,OTHER,2621,False,False,1411,14,1,21,26,1158467,1919885,2012,2014-03-02 14:06:09,41.935907119,-87.693031988,"(41.935907119, -87.693031988)" +6183611,HP273306,2008-04-10 19:00:00,031XX N MELVINA AVE,0810,THEFT,OVER $500,STREET,2739,False,False,2511,25,36,19,06,1134469,1920529,2008,2008-12-04 08:34:56,41.938132293,-87.781212545,"(41.938132293, -87.781212545)" +7078825,HR486686,2009-08-16 11:45:00,049XX W AUGUSTA BLVD,0470,PUBLIC PEACE VIOLATION,RECKLESS CONDUCT,RESIDENTIAL YARD (FRONT/BACK),3558,True,False,1531,15,37,25,24,1143223,1906157,2009,ERROR,41.898534802,-87.749398973,"(41.898534802, -87.749398973)" +5779480,HN586714,2007-09-13 10:30:14,006XX W 61ST ST,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,SIDEWALK,2183,True,False,711,7,16,68,18,1172737,1864389,2007,ERROR,41.783317484,-87.642230568,"(41.783317484, -87.642230568)" +9870258,HX520700,2014-11-25 22:12:00,062XX W 64TH ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,990,False,False,812,8,23,64,07,1135733,1861466,2014,2014-02-12 12:51:55,41.776032284,-87.777971032,"(41.776032284, -87.777971032)" +8288909,HT523203,2011-10-01 17:20:00,048XX W FLOURNOY ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2283,True,False,1533,15,24,25,18,1144395,1896558,2011,2011-01-10 18:31:03,41.872172039,-87.745335817,"(41.872172039, -87.745335817)" +8102316,HT336736,2011-06-08 11:03:00,042XX S ASHLAND AVE,0850,THEFT,ATTEMPT THEFT,PARKING LOT/GARAGE(NON.RESID.),4936,True,False,914,9,12,61,06,1166338,1876850,2011,2011-09-06 11:16:04,41.817650668,-87.665336409,"(41.817650668, -87.665336409)" +7465353,HS267327,2010-04-20 07:15:00,059XX S INDIANA AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4064,False,False,233,2,20,40,05,1178571,1865818,2010,2010-03-07 11:45:21,41.787108136,-87.620797891,"(41.787108136, -87.620797891)" +2448483,HH774056,2002-11-12 03:30:00,027XX S EMERALD AVE,0820,THEFT,$500 AND UNDER,STREET,467,False,False,923,NA,11,60,06,1171708,1886483,2002,2014-04-12 12:43:35,41.843968255,-87.645354708,"(41.843968255, -87.645354708)" +7555079,HS359022,2010-06-14 11:45:00,021XX N HUMBOLDT BLVD,0810,THEFT,OVER $500,SIDEWALK,576,False,False,1414,14,35,22,06,1156190,1914398,2010,ERROR,41.920896739,-87.701548757,"(41.920896739, -87.701548757)" +10102657,HY291732,2015-04-15 12:30:00,098XX S CHARLES ST,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,RESIDENCE,1478,False,False,2213,22,19,72,11,1167555,1839283,2015,ERROR,41.714535835,-87.661947749,"(41.714535835, -87.661947749)" +9023020,HW170629,2013-02-26 04:45:00,119XX S PERRY AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,1991,False,False,522,5,9,53,03,1177683,1825981,2013,2013-02-04 12:18:39,41.677810527,-87.625255393,"(41.677810527, -87.625255393)" +4650295,HM247617,2006-03-23 06:22:00,027XX N RUTHERFORD AVE,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,3249,True,False,2512,25,36,18,07,1130917,1917271,2006,ERROR,41.929253988,-87.794342417,"(41.929253988, -87.794342417)" +7862271,HS676968,2010-12-26 15:15:00,072XX S ROCKWELL ST,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3255,False,True,831,8,18,66,26,1160255,1856471,2010,2011-01-01 13:13:56,41.761855547,-87.688211689,"(41.761855547, -87.688211689)" +1549513,G304867,2001-05-26 17:00:00,064XX S COTTAGE GROVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,"SCHOOL, PUBLIC, GROUNDS",4371,False,False,312,NA,NA,NA,07,1182638,1862119,2001,ERROR,41.776864286,-87.606000899,"(41.776864286, -87.606000899)" +1455026,G184846,2001-04-01 17:12:49,083XX S VERNON AV,0560,ASSAULT,SIMPLE,RESIDENCE,544,False,False,632,NA,NA,NA,08A,1180775,1849537,2001,ERROR,41.742380976,-87.613216643,"(41.742380976, -87.613216643)" +4427823,HL724219,2005-11-08 00:00:00,014XX E 54TH ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,2419,False,False,2132,2,4,41,05,1186925,1869842,2005,2006-04-02 03:37:59,41.797956235,-87.590040347,"(41.797956235, -87.590040347)" +5679423,HN487410,2007-07-25 02:40:00,059XX S CAMPBELL AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2902,True,False,824,8,16,66,14,1160764,1865105,2007,ERROR,41.785537974,-87.686107985,"(41.785537974, -87.686107985)" +7495732,HS275457,2010-04-25 14:45:00,043XX S DR MARTIN LUTHER KING JR DR,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,1747,False,False,222,2,3,38,03,1179615,1876521,2010,2010-04-06 18:55:38,41.816454308,-87.616642833,"(41.816454308, -87.616642833)" +7803721,HS613635,2010-11-13 12:10:00,005XX S PULASKI RD,031A,ROBBERY,ARMED: HANDGUN,STREET,2845,False,False,1132,11,24,26,03,1149769,1897321,2010,ERROR,41.874163085,-87.725585552,"(41.874163085, -87.725585552)" +1710750,G511505,2001-08-25 23:00:00,007XX W WAVELAND AV,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),949,False,False,2323,NA,NA,NA,06,1170739,1924926,2001,2014-04-12 12:43:35,41.94947945,-87.647783443,"(41.94947945, -87.647783443)" +2224263,HH493721,2002-07-07 18:09:00,104XX S WABASH AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1931,False,False,512,NA,9,49,14,1178385,1835579,2002,ERROR,41.70413297,-87.622395901,"(41.70413297, -87.622395901)" +6935102,HR333609,2009-05-21 15:30:00,100XX W OHARE ST,0560,ASSAULT,SIMPLE,AIRPORT/AIRCRAFT,3283,False,False,1651,16,41,76,08A,1100635,1934208,2009,ERROR,41.976200173,-87.905312411,"(41.976200173, -87.905312411)" +5382077,HN231591,2007-03-08 10:10:00,048XX S CHICAGO BEACH DR,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,1299,False,False,2132,2,4,39,26,1187822,1873221,2007,ERROR,41.807207115,-87.5866433,"(41.807207115, -87.5866433)" +2472765,HH804536,2002-11-26 22:00:00,045XX N MILWAUKEE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,OTHER,4091,False,False,1623,NA,45,15,14,1140801,1930053,2002,ERROR,41.96415277,-87.757705682,"(41.96415277, -87.757705682)" +8663325,HV338303,2012-06-16 12:00:00,079XX S MAY ST,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,4672,False,False,612,6,17,71,26,1169994,1852029,2012,ERROR,41.749460143,-87.652645927,"(41.749460143, -87.652645927)" +3682557,HK717624,2004-10-29 10:00:00,018XX S KARLOV AVE,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,CHA PARKING LOT/GROUNDS,594,True,False,1012,10,24,29,18,1149325,1890347,2004,ERROR,41.855034211,-87.727396457,"(41.855034211, -87.727396457)" +8506092,HV183131,2012-03-04 16:20:00,085XX S UNIVERSITY AVE,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,2189,False,False,412,4,8,45,08A,1185352,1848596,2012,2012-06-03 14:26:12,41.739692446,-87.596476215,"(41.739692446, -87.596476215)" +1536639,G279063,2001-05-15 01:15:00,069XX S MICHIGAN AV,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,APARTMENT,3491,False,False,322,NA,NA,NA,04B,1178326,1858850,2001,ERROR,41.767992795,-87.621907549,"(41.767992795, -87.621907549)" +8590655,HV265015,2012-04-30 16:07:00,020XX W 63RD ST,1330,CRIMINAL TRESPASS,TO LAND,RESTAURANT,3738,True,False,726,7,15,67,26,1163970,1862819,2012,2012-01-05 05:05:59,41.779198065,-87.674417489,"(41.779198065, -87.674417489)" +3957643,HL229706,2005-03-12 02:50:00,038XX W 45TH PL,2022,NARCOTICS,POSS: COCAINE,SIDEWALK,1543,True,False,821,8,14,57,18,1151741,1874241,2005,ERROR,41.810790121,-87.718951399,"(41.810790121, -87.718951399)" +5944517,HN743446,2007-12-01 08:00:00,052XX W HUTCHINSON ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1515,False,False,1624,16,38,15,07,1140768,1927717,2007,2007-10-12 01:03:40,41.957743188,-87.757884694,"(41.957743188, -87.757884694)" +8314963,HT548064,2011-10-18 11:20:00,039XX W WILCOX ST,0520,ASSAULT,AGGRAVATED:KNIFE/CUTTING INSTR,STREET,2006,False,False,1122,11,28,26,04A,1150350,1899010,2011,ERROR,41.878786581,-87.723408302,"(41.878786581, -87.723408302)" +2262015,HH538299,2002-07-27 00:01:00,070XX S PULASKI RD,1310,CRIMINAL DAMAGE,TO PROPERTY,SMALL RETAIL STORE,4192,False,False,833,NA,13,65,14,1150947,1857820,2002,ERROR,41.765743919,-87.722291814,"(41.765743919, -87.722291814)" +3238230,HK259979,2004-03-24 11:30:00,019XX W PRYOR AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,692,False,False,2212,22,19,75,26,1165739,1831480,2004,ERROR,41.69316178,-87.668819238,"(41.69316178, -87.668819238)" +8307337,HT541535,2011-10-13 19:10:00,016XX S DRAKE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,RESIDENCE PORCH/HALLWAY,3686,True,False,1021,10,24,29,18,1153013,1891601,2011,ERROR,41.858403118,-87.713826549,"(41.858403118, -87.713826549)" +8075720,HT308470,2011-05-22 12:18:00,030XX N LECLAIRE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,2281,True,False,2521,25,31,19,18,1141796,1919925,2011,ERROR,41.936342208,-87.754298893,"(41.936342208, -87.754298893)" +1831471,G661456,2001-11-02 23:30:30,006XX E 46 ST,0810,THEFT,OVER $500,APARTMENT,3210,False,False,222,NA,NA,NA,06,1181656,1874705,2001,2014-04-12 12:43:35,41.811424106,-87.609212194,"(41.811424106, -87.609212194)" +1577474,G323442,2001-06-04 17:25:00,005XX S DESPLAINES ST,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,4460,True,False,131,NA,NA,NA,18,1172055,1897694,2001,ERROR,41.874724464,-87.643750852,"(41.874724464, -87.643750852)" +6763360,HR180663,2009-02-20 17:00:00,028XX N CLARK ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2537,False,False,1932,19,44,6,06,1171377,1919102,2009,ERROR,41.933484144,-87.645610016,"(41.933484144, -87.645610016)" +6307441,HP396661,2008-06-14 20:00:00,105XX S PERRY AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4564,False,False,512,5,34,49,14,1177515,1834741,2008,ERROR,41.701853045,-87.625606891,"(41.701853045, -87.625606891)" +1963559,HH143797,2002-01-23 15:00:00,005XX W HARRISON ST,2111,NARCOTICS,SALE/DEL HYPODERMIC NEEDLE,HOTEL/MOTEL,4004,True,False,131,NA,NA,NA,26,1173114,1897625,2002,ERROR,41.874511711,-87.639864736,"(41.874511711, -87.639864736)" +4056395,HL405242,2005-06-07 01:00:00,060XX S MARSHFIELD AVE,0820,THEFT,$500 AND UNDER,CHURCH/SYNAGOGUE/PLACE OF WORSHIP,154,False,False,714,7,15,67,06,1166409,1864855,2005,2014-04-12 12:43:35,41.784733473,-87.665417872,"(41.784733473, -87.665417872)" +6587922,HP660944,2008-11-01 23:35:00,069XX N GLENWOOD AVE,0460,BATTERY,SIMPLE,SIDEWALK,4341,False,False,2431,24,49,1,08B,1165536,1945973,2008,2008-03-11 08:27:31,42.007345709,-87.666306413,"(42.007345709, -87.666306413)" +7794546,HS604138,2010-11-07 23:30:00,025XX S CALIFORNIA AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4457,False,True,1033,10,12,30,08B,1158063,1887206,2010,ERROR,41.846241276,-87.695409603,"(41.846241276, -87.695409603)" +9556953,HX208548,2014-04-02 10:30:00,023XX N KEDZIE BLVD,0890,THEFT,FROM BUILDING,RESIDENCE,1548,False,False,1413,14,26,22,06,1154602,1915578,2014,2014-05-04 00:39:46,41.924166711,-87.707351793,"(41.924166711, -87.707351793)" +4911586,HM527657,2006-08-08 09:00:00,013XX W HASTINGS ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,615,False,False,1231,12,2,28,11,1167818,1893895,2006,2006-09-09 04:19:56,41.864392058,-87.659416725,"(41.864392058, -87.659416725)" +8530103,HV207352,2012-03-21 03:28:00,037XX N DRAKE AVE,041A,BATTERY,AGGRAVATED: HANDGUN,ALLEY,1354,False,False,1732,17,35,16,04B,1152101,1924527,2012,ERROR,41.948773209,-87.716304871,"(41.948773209, -87.716304871)" +3202279,HK212812,2004-03-01 04:50:00,119XX S MICHIGAN AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,4345,False,False,532,5,9,53,03,1178938,1826078,2004,ERROR,41.678048316,-87.620658763,"(41.678048316, -87.620658763)" +1535129,G277552,2001-05-14 13:00:20,070XX S RHODES AV,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",895,False,False,322,NA,NA,NA,08B,1181167,1858324,2001,ERROR,41.766484442,-87.611510246,"(41.766484442, -87.611510246)" +9439829,HW583592,2013-12-22 09:00:00,010XX W COLUMBIA AVE,0890,THEFT,FROM BUILDING,RESIDENCE PORCH/HALLWAY,4348,True,False,2432,24,49,1,06,1167737,1945049,2013,ERROR,42.004762897,-87.658235361,"(42.004762897, -87.658235361)" +1486467,G222730,2001-04-19 14:30:00,007XX N MICHIGAN AV,0820,THEFT,$500 AND UNDER,DEPARTMENT STORE,255,True,False,1834,NA,NA,NA,06,1177338,1905329,2001,2014-04-12 12:43:35,41.8955572,-87.624122473,"(41.8955572, -87.624122473)" +3473591,HK542000,2004-08-07 02:10:00,0000X W MAPLE ST,0460,BATTERY,SIMPLE,STREET,4558,False,False,1824,18,42,8,08B,1176112,1907663,2004,ERROR,41.90198953,-87.628554812,"(41.90198953, -87.628554812)" +6110065,HP205470,2008-03-01 12:00:00,051XX S HYDE PARK BLVD,0810,THEFT,OVER $500,STREET,2074,False,False,2132,2,4,41,06,1188191,1871483,2008,2008-05-03 12:11:15,41.80242912,-87.585345416,"(41.80242912, -87.585345416)" +2531598,HJ111717,2002-12-27 14:00:00,005XX N STATE ST,0890,THEFT,FROM BUILDING,COMMERCIAL / BUSINESS OFFICE,3490,False,False,1834,NA,42,8,06,1176317,1903817,2002,ERROR,41.891431292,-87.62791798,"(41.891431292, -87.62791798)" +8908679,HV581869,2012-11-29 17:00:00,029XX S DR MARTIN LUTHER KING JR DR,0880,THEFT,PURSE-SNATCHING,RESIDENCE PORCH/HALLWAY,2582,False,False,133,1,4,35,06,1179425,1885823,2012,2012-02-12 14:35:58,41.841984044,-87.617055292,"(41.841984044, -87.617055292)" +8613270,HV287141,2012-02-24 09:00:00,039XX W 79TH ST,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",4666,False,False,834,8,18,70,06,1151305,1851848,2012,ERROR,41.749348763,-87.721135378,"(41.749348763, -87.721135378)" +7961115,HT185399,2011-03-02 11:00:00,017XX N WHIPPLE ST,0460,BATTERY,SIMPLE,STREET,631,False,False,1421,14,26,23,08B,1155646,1911652,2011,ERROR,41.91337247,-87.703621623,"(41.91337247, -87.703621623)" +7919740,HT148765,2011-02-05 14:25:00,075XX S YALE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,3762,False,True,623,6,17,69,08B,1175854,1854934,2011,2011-08-02 15:08:26,41.75730259,-87.631085699,"(41.75730259, -87.631085699)" +2663371,HJ278006,2003-04-01 19:15:00,067XX S WOLCOTT AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1220,False,False,726,NA,15,67,26,1164809,1859980,2003,ERROR,41.77138979,-87.67142173,"(41.77138979, -87.67142173)" +6550942,HP624443,2008-10-12 17:30:00,071XX S LAWNDALE AVE,0810,THEFT,OVER $500,RESIDENTIAL YARD (FRONT/BACK),1714,False,False,833,8,13,65,06,1152981,1856803,2008,ERROR,41.762913214,-87.714863238,"(41.762913214, -87.714863238)" +9052143,HW197487,2013-03-18 16:14:00,006XX W CHICAGO AVE,0460,BATTERY,SIMPLE,COMMERCIAL / BUSINESS OFFICE,1207,False,True,1822,18,27,8,08B,1172179,1905661,2013,ERROR,41.896583685,-87.643060288,"(41.896583685, -87.643060288)" +2128982,HH359279,2002-05-09 08:20:00,040XX S FEDERAL ST,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,CHA PARKING LOT/GROUNDS,2837,True,False,214,NA,3,38,18,1176399,1878070,2002,ERROR,41.820777898,-87.628393146,"(41.820777898, -87.628393146)" +9466436,HX119098,2014-01-18 12:15:00,016XX N MAPLEWOOD AVE,0560,ASSAULT,SIMPLE,RESIDENCE,3224,True,False,1434,14,1,24,08A,1159155,1910783,2014,ERROR,41.910916447,-87.690754192,"(41.910916447, -87.690754192)" +6833293,HR212717,2009-03-12 23:27:00,067XX S PARNELL AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,4242,False,False,722,7,6,68,14,1173780,1859903,2009,2009-02-04 16:12:49,41.770984325,-87.638539479,"(41.770984325, -87.638539479)" +5816963,HN621314,2007-09-30 22:18:00,033XX W 65TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3009,True,False,823,8,15,66,14,1155444,1861367,2007,ERROR,41.775388583,-87.705713759,"(41.775388583, -87.705713759)" +8683873,HV358753,2012-06-29 14:40:00,062XX S EBERHART AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,2697,True,False,313,3,20,42,18,1180607,1863977,2012,ERROR,41.782009714,-87.613389379,"(41.782009714, -87.613389379)" +9332406,HW476255,2013-10-02 09:15:00,003XX E 51ST ST,0860,THEFT,RETAIL THEFT,SMALL RETAIL STORE,1444,True,False,231,2,3,40,06,1179083,1871232,2013,2013-02-10 12:40:29,41.801952997,-87.618755627,"(41.801952997, -87.618755627)" +6106681,HP199670,2008-02-27 19:00:00,003XX W 45TH ST,0890,THEFT,FROM BUILDING,PARK PROPERTY,1877,False,False,935,9,3,37,06,1174657,1874998,2008,2008-01-03 09:26:24,41.812387089,-87.634875192,"(41.812387089, -87.634875192)" +8322735,HT556921,2011-10-24 09:45:00,016XX W MONROE ST,1320,CRIMINAL DAMAGE,TO VEHICLE,VEHICLE NON-COMMERCIAL,2411,False,True,1211,12,2,28,14,1165749,1899667,2011,2011-10-11 16:19:44,41.880275226,-87.666847435,"(41.880275226, -87.666847435)" +8806688,HV480499,2012-09-17 23:00:00,066XX S GREENWOOD AVE,0560,ASSAULT,SIMPLE,APARTMENT,661,True,True,321,3,5,42,08A,1184405,1861060,2012,ERROR,41.773917101,-87.599556337,"(41.773917101, -87.599556337)" +5649250,HN423753,2007-06-23 12:55:00,033XX W FULLERTON AVE,0820,THEFT,$500 AND UNDER,OTHER,203,False,False,1413,14,35,22,06,1153552,1915794,2007,2014-04-12 12:43:35,41.924780401,-87.711204182,"(41.924780401, -87.711204182)" +9481419,HX134548,2014-01-31 16:30:00,008XX N RIDGEWAY AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,535,True,False,1112,11,27,23,18,1151276,1905276,2014,2014-05-02 00:38:42,41.895963033,-87.719843697,"(41.895963033, -87.719843697)" +7452945,HS254403,2010-04-12 18:00:00,011XX S DELANO CT E,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4917,False,True,131,1,2,32,26,1175238,1895318,2010,ERROR,41.8681338,-87.632135611,"(41.8681338, -87.632135611)" +9863463,HX513290,2014-11-20 01:26:00,028XX W LAWRENCE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,3692,True,False,1911,19,47,4,08B,1156822,1931722,2014,ERROR,41.968422144,-87.698755406,"(41.968422144, -87.698755406)" +3888951,HL158249,2005-02-02 13:25:00,035XX W CHICAGO AVE,2027,NARCOTICS,POSS: CRACK,RESIDENCE,2282,True,False,1121,11,27,23,18,1152516,1905145,2005,ERROR,41.895579135,-87.715292874,"(41.895579135, -87.715292874)" +5869284,HN668807,2007-10-24 23:00:00,049XX W WEST END AVE,0326,ROBBERY,AGGRAVATED VEHICULAR HIJACKING,STREET,934,False,False,1532,15,28,25,03,1143315,1900566,2007,ERROR,41.883190721,-87.749200857,"(41.883190721, -87.749200857)" +2703495,HJ328167,2003-04-27 16:00:00,053XX S MAY ST,0610,BURGLARY,FORCIBLE ENTRY,ABANDONED BUILDING,3328,False,False,934,NA,16,61,05,1169605,1869148,2003,ERROR,41.796445235,-87.653575559,"(41.796445235, -87.653575559)" +4257280,HL574185,2005-08-27 01:30:00,084XX S ELIZABETH ST,0620,BURGLARY,UNLAWFUL ENTRY,APARTMENT,2999,False,False,613,6,21,71,05,1169506,1848666,2005,ERROR,41.740242188,-87.65453137,"(41.740242188, -87.65453137)" +5932502,HN731979,2007-11-28 09:30:00,077XX S BURNHAM AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",3095,False,False,421,4,7,43,08B,1196020,1854375,2007,2007-11-12 01:04:21,41.755293165,-87.557200019,"(41.755293165, -87.557200019)" +6238887,HP326210,2008-05-09 08:00:00,011XX N CLARK ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),1292,False,False,1824,18,42,8,06,1175376,1907750,2008,2008-12-05 11:05:25,41.902244823,-87.631255595,"(41.902244823, -87.631255595)" +1875426,G723872,2001-12-03 03:00:00,043XX S SAWYER AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4250,False,False,821,NA,NA,NA,07,1155459,1875390,2001,ERROR,41.813869382,-87.705283202,"(41.813869382, -87.705283202)" +7725847,HS533443,2010-09-25 13:18:00,023XX N LONG AVE,0810,THEFT,OVER $500,PARK PROPERTY,2755,False,False,2515,25,37,19,06,1139957,1914995,2010,ERROR,41.922847656,-87.761178414,"(41.922847656, -87.761178414)" +3451995,HK003019,2004-07-24 01:30:00,013XX N HALSTED ST,2027,NARCOTICS,POSS: CRACK,STREET,687,True,False,1822,18,27,8,18,1170807,1909089,2004,ERROR,41.906020499,-87.647998785,"(41.906020499, -87.647998785)" +4626034,HM223084,2006-03-09 20:19:52,060XX S HARPER AVE,0890,THEFT,FROM BUILDING,APARTMENT,985,False,True,314,3,5,42,06,1187444,1865041,2006,ERROR,41.784769597,-87.588289691,"(41.784769597, -87.588289691)" +2635210,HJ244826,2003-03-17 22:30:00,041XX W NORTH AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4737,False,False,2534,25,30,23,07,1148753,1910358,2003,ERROR,41.90995769,-87.728978726,"(41.90995769, -87.728978726)" +4038854,HL311429,2005-04-22 14:30:00,055XX N LINCOLN AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,HOTEL/MOTEL,2883,True,False,2011,20,40,4,16,1158216,1936790,2005,ERROR,41.982300536,-87.693490542,"(41.982300536, -87.693490542)" +4031728,HL384570,2005-05-28 07:00:00,003XX N LATROBE AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4941,False,False,1523,15,28,25,07,1141271,1901864,2005,2007-11-06 15:52:33,41.886790527,-87.756674616,"(41.886790527, -87.756674616)" +9537378,HX190485,2014-03-19 09:02:00,118XX S PERRY AVE,0497,BATTERY,AGGRAVATED DOMESTIC BATTERY: OTHER DANG WEAPON,SIDEWALK,2791,False,True,522,5,34,53,04B,1177670,1826389,2014,2014-04-04 00:38:54,41.678930434,-87.625290705,"(41.678930434, -87.625290705)" +7080558,HR488689,2009-08-17 13:55:00,012XX W ARTHUR AVE,0850,THEFT,ATTEMPT THEFT,SIDEWALK,2422,False,False,2432,24,40,1,06,1166693,1943419,2009,ERROR,42.000312672,-87.662123274,"(42.000312672, -87.662123274)" +6119164,HP210946,2008-03-06 13:17:00,020XX N SEDGWICK ST,0460,BATTERY,SIMPLE,STREET,3739,True,False,1814,18,43,7,08B,1173293,1914116,2008,2008-10-03 08:28:03,41.919759963,-87.638717325,"(41.919759963, -87.638717325)" +5259619,HN135255,2007-01-20 17:00:00,041XX N CLARENDON AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,2690,False,False,2322,19,46,3,05,1170144,1927369,2007,2007-12-02 06:20:13,41.956196167,-87.649898952,"(41.956196167, -87.649898952)" +9177023,HW321951,2013-06-16 23:00:00,002XX W 103RD ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4706,True,False,511,5,9,49,18,1176620,1836684,2013,ERROR,41.707205049,-87.628825892,"(41.707205049, -87.628825892)" +2827497,HJ477324,2003-07-07 01:35:00,046XX N CLIFTON AVE,0460,BATTERY,SIMPLE,STREET,1471,False,False,2311,19,46,3,08B,1167640,1930699,2003,ERROR,41.965388238,-87.659007885,"(41.965388238, -87.659007885)" +3012718,HJ716464,2003-10-25 02:42:00,042XX W KAMERLING AVE,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,1750,False,False,2534,25,37,23,06,1147558,1908601,2003,2014-04-12 12:43:35,41.905159324,-87.733413851,"(41.905159324, -87.733413851)" +3294036,HK326749,2004-04-25 21:00:00,051XX W BLOOMINGDALE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4527,False,False,2533,25,37,25,05,1141931,1911525,2004,ERROR,41.913289233,-87.754011314,"(41.913289233, -87.754011314)" +6803317,HR213727,2009-03-12 16:00:00,0000X N STATE ST,0890,THEFT,FROM BUILDING,SMALL RETAIL STORE,4883,False,False,122,1,42,32,06,1176328,1900431,2009,ERROR,41.882139677,-87.627979796,"(41.882139677, -87.627979796)" +9971989,HY161202,2015-02-23 14:56:00,004XX E 48TH ST,0820,THEFT,$500 AND UNDER,APARTMENT,404,False,True,223,2,3,38,06,1180243,1873338,2015,2015-06-03 12:42:07,41.807705499,-87.614436883,"(41.807705499, -87.614436883)" +6271204,HP353232,2008-05-23 19:45:00,094XX S BURNSIDE AVE,0560,ASSAULT,SIMPLE,RESIDENCE,3954,False,False,633,6,9,44,08A,1182714,1842560,2008,2008-02-06 06:39:38,41.72319054,-87.60632814,"(41.72319054, -87.60632814)" +2826618,HJ482716,2003-04-01 13:00:00,034XX W CARROLL AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,3974,False,False,1123,11,28,27,05,1153347,1902226,2003,ERROR,41.887552644,-87.712318379,"(41.887552644, -87.712318379)" +7953888,HT186007,2011-03-02 19:00:00,007XX N WABASH AVE,0820,THEFT,$500 AND UNDER,STREET,92,False,False,1834,18,42,8,06,1176565,1905227,2011,2011-03-03 06:43:40,41.895294805,-87.626964572,"(41.895294805, -87.626964572)" +3979852,HL343037,2005-05-07 19:00:00,016XX E 50TH ST,0810,THEFT,OVER $500,STREET,749,False,False,2132,2,4,39,06,1188465,1872158,2005,2014-04-12 12:43:35,41.804274819,-87.584318985,"(41.804274819, -87.584318985)" +4101174,HL441592,2005-06-24 16:00:00,061XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),44,False,False,825,8,15,66,06,1161471,1863600,2005,2014-04-12 12:43:35,41.781393417,-87.683557482,"(41.781393417, -87.683557482)" +2613348,HJ215734,2003-01-16 16:00:00,077XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,STREET,157,False,False,835,NA,18,70,06,1161675,1853495,2003,2014-04-12 12:43:35,41.753659623,-87.683089644,"(41.753659623, -87.683089644)" +5184536,HM766200,2006-12-09 17:00:00,031XX W 31ST ST,0820,THEFT,$500 AND UNDER,STREET,311,False,False,1033,10,12,30,06,1155985,1883878,2006,2014-04-12 12:43:35,41.837150955,-87.703125393,"(41.837150955, -87.703125393)" +8230238,HT464106,2011-08-24 19:15:00,113XX S STEWART AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,RESIDENCE,3176,False,False,2233,22,34,49,04B,1175621,1829403,2011,ERROR,41.687247278,-87.632701111,"(41.687247278, -87.632701111)" +7374070,HS176750,2010-02-22 20:50:00,019XX S PULASKI RD,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,4263,True,False,1014,10,24,29,15,1150062,1890214,2010,ERROR,41.854654939,-87.724694776,"(41.854654939, -87.724694776)" +5274149,HN143339,2007-01-25 19:00:00,079XX S COTTAGE GROVE AVE,1330,CRIMINAL TRESPASS,TO LAND,TAVERN/LIQUOR STORE,1997,True,False,624,6,8,44,26,1182965,1852736,2007,ERROR,41.751108806,-87.605093358,"(41.751108806, -87.605093358)" +5487050,HN286402,2007-04-16 14:00:00,039XX N LAWNDALE AVE,2890,PUBLIC PEACE VIOLATION,OTHER VIOLATION,"SCHOOL, PUBLIC, BUILDING",640,True,False,1732,17,39,16,26,1150988,1925736,2007,ERROR,41.95211271,-87.720364302,"(41.95211271, -87.720364302)" +9176249,HW319713,2013-06-15 10:45:00,069XX S CRANDON AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE PORCH/HALLWAY,2794,False,True,331,3,5,43,08B,1192569,1859188,2013,ERROR,41.76858515,-87.569690199,"(41.76858515, -87.569690199)" +5970003,HN765590,2007-12-18 14:00:00,028XX N MILWAUKEE AVE,0820,THEFT,$500 AND UNDER,VEHICLE-COMMERCIAL,352,False,False,1412,14,35,21,06,1152714,1918810,2007,2014-04-12 12:43:35,41.933073195,-87.71420336,"(41.933073195, -87.71420336)" +4936885,HM544641,2006-08-16 20:30:00,095XX S INDIANA AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,4828,True,False,511,5,6,49,03,1179217,1841960,2006,ERROR,41.72162439,-87.619155472,"(41.72162439, -87.619155472)" +9474839,HX128512,2014-01-26 16:00:00,019XX W PETERSON AVE,0850,THEFT,ATTEMPT THEFT,RESTAURANT,3213,True,False,2413,24,40,2,06,1162040,1939914,2014,ERROR,41.990793656,-87.679339104,"(41.990793656, -87.679339104)" +7802326,HS611894,2010-11-11 18:45:00,004XX S KEDZIE AVE,0820,THEFT,$500 AND UNDER,SMALL RETAIL STORE,174,False,False,1134,11,28,27,06,1155148,1897810,2010,ERROR,41.87539874,-87.705823098,"(41.87539874, -87.705823098)" +7731223,HS539017,2010-09-28 22:26:00,007XX S CALIFORNIA AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,4969,True,False,1135,11,2,27,18,1157851,1896880,2010,ERROR,41.872792072,-87.695924058,"(41.872792072, -87.695924058)" +5221374,HN105437,2007-01-04 02:13:58,088XX S MARSHFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,600,True,True,2221,22,21,71,08B,1166921,1846213,2007,2014-04-12 12:43:35,41.733566359,-87.664072408,"(41.733566359, -87.664072408)" +4542176,HL749208,2005-11-22 06:19:20,045XX W CERMAK RD,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,2808,True,False,1012,10,24,29,16,1146657,1889031,2005,ERROR,41.851474184,-87.737222838,"(41.851474184, -87.737222838)" +4562689,HM152355,2006-01-29 19:30:00,020XX N LAWLER AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,3832,False,False,2522,25,31,19,05,1142381,1913021,2006,ERROR,41.917386066,-87.752320857,"(41.917386066, -87.752320857)" +9707558,HX357398,2014-07-23 17:30:00,020XX E 71ST ST,0554,ASSAULT,AGG PO HANDS NO/MIN INJURY,SMALL RETAIL STORE,1641,True,False,331,3,5,43,08A,1191387,1858371,2014,ERROR,41.766371955,-87.574049171,"(41.766371955, -87.574049171)" +4714041,HM227293,2006-03-11 20:25:00,065XX S WOLCOTT AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,622,True,False,726,7,15,67,18,1164765,1861497,2006,ERROR,41.775553569,-87.671540228,"(41.775553569, -87.671540228)" +5921937,HN718848,2007-11-20 17:36:30,059XX S PAULINA ST,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,3639,False,False,714,7,15,67,08A,1165991,1865160,2007,2007-02-12 01:04:24,41.785579331,-87.66694177,"(41.785579331, -87.66694177)" +3218316,HK228080,2004-03-03 15:00:00,039XX W 79TH ST,0330,ROBBERY,AGGRAVATED,"SCHOOL, PUBLIC, GROUNDS",4389,False,False,834,8,18,70,03,1151161,1851841,2004,ERROR,41.749332365,-87.721663241,"(41.749332365, -87.721663241)" +3487515,HK557821,2004-08-14 06:20:00,006XX N LONG AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4892,False,False,1524,15,37,25,14,1140252,1903605,2004,ERROR,41.891586777,-87.760374016,"(41.891586777, -87.760374016)" +2451451,HH777241,2002-11-13 04:00:00,084XX S DREXEL AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3522,False,False,632,6,8,44,07,1183741,1848976,2002,ERROR,41.740772908,-87.602366784,"(41.740772908, -87.602366784)" +4502366,HL808084,2005-12-24 13:00:00,066XX N ASHLAND AVE,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,2954,False,False,2432,24,40,1,26,1164366,1944194,2005,ERROR,42.002489027,-87.670661713,"(42.002489027, -87.670661713)" +4931618,HM406308,2006-06-10 20:45:38,026XX W 47TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3205,True,False,911,9,12,58,18,1159669,1873369,2006,ERROR,41.808238032,-87.689895989,"(41.808238032, -87.689895989)" +3614068,HK707958,2004-10-24 23:00:00,066XX S MAY ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3727,False,True,724,7,17,68,08B,1169839,1860579,2004,ERROR,41.77292581,-87.652966109,"(41.77292581, -87.652966109)" +3751122,HK756140,2004-11-17 11:30:00,071XX S VERNON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,2489,True,False,323,3,6,69,18,1180514,1858040,2004,ERROR,41.765720129,-87.613912421,"(41.765720129, -87.613912421)" +8775540,HV450164,2012-08-28 03:10:00,040XX S CALUMET AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3867,True,True,213,2,3,38,08B,1179057,1878335,2012,ERROR,41.821444824,-87.618634338,"(41.821444824, -87.618634338)" +3361951,HK409235,2004-06-04 00:01:00,022XX S WASHTENAW AVE,0820,THEFT,$500 AND UNDER,STREET,380,False,False,1034,10,28,30,06,1158665,1889279,2004,2014-04-12 12:43:35,41.851917522,-87.693143577,"(41.851917522, -87.693143577)" +6028483,HP130477,2008-01-18 08:00:00,054XX S BISHOP ST,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4831,False,False,933,9,16,61,05,1167547,1868813,2008,2008-10-02 09:00:51,41.795570365,-87.661132024,"(41.795570365, -87.661132024)" +3491821,HK563513,2004-08-17 14:22:57,115XX S CHURCH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,DRIVEWAY - RESIDENTIAL,1773,False,False,2212,22,34,75,14,1165666,1828209,2004,ERROR,41.684187135,-87.669178838,"(41.684187135, -87.669178838)" +3018372,HJ720023,2003-10-25 01:00:00,038XX W 31ST ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,2821,False,False,1031,10,22,30,08B,1151504,1883738,2003,ERROR,41.836855819,-87.719571859,"(41.836855819, -87.719571859)" +1354914,G057397,2001-01-28 06:00:00,105XX S TRUMBULL AV,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1221,False,False,2211,NA,NA,NA,14,1155237,1834766,2001,ERROR,41.702395083,-87.707182113,"(41.702395083, -87.707182113)" +4569529,HM158752,2006-01-23 08:00:00,072XX S RHODES AVE,0560,ASSAULT,SIMPLE,APARTMENT,3176,False,True,323,3,6,69,08A,1181195,1857297,2006,ERROR,41.763665603,-87.611439209,"(41.763665603, -87.611439209)" +6806076,HR217090,2009-03-15 12:05:00,054XX N LINCOLN AVE,0820,THEFT,$500 AND UNDER,RESTAURANT,346,False,False,2011,20,40,4,06,1158464,1936018,2009,ERROR,41.980177046,-87.692599697,"(41.980177046, -87.692599697)" +6083955,HP180648,2008-02-17 16:45:01,049XX S LA CROSSE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3195,False,False,814,8,23,56,14,1144901,1871417,2008,ERROR,41.803172073,-87.744111245,"(41.803172073, -87.744111245)" +8807854,HV481114,2012-09-18 08:00:00,006XX E GRAND AVE,0810,THEFT,OVER $500,OTHER,674,False,False,1834,18,42,8,06,1180766,1904096,2012,ERROR,41.89209535,-87.611570504,"(41.89209535, -87.611570504)" +2338089,HH593976,2002-08-20 19:30:00,029XX N CENTRAL PARK AVE,2210,LIQUOR LAW VIOLATION,SELL/GIVE/DEL LIQUOR TO MINOR,TAVERN/LIQUOR STORE,3797,True,False,2523,NA,35,21,22,1151842,1919206,2002,ERROR,41.934177086,-87.717397454,"(41.934177086, -87.717397454)" +8452974,HV130354,2012-01-24 16:11:00,048XX N SHERIDAN RD,0560,ASSAULT,SIMPLE,CURRENCY EXCHANGE,2398,False,True,2024,20,46,3,08A,1168724,1932205,2012,ERROR,41.96949727,-87.654978443,"(41.96949727, -87.654978443)" +5324749,HN183465,2007-02-18 09:00:00,020XX W 70TH PL,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,2289,True,False,735,7,17,67,26,1163866,1857860,2007,ERROR,41.765592081,-87.674937943,"(41.765592081, -87.674937943)" +1623626,G373657,2001-06-27 02:16:41,005XX W DIVISION ST,2027,NARCOTICS,POSS: CRACK,STREET,3021,True,False,1823,NA,NA,NA,18,1172307,1908305,2001,ERROR,41.903836143,-87.642511973,"(41.903836143, -87.642511973)" +6375121,HP457261,2008-07-17 01:28:39,042XX W VAN BUREN ST,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,SIDEWALK,4906,False,False,1132,11,24,26,04B,1148391,1897730,2008,2008-01-08 22:09:13,41.875312091,-87.730634436,"(41.875312091, -87.730634436)" +3783641,HK789089,2004-12-04 10:37:29,029XX S DEARBORN ST,1822,NARCOTICS,MANU/DEL:CANNABIS OVER 10 GMS,CHA APARTMENT,2874,True,False,2113,1,3,35,18,1176498,1885030,2004,ERROR,41.839874474,-87.627820281,"(41.839874474, -87.627820281)" +4549735,HM139445,2006-01-22 23:10:00,019XX S RACINE AVE,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE PORCH/HALLWAY,1515,True,False,1233,12,25,31,26,1168712,1890864,2006,ERROR,41.856055437,-87.656222636,"(41.856055437, -87.656222636)" +2457835,HH783440,2002-11-10 12:30:00,042XX W 25TH PL,0810,THEFT,OVER $500,STREET,4867,False,False,1013,NA,22,30,06,1148465,1886754,2002,2014-04-12 12:43:35,41.845191169,-87.730645627,"(41.845191169, -87.730645627)" +6612069,HP685482,2008-11-15 20:00:00,037XX W LEXINGTON ST,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,4044,False,False,1133,11,24,27,26,1151664,1896487,2008,ERROR,41.871837466,-87.718649859,"(41.871837466, -87.718649859)" +3543604,HK627013,2004-09-16 09:20:00,047XX N WINTHROP AVE,0560,ASSAULT,SIMPLE,SIDEWALK,3752,False,True,2312,19,46,3,08A,1168071,1931384,2004,ERROR,41.967258582,-87.657403334,"(41.967258582, -87.657403334)" +5036221,HM519102,2006-08-04 11:47:26,051XX W MAYPOLE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,OTHER,1726,True,False,1532,15,28,25,18,1141785,1900930,2006,ERROR,41.884218022,-87.754810169,"(41.884218022, -87.754810169)" +8417213,HT650314,2011-12-28 12:50:00,023XX W MADISON ST,2170,NARCOTICS,POSSESSION OF DRUG EQUIPMENT,STREET,2061,True,False,1332,12,2,28,18,1160516,1899992,2011,ERROR,41.881276963,-87.686053431,"(41.881276963, -87.686053431)" +1406729,G971618,2001-02-23 11:00:00,0000X W HUBBARD ST,0820,THEFT,$500 AND UNDER,TAXICAB,182,False,False,1831,NA,NA,NA,06,1176066,1903352,2001,2014-04-12 12:43:35,41.890160967,-87.628853795,"(41.890160967, -87.628853795)" +5919963,HN717978,2007-11-20 10:45:00,011XX N KEYSTONE AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,1751,True,False,1111,11,27,23,26,1149217,1907290,2007,ERROR,41.901529809,-87.727353784,"(41.901529809, -87.727353784)" +2685011,HJ285981,2003-04-07 17:30:00,002XX W 24TH PL,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4426,True,False,2111,NA,25,34,18,1174709,1888096,2003,ERROR,41.848327956,-87.634293516,"(41.848327956, -87.634293516)" +7647393,HS452564,2010-08-08 11:11:00,044XX N BROADWAY,0870,THEFT,POCKET-PICKING,SMALL RETAIL STORE,644,False,False,2311,19,46,3,06,1168390,1929931,2010,2010-12-08 18:54:25,41.96326459,-87.656272632,"(41.96326459, -87.656272632)" +9876690,HX527338,2014-12-02 10:50:00,116XX S WALLACE ST,1365,CRIMINAL TRESPASS,TO RESIDENCE,RESIDENCE,4273,True,False,524,5,34,53,26,1174437,1827665,2014,2014-09-12 12:42:40,41.682504278,-87.637086989,"(41.682504278, -87.637086989)" +8735782,HV411396,2012-08-01 22:30:00,013XX W PRATT BLVD,1812,NARCOTICS,POSS: CANNABIS MORE THAN 30GMS,STREET,4850,True,False,2432,24,49,1,18,1166147,1945262,2012,2012-02-08 02:34:01,42.005381627,-87.664078885,"(42.005381627, -87.664078885)" +1815802,G640754,2001-10-24 17:35:00,072XX S RIDGELAND AV,0460,BATTERY,SIMPLE,SIDEWALK,3648,False,False,324,NA,NA,NA,08B,1189086,1857179,2001,ERROR,41.763156434,-87.582521182,"(41.763156434, -87.582521182)" +9048414,HW192963,2013-03-13 16:10:00,035XX E 114TH ST,0460,BATTERY,SIMPLE,STREET,1436,False,False,433,4,10,52,08B,1201622,1829943,2013,ERROR,41.688109378,-87.537498377,"(41.688109378, -87.537498377)" +4252619,HL568353,2005-08-24 10:51:19,027XX E 83RD ST,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,1402,False,False,423,4,7,46,05,1195865,1850505,2005,ERROR,41.744677425,-87.557895859,"(41.744677425, -87.557895859)" +7015773,HR422984,2009-07-11 07:45:00,060XX N BROADWAY,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,3873,True,False,2433,24,48,77,06,1167253,1940103,2009,ERROR,41.991201436,-87.660159064,"(41.991201436, -87.660159064)" +7085175,HR493489,2009-08-20 03:00:00,042XX S COTTAGE GROVE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,STREET,3060,False,False,213,2,4,38,14,1182265,1877306,2009,ERROR,41.818547345,-87.606897794,"(41.818547345, -87.606897794)" +3872852,HL239738,2005-03-11 19:30:00,007XX W 112TH ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,840,False,False,2233,22,34,49,07,1173427,1830631,2005,ERROR,41.690665788,-87.640696894,"(41.690665788, -87.640696894)" +4784156,HM394717,2006-06-05 08:00:00,058XX N LINCOLN AVE,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1276,False,False,2011,20,40,2,06,1155783,1938688,2006,2006-09-06 04:07:44,41.987558265,-87.702387107,"(41.987558265, -87.702387107)" +6149991,HP239396,2008-03-23 02:31:00,017XX N CLARK ST,0460,BATTERY,SIMPLE,BAR OR TAVERN,2251,False,False,1814,18,43,7,08B,1174666,1912148,2008,2008-08-04 17:08:11,41.914329059,-87.633731746,"(41.914329059, -87.633731746)" +2706200,HJ317376,2003-04-22 23:30:09,058XX W CHICAGO AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1400,True,False,1511,NA,29,25,18,1137385,1904788,2003,ERROR,41.894885131,-87.77087487,"(41.894885131, -87.77087487)" +3243981,HK261903,2004-03-25 07:00:00,041XX W BARRY AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,1844,False,False,2523,25,31,21,26,1148267,1920250,2004,ERROR,41.93711164,-87.730508643,"(41.93711164, -87.730508643)" +4670809,HM272274,2006-04-04 16:30:00,001XX W 110TH PL,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,RESIDENCE PORCH/HALLWAY,2999,False,False,513,5,34,49,04B,1177196,1831716,2006,2006-09-04 03:54:02,41.693559196,-87.626865823,"(41.693559196, -87.626865823)" +3551830,HK636854,2004-09-20 17:00:00,016XX W HOLLYWOOD AVE,0560,ASSAULT,SIMPLE,APARTMENT,2262,False,False,2012,20,40,77,08A,1164030,1937887,2004,ERROR,41.985189571,-87.672076994,"(41.985189571, -87.672076994)" +5555385,HN360342,2007-05-20 06:00:00,071XX S SOUTH SHORE DR,0560,ASSAULT,SIMPLE,STREET,4204,False,True,334,3,7,43,08A,1194449,1858304,2007,2007-11-06 15:52:33,41.766113381,-87.562828234,"(41.766113381, -87.562828234)" +8722256,HV398745,2012-04-24 09:00:00,080XX S DREXEL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1617,False,False,631,6,8,44,26,1183677,1851641,2012,ERROR,41.748087445,-87.602518345,"(41.748087445, -87.602518345)" +2870583,HJ537839,2003-08-03 14:50:00,044XX N HAZEL ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,3347,False,True,2313,19,46,3,08B,1169420,1929662,2003,ERROR,41.96250405,-87.652493562,"(41.96250405, -87.652493562)" +2202542,HH464935,2002-06-24 21:31:52,001XX W 87TH ST,1330,CRIMINAL TRESPASS,TO LAND,PARKING LOT/GARAGE(NON.RESID.),4936,True,False,622,NA,21,44,26,1176895,1847317,2002,ERROR,41.736377238,-87.627499596,"(41.736377238, -87.627499596)" +9078445,HW222090,2013-04-06 15:50:00,003XX E 60TH ST,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,3449,False,False,232,2,20,40,03,1179110,1865332,2013,ERROR,41.785762231,-87.618836444,"(41.785762231, -87.618836444)" +6538835,HP612336,2008-10-06 14:30:00,012XX N MONTICELLO AVE,1563,SEX OFFENSE,CRIMINAL SEXUAL ABUSE,"SCHOOL, PUBLIC, BUILDING",1976,True,False,2535,25,26,23,17,1151767,1908135,2008,2009-03-07 13:40:43,41.90379876,-87.717965025,"(41.90379876, -87.717965025)" +1891896,G739646,2001-12-10 17:30:00,001XX W OAK ST,0460,BATTERY,SIMPLE,APARTMENT,3559,False,True,1824,NA,NA,NA,08B,1174581,1907163,2001,ERROR,41.900651882,-87.63419329,"(41.900651882, -87.63419329)" +6361408,HP448520,2008-07-12 15:30:00,038XX W POLK ST,0560,ASSAULT,SIMPLE,APARTMENT,1593,False,False,1133,11,24,26,08A,1150950,1896132,2008,ERROR,41.870877306,-87.721280557,"(41.870877306, -87.721280557)" +3347368,HK388596,2004-05-26 07:20:00,092XX S ABERDEEN ST,0460,BATTERY,SIMPLE,SIDEWALK,4671,False,False,2222,22,21,73,08B,1170580,1843176,2004,ERROR,41.725153538,-87.650755883,"(41.725153538, -87.650755883)" +9948680,HY137572,2015-02-02 10:40:00,052XX W CONGRESS PKWY,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4151,False,False,1522,15,29,25,05,1141716,1897144,2015,2015-09-02 12:45:35,41.873830036,-87.755157207,"(41.873830036, -87.755157207)" +8157166,HT392395,2011-07-11 22:03:00,012XX N CLYBOURN AVE,0850,THEFT,ATTEMPT THEFT,SIDEWALK,2478,True,False,1821,18,27,8,06,1172815,1908509,2011,2011-12-07 09:18:42,41.904384688,-87.640639932,"(41.904384688, -87.640639932)" +5615641,HN424136,2007-06-23 16:58:43,080XX S ST LOUIS AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,754,False,False,834,8,18,70,05,1154418,1850741,2007,2007-03-08 02:38:51,41.746249624,-87.709757369,"(41.746249624, -87.709757369)" +6678841,HP753255,2008-12-27 17:05:00,052XX N SHERIDAN RD,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,1653,True,False,2023,20,48,77,06,1168731,1935088,2008,ERROR,41.977408151,-87.654868748,"(41.977408151, -87.654868748)" +4652026,HM251430,2006-03-25 00:01:00,097XX S AVENUE L,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,1778,False,False,432,4,10,52,26,1201832,1841147,2006,2006-04-04 03:37:55,41.718848775,-87.53635002,"(41.718848775, -87.53635002)" +7172268,HR582951,2009-10-11 12:15:00,003XX W NORTH AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3748,False,False,1814,18,43,7,26,1173850,1911019,2009,ERROR,41.911249256,-87.636763258,"(41.911249256, -87.636763258)" +6920364,HR325590,2009-05-17 03:50:00,053XX S NORDICA AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,1851,False,False,811,8,23,56,14,1130322,1868174,2009,ERROR,41.794534496,-87.797654615,"(41.794534496, -87.797654615)" +6413617,HP474865,2008-07-25 22:40:00,044XX W MADISON ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,4473,True,False,1113,11,28,26,18,1146975,1899609,2008,2008-07-08 12:18:35,41.880495455,-87.73578545,"(41.880495455, -87.73578545)" +5099538,HM702282,2006-11-05 05:30:00,039XX N SHERIDAN RD,0870,THEFT,POCKET-PICKING,CTA TRAIN,1993,False,False,2324,19,44,6,06,1168850,1926543,2006,2006-08-11 05:36:51,41.953957812,-87.654680033,"(41.953957812, -87.654680033)" +7256815,HR671119,2009-11-22 02:00:00,012XX N CLARK ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,ATM (AUTOMATIC TELLER MACHINE),2265,False,False,1821,18,42,8,11,1175274,1908508,2009,2009-04-12 14:38:56,41.904327102,-87.631607477,"(41.904327102, -87.631607477)" +5593784,HN398599,2007-06-11 15:11:00,005XX E 71ST ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1097,True,False,322,3,6,69,18,1181308,1858110,2007,ERROR,41.765893956,-87.61100002,"(41.765893956, -87.61100002)" +6361346,HP448207,2008-07-11 08:30:00,051XX S HARPER AVE,0560,ASSAULT,SIMPLE,SIDEWALK,4863,False,True,2132,2,4,41,08A,1187200,1871163,2008,ERROR,41.801574625,-87.588989942,"(41.801574625, -87.588989942)" +9900148,HX550849,2014-12-21 21:00:00,033XX S INDIANA AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3486,False,False,211,2,3,35,14,1178111,1882842,2014,ERROR,41.833833929,-87.621967815,"(41.833833929, -87.621967815)" +9176806,HW321737,2013-06-16 07:30:00,111XX S GREEN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENTIAL YARD (FRONT/BACK),1833,False,False,2233,22,34,75,14,1172607,1830711,2013,ERROR,41.690903375,-87.643696617,"(41.690903375, -87.643696617)" +9255503,HW400240,2013-08-09 09:00:00,016XX W WRIGHTWOOD AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),2272,False,False,1931,19,32,7,14,1164679,1917284,2013,ERROR,41.928640244,-87.670276344,"(41.928640244, -87.670276344)" +8879865,HV553462,2012-11-08 14:00:00,075XX S CHAMPLAIN AVE,0880,THEFT,PURSE-SNATCHING,SIDEWALK,937,False,False,624,6,6,69,06,1181824,1855392,2012,2012-09-11 10:05:24,41.758423578,-87.609192588,"(41.758423578, -87.609192588)" +8619862,HV293402,2012-05-19 09:00:00,051XX W HURON ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3108,False,True,1532,15,28,25,08B,1142167,1904099,2012,ERROR,41.892907066,-87.753328745,"(41.892907066, -87.753328745)" +8939790,HV611318,2012-12-20 18:00:00,029XX W WASHINGTON BLVD,0610,BURGLARY,FORCIBLE ENTRY,"SCHOOL, PUBLIC, BUILDING",2123,False,False,1222,12,2,27,05,1156901,1900584,2012,2013-01-01 19:08:51,41.882975519,-87.699311498,"(41.882975519, -87.699311498)" +4471061,HL762489,2005-11-29 18:24:56,025XX S DRAKE AVE,502P,OTHER OFFENSE,FALSE/STOLEN/ALTERED TRP,ALLEY,3995,True,False,1024,10,22,30,26,1153071,1886821,2005,ERROR,41.845285083,-87.71374025,"(41.845285083, -87.71374025)" +6948596,HR354023,2009-05-08 09:00:00,002XX E GARFIELD BLVD,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,RESIDENCE,4044,False,False,232,2,3,40,11,1178909,1868644,2009,ERROR,41.794855256,-87.61947257,"(41.794855256, -87.61947257)" +3024446,HJ731807,2003-11-01 10:19:57,082XX S COMMERCIAL AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,2796,False,True,424,4,10,46,08B,1197665,1850765,2003,ERROR,41.745346184,-87.551291909,"(41.745346184, -87.551291909)" +7096237,HR504828,2009-08-26 16:56:00,019XX W 33RD ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,4912,True,False,922,9,11,59,06,1163768,1882841,2009,ERROR,41.834145069,-87.674595443,"(41.834145069, -87.674595443)" +5309441,HN169039,2007-02-09 18:50:00,061XX W 64TH PL,0560,ASSAULT,SIMPLE,APARTMENT,3080,False,False,812,8,13,64,08A,1136487,1861077,2007,ERROR,41.774951401,-87.775216121,"(41.774951401, -87.775216121)" +3332497,HK367263,2004-05-16 04:42:21,003XX N STATE ST,0460,BATTERY,SIMPLE,STREET,3474,True,False,1831,18,42,8,08B,1176258,1902572,2004,ERROR,41.888016277,-87.628172231,"(41.888016277, -87.628172231)" +5531805,HN344079,2007-05-15 14:45:00,080XX S COTTAGE GROVE AVE,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,SIDEWALK,4786,False,False,631,6,8,44,03,1182985,1852068,2007,2007-03-06 02:49:28,41.749275279,-87.605040789,"(41.749275279, -87.605040789)" +2251499,HH522712,2002-07-19 21:00:00,054XX N LINCOLN AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,811,False,False,2011,NA,40,4,14,1158470,1935921,2002,ERROR,41.979910751,-87.6925803,"(41.979910751, -87.6925803)" +5009126,HM619632,2006-09-24 03:00:00,021XX N RICHMOND ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1319,False,True,1414,14,35,22,08B,1156551,1914178,2006,ERROR,41.920285736,-87.700228327,"(41.920285736, -87.700228327)" +5710364,HN518757,2007-08-01 17:00:00,084XX W BRYN MAWR AVE,0890,THEFT,FROM BUILDING,OTHER,1004,False,False,1614,16,41,76,06,1119008,1936135,2007,ERROR,41.98121629,-87.837704596,"(41.98121629, -87.837704596)" +3722385,HK829243,2004-12-26 19:40:00,104XX S GREEN ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,933,False,True,2233,22,34,73,08B,1172542,1835330,2004,ERROR,41.703580052,-87.643799205,"(41.703580052, -87.643799205)" +3832461,HL203373,2005-02-26 12:35:31,002XX S HOYNE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4153,False,False,1211,12,2,28,14,1162403,1898857,2005,ERROR,41.878123162,-87.679156235,"(41.878123162, -87.679156235)" +8764513,HV439482,2012-08-20 20:08:00,038XX S ELLIS AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE PORCH/HALLWAY,1953,True,True,212,2,4,36,08B,1182599,1879695,2012,ERROR,41.825095178,-87.605598354,"(41.825095178, -87.605598354)" +4299664,HL609145,2005-09-13 08:30:00,014XX E 73RD ST,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,1634,False,False,324,3,5,43,26,1186949,1856916,2005,ERROR,41.762485657,-87.590361895,"(41.762485657, -87.590361895)" +5945461,HN743993,2007-12-05 19:06:00,050XX W HURON ST,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,STREET,551,True,False,1531,15,28,25,26,1142830,1904220,2007,2007-09-12 01:04:39,41.89322678,-87.750890754,"(41.89322678, -87.750890754)" +3283363,HK312676,2004-04-20 00:11:42,046XX N CLIFTON AVE,1220,DECEPTIVE PRACTICE,THEFT OF LOST/MISLAID PROP,STREET,2918,True,False,2311,19,46,3,11,1167636,1931034,2004,ERROR,41.966307576,-87.659012898,"(41.966307576, -87.659012898)" +4021146,HL375918,2005-05-24 12:46:15,107XX S EBERHART AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2789,False,False,513,5,9,49,14,1181446,1833717,2005,ERROR,41.698953504,-87.6112443,"(41.698953504, -87.6112443)" +6928511,HR332778,2009-05-20 17:30:00,007XX W BARRY AVE,0810,THEFT,OVER $500,STREET,4362,False,False,2332,19,44,6,06,1170898,1920733,2009,ERROR,41.937970207,-87.647322338,"(41.937970207, -87.647322338)" +9938186,HY126791,2015-01-24 00:10:00,026XX W EVERGREEN AVE,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,3583,False,False,1423,14,26,24,11,1158479,1908916,2015,ERROR,41.905807118,-87.69328876,"(41.905807118, -87.69328876)" +2981789,HJ644907,2003-09-21 22:04:00,062XX S MORGAN ST,2027,NARCOTICS,POSS: CRACK,STREET,4061,True,False,712,7,16,68,18,1170765,1863047,2003,ERROR,41.779678145,-87.649499695,"(41.779678145, -87.649499695)" +2677516,HJ285368,2003-04-07 13:20:00,001XX W 72ND ST,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1047,False,True,731,NA,6,69,26,1176827,1857294,2003,ERROR,41.763756849,-87.627448846,"(41.763756849, -87.627448846)" +2878862,HJ547518,2003-08-07 12:00:00,048XX W BELLE PLAINE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,2191,False,False,1624,16,45,15,05,1143180,1926855,2003,ERROR,41.955332998,-87.749038868,"(41.955332998, -87.749038868)" +6312467,HP396461,2008-06-13 07:20:00,018XX W LAWRENCE AVE,0460,BATTERY,SIMPLE,PARKING LOT/GARAGE(NON.RESID.),4648,False,False,2032,20,47,4,08B,1163122,1931948,2008,ERROR,41.968911917,-87.675584255,"(41.968911917, -87.675584255)" +2168387,HH420999,2002-06-02 19:00:00,005XX W WILSON DR,0610,BURGLARY,FORCIBLE ENTRY,OTHER,867,False,False,2313,NA,46,3,05,1171655,1930932,2002,ERROR,41.965939938,-87.644238851,"(41.965939938, -87.644238851)" +4428519,HL721751,2005-11-07 17:35:28,003XX S CENTRAL AVE,0460,BATTERY,SIMPLE,STREET,2098,True,False,1522,15,29,25,08B,1139164,1897488,2005,ERROR,41.874820808,-87.764518699,"(41.874820808, -87.764518699)" +6253386,HP340946,2008-05-17 00:30:00,021XX W DIVISION ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,1175,False,False,1424,14,32,24,05,1161971,1908008,2008,2008-02-06 10:11:07,41.903243283,-87.680486791,"(41.903243283, -87.680486791)" +2704055,HJ328889,2003-04-28 10:00:00,115XX S YALE AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,STREET,1601,False,True,522,NA,34,53,26,1176713,1828501,2003,ERROR,41.684747613,-87.628730466,"(41.684747613, -87.628730466)" +1882196,G720932,2001-12-01 13:10:00,020XX S STATE ST,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,800,True,False,2111,NA,NA,NA,18,1176680,1890478,2001,ERROR,41.854820078,-87.626987993,"(41.854820078, -87.626987993)" +7507209,HS310705,2010-05-16 14:00:00,022XX S KOLIN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3702,False,True,1013,10,22,29,08B,1147967,1888437,2010,ERROR,41.849819116,-87.732430029,"(41.849819116, -87.732430029)" +5257400,HM683946,2006-10-26 19:50:02,002XX N KOSTNER AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,3908,True,False,1113,11,28,26,26,1146987,1900681,2006,ERROR,41.883436917,-87.735713976,"(41.883436917, -87.735713976)" +4144821,HL474019,2005-07-10 03:55:00,0000X W 69TH ST,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,CTA GARAGE / OTHER PROPERTY,1874,False,False,731,7,6,69,03,1177312,1859236,2005,ERROR,41.769074977,-87.62561266,"(41.769074977, -87.62561266)" +6107802,HP202858,2008-03-01 03:30:00,001XX E PERSHING RD,0890,THEFT,FROM BUILDING,HOTEL/MOTEL,2798,False,False,211,2,3,35,06,1177861,1879207,2008,2008-02-03 09:27:45,41.823864879,-87.622995394,"(41.823864879, -87.622995394)" +9057825,HW202237,2013-03-22 08:00:00,039XX N LAMON AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2777,False,True,1634,16,45,15,14,1143003,1926071,2013,ERROR,41.953184943,-87.74970919,"(41.953184943, -87.74970919)" +10105129,HY293838,2015-06-09 11:30:00,034XX W 62ND ST,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE-GARAGE,3155,False,False,823,8,15,66,26,1154383,1863255,2015,ERROR,41.780590724,-87.709553096,"(41.780590724, -87.709553096)" +8759952,HV434029,2012-08-16 21:00:00,028XX E 128TH ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,3407,False,False,433,4,10,55,07,1197309,1820733,2012,2012-04-09 11:30:20,41.662944388,-87.553592675,"(41.662944388, -87.553592675)" +7959811,HT190199,2011-03-05 19:15:00,028XX S PULASKI RD,1320,CRIMINAL DAMAGE,TO VEHICLE,VEHICLE NON-COMMERCIAL,1473,False,False,1031,10,22,30,14,1150127,1885011,2011,2011-08-03 08:38:18,41.840375986,-87.724591567,"(41.840375986, -87.724591567)" +3048313,HJ763595,2003-11-16 19:10:00,0000X N LOCKWOOD AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,1950,False,False,1522,15,28,25,03,1141098,1899565,2003,ERROR,41.880484972,-87.757366589,"(41.880484972, -87.757366589)" +5843787,HN653880,2007-10-17 06:15:00,005XX W DIVERSEY PKWY,0820,THEFT,$500 AND UNDER,SIDEWALK,154,False,False,2333,19,44,6,06,1172289,1918919,2007,2014-04-12 12:43:35,41.932961861,-87.642263918,"(41.932961861, -87.642263918)" +2978407,HJ675858,2003-10-06 08:00:00,047XX N ARTESIAN AVE,0810,THEFT,OVER $500,STREET,3029,False,False,1911,19,47,4,06,1159226,1931185,2003,2014-04-12 12:43:35,41.966899389,-87.689930834,"(41.966899389, -87.689930834)" +6332578,HP413557,2008-06-21 14:00:00,060XX S HERMITAGE AVE,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,524,False,True,714,7,15,67,20,1165681,1864379,2008,ERROR,41.783442756,-87.66810053,"(41.783442756, -87.66810053)" +5107772,HM708459,2006-11-08 09:00:00,093XX S VINCENNES AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,DRIVEWAY - RESIDENTIAL,568,False,False,2222,22,21,73,14,1170848,1842818,2006,2006-11-11 07:40:50,41.724165299,-87.649784606,"(41.724165299, -87.649784606)" +7053138,HR458882,2009-07-31 17:50:00,053XX W CHICAGO AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,3429,True,False,1524,15,37,25,18,1140842,1904779,2009,ERROR,41.894797544,-87.758178285,"(41.894797544, -87.758178285)" +3752876,HK761921,2004-11-19 21:59:00,035XX W VAN BUREN ST,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,2319,True,False,1133,11,28,27,26,1152952,1897760,2004,ERROR,41.875305308,-87.713887327,"(41.875305308, -87.713887327)" +8246665,HT470888,2011-08-28 17:00:00,043XX N CALIFORNIA AVE,0610,BURGLARY,FORCIBLE ENTRY,CAR WASH,1992,False,False,1724,17,33,16,05,1156889,1928818,2011,ERROR,41.960452042,-87.698588153,"(41.960452042, -87.698588153)" +8588225,HV262806,2012-04-28 22:55:00,013XX W 15TH ST,1350,CRIMINAL TRESPASS,TO STATE SUP LAND,CHA PARKING LOT/GROUNDS,2791,True,False,1231,12,2,28,26,1167750,1892902,2012,ERROR,41.861668647,-87.659694953,"(41.861668647, -87.659694953)" +4546442,HM132623,2006-01-18 12:40:00,021XX N MELVINA AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2714,False,False,2512,25,29,19,14,1134778,1913205,2006,ERROR,41.91802893,-87.780250515,"(41.91802893, -87.780250515)" +4972684,HM583449,2006-09-04 17:00:00,050XX S ELIZABETH ST,0460,BATTERY,SIMPLE,STREET,1534,False,False,933,9,16,61,08B,1168804,1871336,2006,2006-03-10 05:10:58,41.802466697,-87.656449709,"(41.802466697, -87.656449709)" +7723442,HS530831,2010-09-23 23:32:00,003XX W SCOTT ST,031A,ROBBERY,ARMED: HANDGUN,STREET,4018,True,False,1821,18,27,8,03,1173890,1908750,2010,2011-10-05 09:12:33,41.905022117,-87.636684012,"(41.905022117, -87.636684012)" +2773492,HJ393599,2003-05-29 03:10:00,008XX N SPRINGFIELD AVE,2027,NARCOTICS,POSS: CRACK,STREET,844,True,False,1112,NA,27,23,18,1150284,1905083,2003,ERROR,41.895452828,-87.72349217,"(41.895452828, -87.72349217)" +3692637,HK794797,2004-12-07 11:00:31,018XX W 34TH ST,0920,MOTOR VEHICLE THEFT,ATT: AUTOMOBILE,STREET,3810,False,False,922,9,11,59,07,1164352,1882114,2004,ERROR,41.832137795,-87.672473125,"(41.832137795, -87.672473125)" +4219807,HL460633,2005-07-03 19:58:47,010XX N LECLAIRE AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,2203,True,False,1531,15,37,25,18,1142213,1906317,2005,ERROR,41.898992665,-87.753104713,"(41.898992665, -87.753104713)" +2573876,HJ163682,2003-02-03 19:00:00,038XX N PLAINFIELD AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2418,False,False,1631,NA,36,17,14,1119989,1924864,2003,ERROR,41.950271676,-87.834338101,"(41.950271676, -87.834338101)" +4071798,HL321033,2005-04-27 11:45:00,008XX N HUDSON AVE,2027,NARCOTICS,POSS: CRACK,STREET,4486,True,False,1823,18,27,8,18,1172997,1905793,2005,ERROR,41.896927801,-87.640052025,"(41.896927801, -87.640052025)" +4818852,HM291602,2006-04-14 23:58:42,026XX W 23RD PL,2230,LIQUOR LAW VIOLATION,ILLEGAL CONSUMPTION BY MINOR,SIDEWALK,2628,False,False,1034,10,28,30,22,1158965,1888336,2006,ERROR,41.849323686,-87.69206834,"(41.849323686, -87.69206834)" +7027138,HR426417,2009-07-13 04:45:00,060XX S WABASH AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,3907,False,False,311,3,20,40,05,1177713,1864904,2009,ERROR,41.784619498,-87.623971417,"(41.784619498, -87.623971417)" +9581848,HX232365,2014-04-21 23:03:00,015XX W SUPERIOR ST,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,4430,False,False,1215,12,27,24,14,1165974,1905059,2014,ERROR,41.895066484,-87.66586726,"(41.895066484, -87.66586726)" +9174144,HW318226,2013-06-11 00:01:00,014XX W ROSEMONT AVE,0841,THEFT,FINANCIAL ID THEFT:$300 &UNDER,APARTMENT,4653,False,False,2433,24,40,77,06,1165397,1941952,2013,ERROR,41.996314969,-87.666932963,"(41.996314969, -87.666932963)" +5992602,HP100090,2008-01-01 00:30:00,063XX S DR MARTIN LUTHER KING JR DR,051A,ASSAULT,AGGRAVATED: HANDGUN,SIDEWALK,3889,False,False,312,3,20,69,04A,1179974,1862865,2008,2008-06-01 06:49:00,41.778972797,-87.615744123,"(41.778972797, -87.615744123)" +2254466,HH524868,2002-07-21 05:00:00,006XX N ST CLAIR ST,0460,BATTERY,SIMPLE,STREET,3497,False,False,1834,NA,42,8,08B,1177765,1904573,2002,ERROR,41.893473003,-87.622577223,"(41.893473003, -87.622577223)" +7861681,HS675991,2010-12-20 07:00:00,016XX W WRIGHTWOOD AVE,0890,THEFT,FROM BUILDING,RESIDENCE-GARAGE,741,False,False,1931,19,32,7,06,1164728,1917284,2010,ERROR,41.928639204,-87.670096285,"(41.928639204, -87.670096285)" +4305016,HL615231,2005-09-15 19:00:00,014XX W ESTES AVE,0820,THEFT,$500 AND UNDER,STREET,331,False,False,2423,24,49,1,06,1165114,1947454,2005,2014-04-12 12:43:35,42.011418612,-87.667816666,"(42.011418612, -87.667816666)" +6978791,HR377687,2009-06-16 05:00:00,064XX S WINCHESTER AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,4805,True,False,726,7,15,67,26,1164424,1861895,2009,ERROR,41.776652928,-87.672779095,"(41.776652928, -87.672779095)" +7951980,HT184140,2011-03-01 16:00:00,069XX S MAPLEWOOD AVE,0560,ASSAULT,SIMPLE,APARTMENT,4213,False,True,832,8,15,66,08A,1160614,1858473,2011,2011-03-03 11:09:39,41.767341932,-87.686840757,"(41.767341932, -87.686840757)" +9319944,HW463891,2013-09-23 15:24:00,007XX N MENARD AVE,0650,BURGLARY,HOME INVASION,APARTMENT,3017,False,False,1511,15,29,25,05,1137630,1904607,2013,2013-09-10 17:10:49,41.894384033,-87.769979402,"(41.894384033, -87.769979402)" +6757834,HR174805,2009-02-17 16:34:15,070XX S WENTWORTH AVE,1330,CRIMINAL TRESPASS,TO LAND,CURRENCY EXCHANGE,3968,True,False,731,7,6,69,26,1176225,1857985,2009,ERROR,41.765666567,-87.629634577,"(41.765666567, -87.629634577)" +4825337,HM438385,2006-04-20 09:00:00,023XX S LAKE SHORE DR E,0890,THEFT,FROM BUILDING,OTHER,2059,False,False,133,1,2,33,06,1180538,1889157,2006,ERROR,41.851107212,-87.612868333,"(41.851107212, -87.612868333)" +8933517,HV605679,2012-12-17 02:14:00,012XX S SPRINGFIELD AVE,0820,THEFT,$500 AND UNDER,STREET,31,False,False,1011,10,24,29,06,1150625,1893795,2012,ERROR,41.864470661,-87.722534804,"(41.864470661, -87.722534804)" +3080102,HJ805073,2003-12-04 19:00:00,043XX N KEDVALE AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,2862,False,True,1722,17,39,16,26,1148110,1928515,2003,ERROR,41.959794498,-87.730872135,"(41.959794498, -87.730872135)" +7734425,HS535056,2010-09-25 17:30:00,088XX S LOWE AVE,0810,THEFT,OVER $500,STREET,3573,False,False,2223,22,21,71,06,1173480,1846204,2010,2010-02-10 09:42:41,41.733399192,-87.640043801,"(41.733399192, -87.640043801)" +9596026,HX246438,2014-05-02 23:30:00,005XX N KEDZIE AVE,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,STREET,4495,False,False,1121,11,27,23,03,1154906,1903472,2014,2014-07-05 00:40:24,41.890940685,-87.706559801,"(41.890940685, -87.706559801)" +9870090,HX520069,2014-11-25 11:40:00,053XX N WESTERN AVE,0460,BATTERY,SIMPLE,NURSING HOME/RETIREMENT HOME,2515,False,False,2011,20,40,4,08B,1159335,1935518,2014,2014-02-12 12:51:55,41.978787101,-87.689410305,"(41.978787101, -87.689410305)" +8508277,HV185019,2012-03-06 08:40:00,072XX S COLES AVE,1360,CRIMINAL TRESPASS,TO VEHICLE,STREET,3879,True,False,334,3,7,43,26,1194431,1857617,2012,2012-06-03 15:34:55,41.764228646,-87.562916763,"(41.764228646, -87.562916763)" +2177946,HH431848,2002-06-03 16:00:00,041XX W 31ST ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,868,False,False,1031,NA,22,30,06,1149298,1883758,2002,ERROR,41.836953671,-87.727666121,"(41.836953671, -87.727666121)" +1986260,HH132885,2002-01-17 20:40:00,030XX W MADISON ST,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,1187,True,False,1124,NA,NA,NA,16,1156378,1899829,2002,ERROR,41.880914312,-87.701252404,"(41.880914312, -87.701252404)" +9234994,HW381728,2013-07-26 09:30:00,0000X W WACKER DR,0820,THEFT,$500 AND UNDER,SIDEWALK,318,False,False,111,1,42,32,06,1175775,1902091,2013,ERROR,41.886707265,-87.629960428,"(41.886707265, -87.629960428)" +7988307,HT219886,2011-03-25 20:58:00,009XX N MOZART ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,797,True,True,1311,12,26,24,08B,1157172,1906205,2011,ERROR,41.898394561,-87.69816358,"(41.898394561, -87.69816358)" +7278970,HR694241,2009-12-17 11:00:00,041XX N GREENVIEW AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,4882,False,False,1923,19,47,6,14,1165286,1927513,2009,ERROR,41.956696233,-87.66775397,"(41.956696233, -87.66775397)" +8025601,HT256918,2011-04-19 13:00:00,076XX S KINGSTON AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,APARTMENT,2051,True,False,421,4,7,43,15,1194477,1854868,2011,ERROR,41.756684054,-87.562838411,"(41.756684054, -87.562838411)" +2962454,HJ650469,2003-09-24 11:45:00,0000X W 79TH ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,OTHER,2463,True,False,623,6,6,44,11,1177482,1852588,2003,ERROR,41.750828271,-87.625190123,"(41.750828271, -87.625190123)" +3532647,HK574001,2004-08-22 14:49:40,056XX S WINCHESTER AVE,2014,NARCOTICS,MANU/DELIVER: HEROIN (WHITE),SIDEWALK,1797,True,False,715,7,15,67,18,1164271,1867524,2004,ERROR,41.792102855,-87.673181553,"(41.792102855, -87.673181553)" +3038829,HJ750788,2003-11-10 15:25:00,020XX S KOSTNER AVE,0560,ASSAULT,SIMPLE,OTHER,4836,True,False,1012,10,24,29,08A,1147406,1890054,2003,ERROR,41.854267127,-87.734447612,"(41.854267127, -87.734447612)" +9806012,HX453692,2014-10-03 13:30:00,023XX N PULASKI RD,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2254,False,False,2525,25,31,20,06,1149281,1915512,2014,2014-10-10 12:36:29,41.924090526,-87.72690517,"(41.924090526, -87.72690517)" +3511264,HK589583,2004-08-29 14:30:00,022XX N CANNON DR,0820,THEFT,$500 AND UNDER,STREET,219,False,False,1814,18,43,7,06,1175058,1914939,2004,2014-04-12 12:43:35,41.921978896,-87.632207786,"(41.921978896, -87.632207786)" +5053020,HM659802,2006-10-14 14:45:00,035XX S WESTERN BLVD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2470,False,False,913,9,11,59,08B,1161140,1881089,2006,ERROR,41.829392282,-87.684286808,"(41.829392282, -87.684286808)" +8357212,HT590698,2011-11-15 15:45:00,040XX W WEST END AVE,0470,PUBLIC PEACE VIOLATION,RECKLESS CONDUCT,STREET,2682,True,False,1114,11,28,26,24,1149062,1900618,2011,ERROR,41.883224133,-87.728095989,"(41.883224133, -87.728095989)" +2234104,HH504870,2002-07-12 13:00:00,016XX S SPRINGFIELD AVE,0420,BATTERY,AGGRAVATED:KNIFE/CUTTING INSTR,SIDEWALK,557,False,False,1014,NA,24,29,04B,1150624,1891627,2002,ERROR,41.858521432,-87.722595091,"(41.858521432, -87.722595091)" +5023389,HM632245,2006-09-29 19:00:00,012XX N LA SALLE DR,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,549,False,False,1821,18,43,8,14,1174869,1908585,2006,2006-03-10 05:10:58,41.904547475,-87.633092826,"(41.904547475, -87.633092826)" +4167710,HL497000,2005-07-18 14:07:00,039XX N RAVENSWOOD AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1045,False,False,1923,19,47,6,26,1163702,1926130,2005,ERROR,41.952934826,-87.673616315,"(41.952934826, -87.673616315)" +2170727,HH421736,2002-06-05 21:00:00,100XX W OHARE ST,0460,BATTERY,SIMPLE,AIRPORT/AIRCRAFT,994,False,False,1651,NA,41,76,08B,1100635,1934208,2002,ERROR,41.976200173,-87.905312411,"(41.976200173, -87.905312411)" +2294558,HH580615,2002-08-14 08:30:00,025XX N BOSWORTH AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3119,False,False,1931,NA,32,7,07,1165613,1916698,2002,ERROR,41.927012356,-87.666860956,"(41.927012356, -87.666860956)" +7991942,HT224010,2011-03-28 20:45:00,063XX S ARTESIAN AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1474,True,False,825,8,15,66,18,1161174,1862364,2011,ERROR,41.778007816,-87.684680539,"(41.778007816, -87.684680539)" +3096983,HJ825511,2003-12-18 17:20:00,075XX N CLARK ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,706,True,False,2422,24,49,1,06,1162974,1949868,2003,ERROR,42.018088059,-87.675622371,"(42.018088059, -87.675622371)" +2315170,HH603150,2002-08-25 01:40:00,093XX S PARNELL AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,3586,False,False,2223,NA,21,73,14,1174239,1842710,2002,ERROR,41.723794372,-87.637366715,"(41.723794372, -87.637366715)" +4440071,HL737554,2005-11-15 19:05:00,032XX W 63RD ST,031A,ROBBERY,ARMED: HANDGUN,GROCERY FOOD STORE,4578,False,False,823,8,15,66,03,1155598,1862622,2005,ERROR,41.778829406,-87.705115599,"(41.778829406, -87.705115599)" +7177547,HR586223,2009-10-09 10:00:00,081XX S PAULINA ST,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,2086,False,False,614,6,18,71,05,1166467,1850703,2009,2009-10-11 13:28:08,41.745897252,-87.665608013,"(41.745897252, -87.665608013)" +3254737,HK280639,2004-03-01 00:00:00,067XX S MERRILL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4572,False,False,331,3,5,43,26,1191738,1860941,2004,ERROR,41.773415721,-87.57267934,"(41.773415721, -87.57267934)" +3311197,HK345294,2004-05-05 21:35:00,008XX N CALIFORNIA AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,1400,True,True,1311,12,26,24,08B,1157536,1905231,2004,ERROR,41.895714416,-87.69685317,"(41.895714416, -87.69685317)" +2302106,HH588911,2002-08-17 21:00:00,041XX S CAMPBELL AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,1887,False,False,914,NA,12,58,05,1160346,1876870,2002,ERROR,41.817831272,-87.687316372,"(41.817831272, -87.687316372)" +6488303,HP564304,2008-09-10 18:17:00,001XX W BRAYTON ST,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,4667,False,False,523,5,9,53,08A,1177795,1821422,2008,ERROR,41.665297395,-87.62498261,"(41.665297395, -87.62498261)" +5443072,HN275682,2006-02-01 08:00:00,006XX E 89TH ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,2116,False,False,632,6,6,44,06,1182025,1846146,2006,ERROR,41.733046889,-87.608741291,"(41.733046889, -87.608741291)" +9210579,HW356745,2013-07-05 22:00:00,100XX S PROSPECT AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,594,False,False,2213,22,19,72,14,1167485,1837705,2013,ERROR,41.710207052,-87.662249162,"(41.710207052, -87.662249162)" +9287060,HW431457,2013-08-30 19:00:00,081XX S EUCLID AVE,0890,THEFT,FROM BUILDING,APARTMENT,1161,False,False,414,4,8,46,06,1190659,1851058,2013,2013-01-09 07:31:32,41.746322062,-87.576953181,"(41.746322062, -87.576953181)" +5794196,HN603823,2007-09-21 23:33:00,078XX S EAST END AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1314,True,False,414,4,8,43,18,1188864,1853073,2007,ERROR,41.751894519,-87.583466017,"(41.751894519, -87.583466017)" +4344629,HL643695,2005-09-29 21:00:00,063XX S PULASKI RD,051A,ASSAULT,AGGRAVATED: HANDGUN,STREET,2707,False,False,813,8,13,65,04A,1150755,1862153,2005,ERROR,41.777638112,-87.722882838,"(41.777638112, -87.722882838)" +6191990,HP281294,2008-04-15 15:00:00,006XX W WAVELAND AVE,0810,THEFT,OVER $500,STREET,932,False,False,2323,19,46,6,06,1171539,1925299,2008,ERROR,41.950485378,-87.644831764,"(41.950485378, -87.644831764)" +8304880,HT539173,2011-10-12 03:00:00,056XX W WASHINGTON BLVD,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,3476,False,True,1512,15,29,25,14,1138540,1900218,2011,ERROR,41.882323613,-87.766743629,"(41.882323613, -87.766743629)" +1587243,G356113,2001-06-18 20:30:00,045XX N CLARK ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4800,False,False,1922,NA,NA,NA,07,1165538,1930394,2001,ERROR,41.964596441,-87.66674516,"(41.964596441, -87.66674516)" +9232964,HW379118,2013-07-25 19:00:00,060XX N FRANCISCO AVE,0460,BATTERY,SIMPLE,APARTMENT,4723,False,False,2413,24,50,2,08B,1155879,1940034,2013,ERROR,41.991249811,-87.701997517,"(41.991249811, -87.701997517)" +5382099,HN230597,2007-03-15 20:30:00,016XX N PAULINA ST,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,562,False,False,1433,14,1,24,05,1164792,1911272,2007,2007-02-08 01:58:25,41.912140543,-87.670031984,"(41.912140543, -87.670031984)" +8703573,HV379876,2012-07-12 19:30:00,040XX W LAKE ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA GARAGE / OTHER PROPERTY,4140,True,False,1114,11,28,26,11,1149648,1901481,2012,ERROR,41.885580938,-87.725921713,"(41.885580938, -87.725921713)" +6026181,HP129984,2008-01-17 13:00:00,029XX S LOOMIS ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1082,True,False,923,9,11,60,07,1168373,1885564,2008,ERROR,41.841519091,-87.657620002,"(41.841519091, -87.657620002)" +3088718,HJ812750,2003-12-11 23:20:00,019XX N KARLOV AVE,0650,BURGLARY,HOME INVASION,RESIDENCE,2638,False,False,2534,25,30,20,05,1148701,1912536,2003,ERROR,41.915935339,-87.729113388,"(41.915935339, -87.729113388)" +3998545,HL355767,2005-05-14 10:20:57,023XX E 75TH ST,0454,BATTERY,AGG PO HANDS NO/MIN INJURY,GAS STATION,1092,True,False,334,3,7,43,08B,1193470,1855738,2005,ERROR,41.759096092,-87.566500388,"(41.759096092, -87.566500388)" +7613298,HS418212,2010-07-19 00:30:00,056XX S NEENAH AVE,0820,THEFT,$500 AND UNDER,STREET,101,False,False,811,8,23,56,06,1133641,1866435,2010,ERROR,41.789704965,-87.785524264,"(41.789704965, -87.785524264)" +6980905,HR385311,2009-06-19 20:00:00,024XX N CICERO AVE,0610,BURGLARY,FORCIBLE ENTRY,OTHER,902,False,False,2521,25,31,19,05,1143947,1915964,2009,ERROR,41.9254327,-87.746493281,"(41.9254327, -87.746493281)" +4628112,HM225198,2006-03-10 20:00:00,089XX S RIDGELAND AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1600,False,False,413,4,8,48,14,1189473,1845824,2006,ERROR,41.731987986,-87.581466439,"(41.731987986, -87.581466439)" +9426414,HW569938,2013-12-12 11:45:00,012XX N ASHLAND AVE,031A,ROBBERY,ARMED: HANDGUN,CTA BUS STOP,2851,False,False,1424,14,1,24,03,1165544,1908172,2013,ERROR,41.903617945,-87.667357779,"(41.903617945, -87.667357779)" +5040428,HM649718,2006-10-09 02:00:00,005XX S CAMPBELL AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4722,False,False,1135,11,2,28,07,1159775,1897648,2006,ERROR,41.874860113,-87.68883898,"(41.874860113, -87.68883898)" +9176835,HW321707,2013-06-16 19:30:00,055XX W GRAND AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,POLICE FACILITY/VEH PARKING LOT,3141,True,False,2515,25,29,19,18,1138769,1913443,2013,ERROR,41.918610464,-87.765581339,"(41.918610464, -87.765581339)" +9817998,HX467737,2014-10-14 15:30:00,020XX N MILWAUKEE AVE,0820,THEFT,$500 AND UNDER,OTHER,287,False,False,1431,14,1,22,06,1159346,1913460,2014,ERROR,41.918258409,-87.689978754,"(41.918258409, -87.689978754)" +1398760,G114124,2001-02-22 23:55:00,082XX S JEFFERY BL,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),273,False,False,414,NA,NA,NA,06,1190990,1850969,2001,2014-04-12 12:43:35,41.746069847,-87.575743214,"(41.746069847, -87.575743214)" +4410684,HL706234,2005-10-29 09:00:00,044XX N RAVENSWOOD AVE,0810,THEFT,OVER $500,STREET,3661,False,False,1922,19,47,3,06,1163617,1929327,2005,2014-04-12 12:43:35,41.961709338,-87.673838335,"(41.961709338, -87.673838335)" +3516152,HK591240,2004-08-29 15:30:00,048XX N PAULINA ST,0820,THEFT,$500 AND UNDER,STREET,171,False,False,2032,20,47,3,06,1164401,1932012,2004,2014-04-12 12:43:35,41.969060495,-87.670879593,"(41.969060495, -87.670879593)" +4894707,HM510203,2006-07-30 11:00:00,047XX N SAWYER AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,RESIDENTIAL YARD (FRONT/BACK),4021,False,False,1713,17,33,14,14,1153814,1931419,2006,2006-02-08 04:53:53,41.967651297,-87.709823827,"(41.967651297, -87.709823827)" +9622145,HX271986,2014-05-22 13:00:00,017XX N HALSTED ST,0820,THEFT,$500 AND UNDER,STREET,139,False,False,1813,18,43,7,06,1170734,1911649,2014,ERROR,41.91304688,-87.648191824,"(41.91304688, -87.648191824)" +4015702,HL308318,2005-04-21 01:56:40,042XX S PRAIRIE AVE,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,2725,True,False,214,2,3,38,16,1178699,1877152,2005,ERROR,41.81820674,-87.619983684,"(41.81820674, -87.619983684)" +3849018,HL221418,2005-03-07 20:10:00,113XX S EDBROOKE AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,1114,True,False,531,5,9,49,04B,1179176,1829999,2005,ERROR,41.68880271,-87.619668717,"(41.68880271, -87.619668717)" +6545175,HP615080,2008-10-08 08:55:39,073XX S PHILLIPS AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1777,False,True,334,3,7,43,08B,1193960,1856732,2008,2008-10-10 05:55:47,41.761811703,-87.564672045,"(41.761811703, -87.564672045)" +2948741,HJ636339,2003-09-17 19:10:00,011XX N MAYFIELD AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2818,False,True,1511,15,29,25,26,1136769,1907269,2003,ERROR,41.901704366,-87.773077802,"(41.901704366, -87.773077802)" +5845070,HN655038,2007-10-15 06:15:00,056XX W LAKE ST,0810,THEFT,OVER $500,STREET,1243,False,False,1512,15,29,25,06,1138390,1902170,2007,2014-04-12 12:43:35,41.88768287,-87.767247156,"(41.88768287, -87.767247156)" +5008839,HM618424,2006-09-23 13:20:00,017XX N HUMBOLDT BLVD,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,4129,False,False,1421,14,35,23,03,1156056,1911677,2006,2006-01-10 07:25:21,41.913432802,-87.702114688,"(41.913432802, -87.702114688)" +8587110,HV261318,2012-04-27 21:40:00,084XX S MANISTEE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4048,False,False,423,4,7,46,08B,1195995,1849774,2012,ERROR,41.742668288,-87.557443682,"(41.742668288, -87.557443682)" +7872477,HT102978,2011-01-03 10:30:00,034XX E 133RD ST,0810,THEFT,OVER $500,STREET,3405,False,False,433,4,10,55,06,1201126,1817424,2011,2011-04-01 07:20:15,41.653768523,-87.539736212,"(41.653768523, -87.539736212)" +9916472,HY105958,2014-11-26 12:00:00,030XX E 92ND ST,1110,DECEPTIVE PRACTICE,BOGUS CHECK,BANK,4969,False,False,424,4,10,46,11,1198084,1844583,2014,2015-08-01 12:39:18,41.728371838,-87.549962839,"(41.728371838, -87.549962839)" +9490762,HX144691,2014-02-07 22:00:00,055XX S ALBANY AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3213,False,False,824,8,14,63,14,1156621,1867583,2014,2014-12-02 00:40:02,41.792422534,-87.701231458,"(41.792422534, -87.701231458)" +5857483,HN656362,2007-10-18 18:45:00,005XX E 107TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,2928,False,True,512,5,9,49,08B,1181507,1834155,2007,ERROR,41.700154031,-87.611007498,"(41.700154031, -87.611007498)" +2535237,HJ109917,2002-12-20 15:00:00,074XX S COLFAX AVE,1562,SEX OFFENSE,AGG CRIMINAL SEXUAL ABUSE,APARTMENT,4858,False,False,334,NA,7,43,17,1194702,1856124,2002,ERROR,41.76012508,-87.561972571,"(41.76012508, -87.561972571)" +8804120,HV477870,2012-09-16 03:10:00,047XX S LOOMIS BLVD,0460,BATTERY,SIMPLE,RESIDENTIAL YARD (FRONT/BACK),3213,False,False,933,9,3,61,08B,1167835,1873371,2012,ERROR,41.808071849,-87.659944973,"(41.808071849, -87.659944973)" +3273588,HK301044,2004-04-14 11:45:00,012XX S KOSTNER AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,1578,False,False,1011,10,24,29,03,1147285,1893972,2004,ERROR,41.865020926,-87.734791482,"(41.865020926, -87.734791482)" +7072295,HR477075,2009-08-08 19:00:00,001XX E 47TH ST,0870,THEFT,POCKET-PICKING,BAR OR TAVERN,3597,False,False,224,2,3,38,06,1178126,1873860,2009,2009-12-08 13:56:14,41.809186244,-87.622185553,"(41.809186244, -87.622185553)" +9162055,HW306837,2013-04-09 11:00:00,068XX S CORNELL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3084,False,True,332,3,5,43,26,1188350,1859644,2013,2013-09-06 03:34:44,41.769938203,-87.585140129,"(41.769938203, -87.585140129)" +2688193,HJ309724,2003-04-18 19:30:00,022XX N STOCKTON DR,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2743,False,False,1814,NA,43,7,14,1174127,1915368,2003,ERROR,41.923176926,-87.635615683,"(41.923176926, -87.635615683)" +7691340,HS497006,2010-09-03 13:07:00,009XX E MARQUETTE RD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,ALLEY,1215,False,True,321,3,5,42,08B,1183558,1861405,2010,2010-08-09 07:54:48,41.774883602,-87.602650487,"(41.774883602, -87.602650487)" +3282771,HK213978,2004-03-01 16:53:11,007XX W 61ST ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1555,True,False,711,7,16,68,18,1172314,1864376,2004,ERROR,41.783291129,-87.643781806,"(41.783291129, -87.643781806)" +4751054,HM261185,2006-03-30 12:02:00,011XX N RIDGEWAY AVE,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,STREET,2426,True,False,1112,11,27,23,18,1151136,1907139,2006,ERROR,41.901078036,-87.720308999,"(41.901078036, -87.720308999)" +5962440,HN757326,2007-12-12 22:20:00,055XX W ADAMS ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1914,False,False,1522,15,29,25,07,1139406,1898740,2007,2008-06-01 01:05:00,41.878252056,-87.76359965,"(41.878252056, -87.76359965)" +1834047,G670254,2001-11-06 18:00:00,013XX W ERIE ST,0560,ASSAULT,SIMPLE,STREET,3142,False,False,1324,NA,NA,NA,08A,1167244,1904514,2001,ERROR,41.89354376,-87.661218582,"(41.89354376, -87.661218582)" +3301460,HK334439,2004-04-30 13:30:00,022XX N AUSTIN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4380,False,True,2515,25,37,19,08B,1136060,1914635,2004,ERROR,41.921930233,-87.77550612,"(41.921930233, -87.77550612)" +2669621,HJ286771,2003-04-06 01:00:00,030XX W 55TH ST,0460,BATTERY,SIMPLE,STREET,2322,False,False,824,NA,14,63,08B,1157258,1867977,2003,ERROR,41.793490848,-87.69888499,"(41.793490848, -87.69888499)" +6369079,HP455430,2008-07-16 06:10:00,076XX S PHILLIPS AVE,0560,ASSAULT,SIMPLE,STREET,4088,False,False,421,4,7,43,08A,1193825,1854450,2008,ERROR,41.755553029,-87.565241501,"(41.755553029, -87.565241501)" +6855131,HR261020,2009-04-10 00:00:00,014XX W ERIE ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3068,False,False,1324,12,27,24,14,1166896,1904503,2009,ERROR,41.893521048,-87.662496975,"(41.893521048, -87.662496975)" +4274768,HL591848,2005-09-04 20:00:00,033XX W WRIGHTWOOD AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2202,False,False,1412,14,35,22,07,1153390,1917123,2005,ERROR,41.928430509,-87.711764046,"(41.928430509, -87.711764046)" +6197251,HP285608,2008-04-17 18:30:00,030XX W FILLMORE ST,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),3447,True,False,1134,11,28,29,14,1155966,1895253,2008,ERROR,41.868365618,-87.702888661,"(41.868365618, -87.702888661)" +8433392,HV111791,2012-01-10 01:40:00,067XX S ST LAWRENCE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3287,False,True,321,3,20,42,08B,1181358,1860502,2012,ERROR,41.772456691,-87.610743096,"(41.772456691, -87.610743096)" +7505865,HS308821,2010-05-04 08:00:00,080XX S SANGAMON ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,3502,False,True,621,6,21,71,26,1171334,1851481,2010,ERROR,41.747927164,-87.647751629,"(41.747927164, -87.647751629)" +7557528,HS361664,2010-06-15 19:11:00,016XX S KARLOV AVE,2027,NARCOTICS,POSS: CRACK,RESIDENCE,863,True,False,1012,10,24,29,18,1149364,1891408,2010,ERROR,41.85794497,-87.727225818,"(41.85794497, -87.727225818)" +6702067,HR108720,2009-01-06 14:45:00,004XX S KEELER AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, GROUNDS",4011,False,False,1132,11,24,26,08B,1148424,1897525,2009,ERROR,41.874748911,-87.730518558,"(41.874748911, -87.730518558)" +6400565,HP436822,2008-07-06 21:08:22,064XX S CALIFORNIA AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,1031,True,False,825,8,15,66,26,1158854,1861997,2008,2008-03-08 06:46:11,41.777048422,-87.693195807,"(41.777048422, -87.693195807)" +3736578,HL102041,2005-01-02 00:30:00,065XX S LOOMIS BLVD,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,1274,False,False,725,7,17,67,06,1168076,1861511,2005,2014-04-12 12:43:35,41.775521427,-87.659402042,"(41.775521427, -87.659402042)" +9845718,HX495009,2014-11-04 15:30:00,013XX W 95TH ST,0810,THEFT,OVER $500,"SCHOOL, PUBLIC, BUILDING",2826,False,False,2213,22,21,73,06,1169214,1841722,2014,2014-11-11 12:41:33,41.72119316,-87.655801492,"(41.72119316, -87.655801492)" +5253960,HN130561,2007-01-16 00:00:00,024XX N LINCOLN AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,2443,False,False,1933,19,43,7,26,1170249,1916386,2007,ERROR,41.926056068,-87.649834888,"(41.926056068, -87.649834888)" +3927220,HL299821,2005-04-16 22:55:00,009XX W 32ND PL,0460,BATTERY,SIMPLE,APARTMENT,951,False,False,924,9,11,60,08B,1170602,1883473,2005,ERROR,41.835732786,-87.64950144,"(41.835732786, -87.64950144)" +5904951,HN703395,2007-11-11 10:00:00,053XX S PULASKI RD,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,GROCERY FOOD STORE,2864,False,False,815,8,23,62,11,1150568,1868904,2007,ERROR,41.796167541,-87.723392818,"(41.796167541, -87.723392818)" +7616487,HS420429,2010-07-20 13:40:00,071XX S JEFFERY BLVD,0860,THEFT,RETAIL THEFT,SMALL RETAIL STORE,679,True,False,333,3,5,43,06,1190817,1858158,2010,ERROR,41.765801254,-87.576145268,"(41.765801254, -87.576145268)" +2377718,HH685782,2002-10-01 16:00:00,008XX S WESTERN AVE,0890,THEFT,FROM BUILDING,COMMERCIAL / BUSINESS OFFICE,3836,False,False,1224,NA,25,28,06,1160551,1896364,2002,ERROR,41.871320673,-87.686025384,"(41.871320673, -87.686025384)" +3401548,HK459731,2004-06-11 02:00:00,025XX N FRANCISCO AVE,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,BANK,3180,False,False,1414,14,35,22,11,1156577,1916550,2004,ERROR,41.92679416,-87.700068437,"(41.92679416, -87.700068437)" +4828468,HM412792,2006-06-14 02:11:58,0000X E GARFIELD BLVD,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,3102,False,False,233,2,20,40,03,1177699,1868405,2006,2006-07-10 09:43:17,41.794226904,-87.62391683,"(41.794226904, -87.62391683)" +2097067,HH314898,2002-04-18 08:40:00,024XX W LAWRENCE AV,0560,ASSAULT,SIMPLE,OTHER,4703,True,False,2031,NA,NA,NA,08A,1159019,1931842,2002,ERROR,41.968706493,-87.690673825,"(41.968706493, -87.690673825)" +3603839,HK646357,2004-09-25 03:10:00,008XX N ASHLAND AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2836,False,False,1322,12,1,24,14,1165526,1906012,2004,ERROR,41.897691137,-87.667485477,"(41.897691137, -87.667485477)" +9512342,HX167043,2014-02-27 23:16:00,064XX S CARPENTER ST,0497,BATTERY,AGGRAVATED DOMESTIC BATTERY: OTHER DANG WEAPON,RESIDENCE,4856,False,False,724,7,16,68,04B,1170455,1862310,2014,2014-03-03 00:39:23,41.777662488,-87.650657643,"(41.777662488, -87.650657643)" +4261619,HL578454,2005-08-29 09:15:00,134XX S VERNON AVE,0610,BURGLARY,FORCIBLE ENTRY,BAR OR TAVERN,1530,False,False,533,5,9,54,05,1181807,1816319,2005,ERROR,41.651202555,-87.610456918,"(41.651202555, -87.610456918)" +6432144,HP512424,2008-08-14 09:50:00,069XX S MICHIGAN AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,600,True,False,322,3,6,69,26,1178318,1859155,2008,ERROR,41.768829929,-87.621927626,"(41.768829929, -87.621927626)" +2698942,HJ315490,2003-04-22 05:50:00,051XX W CHICAGO AVE,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,STREET,1909,False,False,1531,NA,37,25,03,1141640,1904877,2003,ERROR,41.89505175,-87.755244987,"(41.89505175, -87.755244987)" +9682603,HX333155,2014-07-06 03:15:00,037XX N PLAINFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4490,False,True,1631,16,36,17,08B,1120024,1923862,2014,ERROR,41.9475215,-87.83423089,"(41.9475215, -87.83423089)" +6546232,HP616924,2008-10-09 08:25:00,099XX S PEORIA ST,051A,ASSAULT,AGGRAVATED: HANDGUN,RESIDENTIAL YARD (FRONT/BACK),1859,False,False,2232,22,34,73,04A,1172031,1838751,2008,ERROR,41.712979002,-87.645570329,"(41.712979002, -87.645570329)" +1881357,G729957,2001-12-05 20:38:41,119XX S INDIANA AV,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,2478,False,False,532,NA,NA,NA,05,1179753,1826087,2001,ERROR,41.678054476,-87.617675326,"(41.678054476, -87.617675326)" +6182379,HP269231,2008-04-08 20:30:00,004XX E RANDOLPH ST,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,RESIDENCE,739,True,False,124,1,42,32,16,1179262,1901369,2008,2008-12-04 06:24:35,41.884646917,-87.617177553,"(41.884646917, -87.617177553)" +10144936,HY333549,2015-07-05 15:00:00,044XX N HARDING AVE,031A,ROBBERY,ARMED: HANDGUN,APARTMENT,1023,False,False,1723,17,39,14,03,1149292,1929511,2015,2015-12-07 12:42:46,41.962504708,-87.726500599,"(41.962504708, -87.726500599)" +7819756,HS629676,2010-11-22 19:30:00,047XX S PRINCETON AVE,0810,THEFT,OVER $500,PARK PROPERTY,1456,False,False,935,9,3,37,06,1175039,1873724,2010,ERROR,41.808882585,-87.633512079,"(41.808882585, -87.633512079)" +1863051,G706647,2001-11-24 19:41:00,019XX S SPRINGFIELD AV,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,ALLEY,1573,True,False,1014,NA,NA,NA,15,1150673,1889905,2001,ERROR,41.853795099,-87.722460197,"(41.853795099, -87.722460197)" +5895492,HN672912,2007-10-27 10:56:31,0000X E 103RD PL,2027,NARCOTICS,POSS: CRACK,APARTMENT,2190,False,False,512,5,9,49,18,1178109,1836390,2007,2007-09-11 09:43:39,41.706364714,-87.623382077,"(41.706364714, -87.623382077)" +9222506,HW369005,2013-07-19 01:00:00,062XX S EBERHART AVE,041A,BATTERY,AGGRAVATED: HANDGUN,SIDEWALK,2207,False,False,313,3,20,42,04B,1180607,1863944,2013,ERROR,41.781919159,-87.613390392,"(41.781919159, -87.613390392)" +9470449,HX123527,2014-01-22 11:30:00,064XX N SHERIDAN RD,0810,THEFT,OVER $500,CHA PARKING LOT/GROUNDS,3913,False,False,2432,24,40,1,06,1167086,1942670,2014,ERROR,41.998248939,-87.660699164,"(41.998248939, -87.660699164)" +4564532,HM151795,2006-01-29 12:30:00,005XX N STATE ST,0870,THEFT,POCKET-PICKING,CTA TRAIN,4085,False,False,1834,18,42,8,06,1176315,1903875,2006,2006-02-02 04:21:12,41.891590492,-87.627923574,"(41.891590492, -87.627923574)" +7288999,HR705891,2009-12-26 01:00:00,060XX S WOLCOTT AVE,0560,ASSAULT,SIMPLE,RESIDENCE,1934,True,True,714,7,15,67,08A,1164770,1864356,2009,ERROR,41.783398933,-87.671441226,"(41.783398933, -87.671441226)" +4264409,HL581667,2005-08-30 18:11:00,012XX N STATE PKWY,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4438,True,False,1824,18,43,8,05,1176096,1908851,2005,ERROR,41.905249818,-87.628577732,"(41.905249818, -87.628577732)" +4500033,HL801967,2005-12-20 22:30:00,027XX S KARLOV AVE,0810,THEFT,OVER $500,STREET,3640,False,False,1031,10,22,30,06,1149446,1885667,2005,2014-04-12 12:43:35,41.842189353,-87.727073599,"(41.842189353, -87.727073599)" +4521721,HM109102,2006-01-05 22:30:00,072XX S KEDZIE AVE,0281,CRIM SEXUAL ASSAULT,NON-AGGRAVATED,APARTMENT,2123,False,False,831,8,18,66,02,1156259,1856141,2006,ERROR,41.761031279,-87.702866467,"(41.761031279, -87.702866467)" +5579203,HN387749,2007-06-05 23:00:00,028XX N LAKE SHORE DR,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1495,False,False,2333,19,44,6,26,1173893,1918915,2007,2007-10-07 01:59:11,41.932915255,-87.636369534,"(41.932915255, -87.636369534)" +4112242,HL450369,2005-06-28 17:30:00,043XX S GREENWOOD AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3025,False,False,2123,2,4,39,14,1184277,1876379,2005,ERROR,41.815956678,-87.599546214,"(41.815956678, -87.599546214)" +3506201,HK555583,2004-08-13 16:42:00,044XX W IOWA ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4369,False,False,1111,11,37,23,08B,1146528,1905586,2004,ERROR,41.896905546,-87.737274355,"(41.896905546, -87.737274355)" +2409260,HH725869,2002-10-20 01:00:00,007XX W ROSCOE ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESTAURANT,2063,False,False,2331,NA,44,6,26,1170419,1922748,2002,ERROR,41.943509953,-87.649023628,"(41.943509953, -87.649023628)" +2203186,HH000417,2002-06-19 17:15:00,057XX S LOWE AVE,0313,ROBBERY,ARMED: OTHER DANGEROUS WEAPON,SIDEWALK,4447,False,False,711,NA,3,68,03,1172986,1866791,2002,ERROR,41.789903341,-87.641246766,"(41.789903341, -87.641246766)" +5011028,HM467142,2006-07-10 17:24:47,039XX W ARMITAGE AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,4204,True,False,2535,25,30,20,16,1150000,1912964,2006,2006-07-10 09:43:17,41.917084599,-87.724329742,"(41.917084599, -87.724329742)" +2248732,HH525417,2002-06-08 09:00:00,014XX N WELLS ST,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,4807,False,False,1821,NA,27,8,10,1174403,1909875,2002,ERROR,41.908097724,-87.634765969,"(41.908097724, -87.634765969)" +5736007,HN543999,2007-08-22 14:00:45,072XX N CLARK ST,0820,THEFT,$500 AND UNDER,STREET,248,False,False,2423,24,49,1,06,1163247,1948255,2007,2014-04-12 12:43:35,42.013656195,-87.674663459,"(42.013656195, -87.674663459)" +1601570,G372788,2001-06-26 17:53:24,089XX S COTTAGE GROVE,0460,BATTERY,SIMPLE,RESIDENCE,3120,False,False,633,NA,NA,NA,08B,1183085,1845890,2001,ERROR,41.732319842,-87.604865988,"(41.732319842, -87.604865988)" +8530168,HV207435,2012-03-21 05:45:00,009XX E 130TH PL,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4330,False,False,533,5,9,54,14,1184644,1819036,2012,ERROR,41.658592656,-87.599992356,"(41.658592656, -87.599992356)" +4862832,HM474817,2006-07-10 20:00:00,055XX S SHORE DR,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESTAURANT,2392,False,False,2132,2,5,41,26,1189509,1868821,2006,ERROR,41.795092837,-87.580597321,"(41.795092837, -87.580597321)" +4406900,HK742347,2004-11-10 11:59:00,078XX S CORNELL AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4344,True,False,411,4,8,43,18,1188552,1853522,2004,ERROR,41.753134073,-87.584595019,"(41.753134073, -87.584595019)" +3010706,HJ715145,2003-10-17 11:00:00,062XX N SACRAMENTO AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,1790,False,False,2413,24,50,2,10,1155185,1941017,2003,ERROR,41.993961219,-87.704523692,"(41.993961219, -87.704523692)" +5557879,HN367489,2007-05-27 01:15:00,020XX N STAVE ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,SIDEWALK,4000,True,False,1431,14,1,22,15,1158880,1913340,2007,2010-02-06 10:34:17,41.917938701,-87.691694171,"(41.917938701, -87.691694171)" +3066701,HJ786859,2003-11-22 23:30:00,105XX S MICHIGAN AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,1288,False,False,512,5,9,49,26,1178884,1835024,2003,ERROR,41.702598655,-87.62058548,"(41.702598655, -87.62058548)" +6668993,HP742526,2008-12-19 22:00:00,002XX W EVERGREEN AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2419,False,False,1821,NA,43,8,14,NA,NA,2008,1999-08-11 15:39:40,NA,NA, +6353756,HP432354,2008-07-04 05:00:00,013XX W 97TH ST,0320,ROBBERY,STRONGARM - NO WEAPON,RESIDENCE PORCH/HALLWAY,2082,False,False,2213,22,21,73,03,1168978,1840625,2008,ERROR,41.71818792,-87.656697493,"(41.71818792, -87.656697493)" +6669938,HP740347,2008-11-26 12:00:00,052XX W FOSTER AVE,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,1990,False,False,1623,16,45,11,06,1140701,1934124,2008,ERROR,41.97532578,-87.757972837,"(41.97532578, -87.757972837)" +4826288,HM332892,2006-05-05 20:37:41,042XX S COTTAGE GROVE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1058,True,False,2123,2,4,36,18,1182360,1876778,2006,ERROR,41.817096269,-87.606565687,"(41.817096269, -87.606565687)" +7886005,HT116106,2011-01-12 09:30:00,054XX S HERMITAGE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4182,False,False,932,9,16,61,05,1165565,1868597,2011,ERROR,41.795019934,-87.668406224,"(41.795019934, -87.668406224)" +6705302,HR108700,2005-12-31 09:00:00,029XX N KEDZIE AVE,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,889,False,False,1411,NA,35,21,06,NA,NA,2005,ERROR,NA,NA, +8964081,HW112182,2013-01-10 00:00:00,044XX S ARCHER AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2466,False,False,821,8,14,58,14,1155228,1874993,2013,2013-11-01 07:24:01,41.81278459,-87.706141165,"(41.81278459, -87.706141165)" +7699490,HS506358,2010-09-03 11:00:00,067XX S EAST END AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,776,False,False,332,3,5,43,26,1188736,1860587,2010,ERROR,41.77251665,-87.583695111,"(41.77251665, -87.583695111)" +9180878,HW325720,2013-06-19 11:15:00,110XX S WESTERN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,4683,False,True,2212,22,19,75,08B,1162390,1831737,2013,ERROR,41.693937278,-87.681073558,"(41.693937278, -87.681073558)" +2297694,HH568797,2002-08-09 14:50:00,050XX W WASHINGTON BLVD,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3902,True,False,1533,NA,28,25,18,1142677,1899991,2002,ERROR,41.881624743,-87.751557974,"(41.881624743, -87.751557974)" +7644446,HS448315,2010-08-06 05:23:00,008XX W ADDISON ST,0860,THEFT,RETAIL THEFT,GAS STATION,2267,False,False,2331,19,44,6,06,1170247,1924094,2010,2010-09-08 10:51:39,41.947207196,-87.64961635,"(41.947207196, -87.64961635)" +1423522,G144180,2001-03-11 14:00:00,016XX W GREENLEAF AV,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4093,False,True,2423,NA,NA,NA,26,1164186,1947061,2001,ERROR,42.010359958,-87.671242346,"(42.010359958, -87.671242346)" +5125870,HM723860,2006-11-16 14:00:00,011XX W WILSON AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",4704,True,False,2311,19,46,3,08B,1167577,1930654,2006,ERROR,41.965266117,-87.659240822,"(41.965266117, -87.659240822)" +2492829,HH829410,2002-12-10 12:59:05,041XX W 16TH ST,502R,OTHER OFFENSE,VEHICLE TITLE/REG OFFENSE,STREET,2870,True,False,1012,NA,24,29,26,1148968,1891764,2002,ERROR,41.85892954,-87.728670191,"(41.85892954, -87.728670191)" +3807185,HL176495,2005-02-11 23:40:00,053XX W HIRSCH ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3180,False,False,2532,25,37,25,07,1140742,1908843,2005,ERROR,41.905951477,-87.758445541,"(41.905951477, -87.758445541)" +5654829,HN465341,2007-07-14 01:00:00,053XX S WABASH AVE,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,1188,False,False,232,2,3,40,07,1177577,1869771,2007,ERROR,41.7979781,-87.624322882,"(41.7979781, -87.624322882)" +7897069,HT126881,2011-01-19 20:50:00,016XX N KIMBALL AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,RESIDENCE,3699,True,False,1422,14,26,23,18,1153494,1910674,2011,ERROR,41.910731836,-87.7115537,"(41.910731836, -87.7115537)" +6053794,HP155338,2008-02-02 02:15:00,002XX N ASHLAND AVE,0560,ASSAULT,SIMPLE,TAVERN/LIQUOR STORE,4130,False,False,1333,12,27,28,08A,1165726,1901925,2008,2008-07-02 06:38:43,41.886471844,-87.666867499,"(41.886471844, -87.666867499)" +5565328,HN356933,2007-05-21 21:30:57,070XX S JEFFERY BLVD,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,VEHICLE NON-COMMERCIAL,4147,True,False,331,3,5,43,18,1190805,1858575,2007,ERROR,41.766945825,-87.576175796,"(41.766945825, -87.576175796)" +3248772,HK265125,2004-03-27 02:54:00,0000X W DIVISION ST,3710,INTERFERENCE WITH PUBLIC OFFICER,RESIST/OBSTRUCT/DISARM OFFICER,SIDEWALK,4310,True,False,1824,18,42,8,24,1175892,1908408,2004,2014-04-12 12:43:35,41.904038802,-87.629340436,"(41.904038802, -87.629340436)" +1757877,G572420,2001-09-24 01:00:00,009XX N HARDING AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,797,False,False,1112,NA,NA,NA,07,1149924,1905981,2001,ERROR,41.897924046,-87.72479098,"(41.897924046, -87.72479098)" +2774357,HJ418762,2003-06-09 22:26:00,017XX N TALMAN AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,826,False,False,1421,NA,1,24,03,1158398,1911205,2003,ERROR,41.912089977,-87.693523584,"(41.912089977, -87.693523584)" +8926173,HV598195,2012-12-11 15:00:00,001XX N KEDZIE AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,4028,True,False,1331,12,27,27,26,1155069,1900663,2012,2012-11-12 15:22:54,41.883229246,-87.706036593,"(41.883229246, -87.706036593)" +5741562,HN545599,2007-08-23 11:06:35,056XX W CORCORAN PL,0560,ASSAULT,SIMPLE,GROCERY FOOD STORE,2009,True,False,1512,15,29,25,08A,1138929,1901964,2007,ERROR,41.887107805,-87.765272755,"(41.887107805, -87.765272755)" +7388091,HS188642,2010-03-02 16:00:00,059XX W AUGUSTA BLVD,0460,BATTERY,SIMPLE,SIDEWALK,4552,False,False,1511,15,29,25,08B,1136861,1906101,2010,2010-04-03 11:52:03,41.898497579,-87.772767903,"(41.898497579, -87.772767903)" +4148394,HL472015,2005-07-09 03:00:00,004XX W 98TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,1031,False,True,2223,22,21,73,08B,1174873,1839956,2005,ERROR,41.716222926,-87.635126228,"(41.716222926, -87.635126228)" +7111964,HR520191,2009-09-04 19:15:00,040XX W LAKE ST,2024,NARCOTICS,POSS: HEROIN(WHITE),CTA PLATFORM,1207,True,False,1114,11,28,26,18,1149614,1901481,2009,2009-04-09 20:50:05,41.885581599,-87.726046569,"(41.885581599, -87.726046569)" +1498551,G234518,2001-04-24 20:15:00,005XX N COLUMBUS DR,0810,THEFT,OVER $500,STREET,1805,False,False,1834,NA,NA,NA,06,1178452,1903693,2001,2014-04-12 12:43:35,41.891042598,-87.620081013,"(41.891042598, -87.620081013)" +1470135,G198684,2001-03-30 19:30:00,049XX W WEST END AV,0460,BATTERY,SIMPLE,APARTMENT,883,False,True,1532,NA,NA,NA,08B,1143503,1900491,2001,ERROR,41.882981399,-87.748512379,"(41.882981399, -87.748512379)" +4380629,HL672144,2005-10-14 08:30:00,002XX W 87TH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),2556,False,False,622,6,21,44,14,1176403,1847255,2005,ERROR,41.736218161,-87.629303966,"(41.736218161, -87.629303966)" +3828054,HL198936,2005-02-23 21:00:00,049XX N MILWAUKEE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),1349,False,False,1623,16,45,11,14,1139081,1932636,2005,ERROR,41.971272283,-87.763966593,"(41.971272283, -87.763966593)" +5649370,HN457869,2007-07-10 15:30:00,121XX S PRINCETON AVE,0530,ASSAULT,AGGRAVATED: OTHER DANG WEAPON,STREET,2266,False,False,523,5,34,53,04A,1176434,1824279,2007,ERROR,41.673168048,-87.629877991,"(41.673168048, -87.629877991)" +1926664,G777431,2001-12-30 12:22:03,075XX S COTTAGE GROVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2792,True,False,624,NA,NA,NA,18,1182916,1854849,2001,ERROR,41.756908237,-87.605207385,"(41.756908237, -87.605207385)" +3349468,HK391905,2004-05-25 15:00:00,015XX E 93RD ST,0820,THEFT,$500 AND UNDER,SIDEWALK,483,False,False,413,4,8,48,06,1187948,1843656,2004,2014-04-12 12:43:35,41.726075187,-87.587121912,"(41.726075187, -87.587121912)" +9289687,HW434804,2013-09-02 18:45:00,057XX S PRAIRIE AVE,2023,NARCOTICS,POSS: HEROIN(BRN/TAN),VACANT LOT/LAND,3421,True,False,232,2,20,40,18,1178985,1866850,2013,2013-03-09 11:05:41,41.789930619,-87.619248524,"(41.789930619, -87.619248524)" +4657553,HM172738,2006-02-09 20:15:00,001XX E WACKER DR,1505,PROSTITUTION,CALL OPERATION,HOTEL/MOTEL,2254,True,False,124,1,42,32,16,1177757,1902582,2006,2006-01-04 03:33:39,41.888009783,-87.622667166,"(41.888009783, -87.622667166)" +1848637,G686275,2001-10-17 17:00:00,031XX W 103 ST,1130,DECEPTIVE PRACTICE,FRAUD OR CONFIDENCE GAME,BANK,2485,False,False,2211,NA,NA,NA,11,1157251,1836208,2001,ERROR,41.706311774,-87.699768468,"(41.706311774, -87.699768468)" +1714907,G518024,2001-08-30 00:38:59,122XX S PEORIA ST,0326,ROBBERY,AGGRAVATED VEHICULAR HIJACKING,STREET,2852,False,False,524,NA,NA,NA,03,1172502,1823677,2001,ERROR,41.671603244,-87.644286984,"(41.671603244, -87.644286984)" +8501862,HV177514,2012-02-19 16:00:00,027XX N MOBILE AVE,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,STREET,3247,False,False,2512,25,29,19,26,1133987,1917369,2012,ERROR,41.9294694,-87.783058569,"(41.9294694, -87.783058569)" +2788429,HJ437145,2003-06-17 08:00:00,084XX W GREGORY ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3143,False,False,1614,NA,41,76,14,NA,NA,2003,ERROR,NA,NA, +7308921,HS113429,2010-01-10 10:28:00,130XX S DR MARTIN LUTHER KING JR DR,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4532,True,True,533,5,9,54,08B,1180948,1818658,2010,2010-11-01 05:08:49,41.657640861,-87.613528366,"(41.657640861, -87.613528366)" +9411869,HW555535,2013-11-28 08:00:00,032XX W DOUGLAS BLVD,0890,THEFT,FROM BUILDING,COMMERCIAL / BUSINESS OFFICE,2178,False,False,1022,10,24,29,06,1154818,1893309,2013,2013-02-12 13:43:47,41.86305413,-87.707155285,"(41.86305413, -87.707155285)" +6863000,HR267765,2009-04-14 15:32:00,016XX E 53RD ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,TAXICAB,1052,False,False,2132,2,4,41,11,1188194,1870489,2009,ERROR,41.799701444,-87.585366136,"(41.799701444, -87.585366136)" +8079527,HT312157,2011-05-17 14:00:00,063XX N CLAREMONT AVE,0620,BURGLARY,UNLAWFUL ENTRY,APARTMENT,4304,False,False,2413,24,50,2,05,1159464,1941832,2011,ERROR,41.99611031,-87.688761143,"(41.99611031, -87.688761143)" +5959995,HN755465,2007-11-06 08:00:00,076XX S PHILLIPS AVE,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,BANK,3955,False,True,421,4,7,43,11,1193897,1854798,2007,2008-12-01 01:05:03,41.756506203,-87.564966253,"(41.756506203, -87.564966253)" +4579723,HM166790,2006-02-06 17:30:00,084XX S INGLESIDE AVE,0460,BATTERY,SIMPLE,SIDEWALK,2807,False,False,632,6,8,44,08B,1183976,1849454,2006,ERROR,41.74207911,-87.601490881,"(41.74207911, -87.601490881)" +5789960,HN600058,2007-09-20 10:00:00,071XX W DICKENS AVE,0820,THEFT,$500 AND UNDER,STREET,270,False,False,2512,25,36,25,06,1127875,1913121,2007,2014-04-12 12:43:35,41.917917786,-87.805614941,"(41.917917786, -87.805614941)" +4775666,HM388940,2006-05-30 13:00:00,072XX S KIMBARK AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2394,False,False,324,3,5,69,07,1186082,1857490,2006,2006-04-06 04:24:23,41.764081271,-87.593521441,"(41.764081271, -87.593521441)" +7711055,HS518043,2010-09-16 12:05:00,026XX E 79TH ST,0460,BATTERY,SIMPLE,OTHER,3669,False,False,421,4,7,43,08B,1195505,1853147,2010,ERROR,41.751936174,-87.559127815,"(41.751936174, -87.559127815)" +8137536,HT371329,2011-06-29 00:08:00,038XX W FLOURNOY ST,041A,BATTERY,AGGRAVATED: HANDGUN,RESIDENTIAL YARD (FRONT/BACK),4277,False,False,1133,11,24,26,04B,1150802,1896712,2011,ERROR,41.872471787,-87.721808751,"(41.872471787, -87.721808751)" +7387445,HS189260,2010-01-24 20:37:00,016XX W 32ND ST,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,3922,False,False,922,9,11,59,26,1166016,1883413,2010,2010-05-03 08:02:26,41.835667093,-87.666330688,"(41.835667093, -87.666330688)" +1834918,G668790,2001-11-05 19:00:00,015XX N SPRINGFIELD AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3471,False,False,2535,NA,NA,NA,07,1150124,1910319,2001,ERROR,41.909824052,-87.723943209,"(41.909824052, -87.723943209)" +6824043,HR227480,2009-03-21 10:35:00,124XX S EMERALD AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,896,False,False,523,5,34,53,14,1173548,1822149,2009,ERROR,41.667387143,-87.640503609,"(41.667387143, -87.640503609)" +3334126,HK375058,2004-05-18 19:25:00,018XX W FARWELL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1815,False,False,2424,24,49,1,26,1162731,1945686,2004,ERROR,42.00661768,-87.676634619,"(42.00661768, -87.676634619)" +4991479,HM599065,2006-09-13 14:20:00,004XX N STATE ST,0460,BATTERY,SIMPLE,RESTAURANT,4867,True,False,1831,18,42,8,08B,1176246,1903580,2006,ERROR,41.890782553,-87.628185879,"(41.890782553, -87.628185879)" +4779523,HM394310,2006-06-02 15:30:00,042XX N CICERO AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,VEHICLE NON-COMMERCIAL,2942,False,False,1624,16,45,15,14,1143559,1928046,2006,2006-07-06 04:08:49,41.958594104,-87.747615647,"(41.958594104, -87.747615647)" +7209980,HR625015,2009-11-04 00:05:00,012XX W DIVISION ST,2022,NARCOTICS,POSS: COCAINE,STREET,851,True,False,1323,12,27,24,18,1168223,1908112,2009,2009-04-11 01:46:02,41.903395808,-87.657518982,"(41.903395808, -87.657518982)" +4855527,HM466819,2006-07-06 21:00:00,055XX N CLARK ST,0890,THEFT,FROM BUILDING,CHURCH/SYNAGOGUE/PLACE OF WORSHIP,3503,False,False,2012,20,40,77,06,1164942,1936997,2006,ERROR,41.982728013,-87.668748145,"(41.982728013, -87.668748145)" +5216189,HN101090,2007-01-01 14:47:51,002XX E 136TH ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4188,False,False,533,5,9,54,05,1180454,1815072,2007,ERROR,41.647811613,-87.615445268,"(41.647811613, -87.615445268)" +6782563,HR197644,2009-03-03 19:00:00,076XX S CRANDON AVE,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1821,False,False,414,4,7,43,06,1192816,1854954,2009,2009-04-03 06:03:14,41.756960706,-87.568922754,"(41.756960706, -87.568922754)" +3363754,HK410972,2004-06-05 17:27:50,110XX S AVENUE M,0560,ASSAULT,SIMPLE,STREET,4448,True,False,433,4,10,52,08A,1201506,1832221,2004,ERROR,41.694363358,-87.537846019,"(41.694363358, -87.537846019)" +9749727,HX399758,2014-08-23 16:50:00,101XX S VERNON AVE,0810,THEFT,OVER $500,RESIDENCE,4015,False,False,511,5,9,49,06,1181071,1837939,2014,ERROR,41.71054785,-87.612487964,"(41.71054785, -87.612487964)" +3810647,HL177913,2005-02-12 19:10:00,072XX N SHERIDAN RD,1365,CRIMINAL TRESPASS,TO RESIDENCE,NURSING HOME/RETIREMENT HOME,4948,True,False,2423,24,49,1,26,1166257,1947965,2005,ERROR,42.012796343,-87.66359639,"(42.012796343, -87.66359639)" +2167336,HH418150,2002-06-04 06:00:00,001XX W SUPERIOR ST,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,3092,False,False,1832,NA,42,8,06,1175214,1905375,2002,2014-04-12 12:43:35,41.895731339,-87.631921962,"(41.895731339, -87.631921962)" +2336983,HH635814,2002-09-08 20:00:00,086XX S ELIZABETH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1484,False,False,613,NA,21,71,14,1169547,1847226,2002,ERROR,41.736289738,-87.654422772,"(41.736289738, -87.654422772)" +2283233,HH562586,2002-08-06 12:00:00,032XX W FLOURNOY ST,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2392,False,True,1134,NA,24,27,26,1154671,1896885,2002,ERROR,41.87287,-87.707599224,"(41.87287, -87.707599224)" +9145362,HW290799,2013-05-25 18:20:00,066XX S HALSTED ST,1330,CRIMINAL TRESPASS,TO LAND,SMALL RETAIL STORE,2918,False,False,723,7,6,68,26,1172149,1860963,2013,ERROR,41.773929092,-87.644486986,"(41.773929092, -87.644486986)" +8134619,HT369020,2011-06-27 18:35:00,094XX S WESTERN AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1575,True,False,2221,22,19,72,18,1162082,1841641,2011,ERROR,41.721121942,-87.68192693,"(41.721121942, -87.68192693)" +1662674,G451886,2001-07-31 08:30:00,077XX S CALUMET AV,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4005,False,False,623,NA,NA,NA,05,1179859,1853565,2001,ERROR,41.753455253,-87.616449881,"(41.753455253, -87.616449881)" +5926846,HN721506,2007-11-21 06:00:00,012XX W 98TH ST,0281,CRIM SEXUAL ASSAULT,NON-AGGRAVATED,RESIDENCE,3077,False,False,2213,22,21,73,02,1169700,1839736,2007,ERROR,41.715732768,-87.654078763,"(41.715732768, -87.654078763)" +2621303,HJ224035,2003-03-05 10:00:00,019XX W GREENLEAF AVE,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,1523,False,False,2424,NA,49,1,07,1162189,1946919,2003,ERROR,42.010012453,-87.67859402,"(42.010012453, -87.67859402)" +2334124,HH631588,2002-09-06 21:00:00,025XX N LINDER AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,3020,False,False,2515,NA,30,19,05,1139329,1916155,2002,ERROR,41.926042297,-87.763457601,"(41.926042297, -87.763457601)" +8519471,HV196442,2012-03-12 21:40:00,100XX S CRANDON AVE,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,STREET,3149,False,False,431,4,7,51,03,1193441,1839086,2012,2012-11-04 16:56:10,41.713402235,-87.567149974,"(41.713402235, -87.567149974)" +2508938,HH852379,2002-12-21 02:00:00,025XX N KEDZIE BLVD,0890,THEFT,FROM BUILDING,OTHER,3585,False,False,1414,NA,35,22,06,1154663,1916875,2002,ERROR,41.92772456,-87.707092848,"(41.92772456, -87.707092848)" +4690771,HM291349,2006-04-14 20:50:00,012XX W 72ND ST,0460,BATTERY,SIMPLE,SIDEWALK,1089,False,False,734,7,17,67,08B,1169088,1857072,2006,ERROR,41.763318426,-87.655820347,"(41.763318426, -87.655820347)" +3831802,HL200488,2005-02-24 21:20:56,124XX S EGGLESTON AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,RESIDENCE,3847,False,False,523,5,34,53,15,1175509,1822487,2005,2014-04-12 12:43:35,41.668271184,-87.633316824,"(41.668271184, -87.633316824)" +4288666,HL600343,2005-09-09 00:45:00,033XX W OGDEN AVE,2850,PUBLIC PEACE VIOLATION,BOMB THREAT,POLICE FACILITY/VEH PARKING LOT,4337,False,False,1024,10,24,29,26,1154500,1890985,2005,ERROR,41.856683172,-87.708384737,"(41.856683172, -87.708384737)" +6404121,HP476419,2008-07-26 18:15:00,068XX S CLAREMONT AVE,0530,ASSAULT,AGGRAVATED: OTHER DANG WEAPON,STREET,3421,False,True,832,8,17,66,04A,1161871,1859315,2008,2008-07-08 10:22:37,41.76962648,-87.682209929,"(41.76962648, -87.682209929)" +7629586,HS432289,2010-07-26 14:00:00,011XX W 62ND ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2636,False,True,712,7,16,68,08B,1169767,1863724,2010,ERROR,41.781557634,-87.653138853,"(41.781557634, -87.653138853)" +1504741,G234331,2001-04-24 19:30:00,027XX N SAWYER AV,2027,NARCOTICS,POSS: CRACK,SIDEWALK,4441,True,False,1412,NA,NA,NA,18,1154131,1917989,2001,ERROR,41.930792109,-87.709017944,"(41.930792109, -87.709017944)" +8748002,HV422718,2012-08-09 12:20:00,103XX S AVENUE L,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,3008,False,False,432,4,10,52,07,1201793,1836826,2012,ERROR,41.706992596,-87.536639296,"(41.706992596, -87.536639296)" +3768741,HL139720,2005-01-23 13:30:00,035XX N ELSTON AVE,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,1452,False,False,1733,17,35,21,06,1154312,1923546,2005,2014-04-12 12:43:35,41.946037311,-87.708203891,"(41.946037311, -87.708203891)" +4462522,HL759778,2005-11-28 11:36:00,074XX N GREENVIEW AVE,0460,BATTERY,SIMPLE,APARTMENT,3452,False,False,2422,24,49,1,08B,1164955,1949681,2005,ERROR,42.017532926,-87.668338035,"(42.017532926, -87.668338035)" +6045601,HP146797,2008-01-26 15:00:00,006XX W WAYMAN ST,0610,BURGLARY,FORCIBLE ENTRY,CONSTRUCTION SITE,1006,False,False,1212,12,27,28,05,1171516,1902377,2008,2008-01-02 20:14:43,41.887586797,-87.645592029,"(41.887586797, -87.645592029)" +5682032,HN490569,2007-07-23 00:01:00,004XX N DEARBORN ST,1130,DECEPTIVE PRACTICE,FRAUD OR CONFIDENCE GAME,RESTAURANT,3345,False,False,1831,18,42,8,11,1175894,1903544,2007,ERROR,41.890691698,-87.629479667,"(41.890691698, -87.629479667)" +8520242,HV196949,2012-03-14 12:15:00,015XX W 45TH ST,0312,ROBBERY,ARMED:KNIFE/CUTTING INSTRUMENT,STREET,4644,False,False,924,9,3,61,03,1167048,1874840,2012,ERROR,41.812119833,-87.662789453,"(41.812119833, -87.662789453)" +8071070,HT292750,2011-05-12 17:18:00,006XX E GRAND AVE,0860,THEFT,RETAIL THEFT,OTHER,2071,True,False,1834,18,42,8,06,1180772,1904096,2011,ERROR,41.892095212,-87.611548469,"(41.892095212, -87.611548469)" +9957363,HY146143,2014-12-27 21:00:00,013XX W CHICAGO AVE,0810,THEFT,OVER $500,RESIDENCE,904,False,False,1215,12,27,24,06,1167548,1905439,2014,ERROR,41.896075483,-87.660075443,"(41.896075483, -87.660075443)" +10067136,HY256096,2015-05-11 16:00:00,087XX S BEVERLY AVE,0820,THEFT,$500 AND UNDER,STREET,466,False,False,2221,22,21,71,06,1164962,1846395,2015,ERROR,41.734107372,-87.671244102,"(41.734107372, -87.671244102)" +8115927,HT350334,2011-06-16 12:00:00,058XX N KENMORE AVE,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,2492,False,False,2022,20,48,77,08A,1168225,1939040,2011,ERROR,41.988263519,-87.656614711,"(41.988263519, -87.656614711)" +3295784,HK327947,2004-04-27 12:42:44,055XX W CHICAGO AVE,0860,THEFT,RETAIL THEFT,DRUG STORE,630,True,False,1524,15,37,25,06,1139149,1904830,2004,ERROR,41.894968468,-87.764395055,"(41.894968468, -87.764395055)" +4118774,HL445725,2005-06-26 18:35:00,076XX S CICERO AVE,0560,ASSAULT,SIMPLE,OTHER,3553,True,False,833,8,13,65,08A,1145766,1853738,2005,ERROR,41.75464162,-87.741385158,"(41.75464162, -87.741385158)" +4313295,HL623082,2005-09-19 22:30:00,078XX S CREGIER AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2726,False,True,414,4,8,43,26,1189530,1853090,2005,ERROR,41.751925214,-87.581024925,"(41.751925214, -87.581024925)" +5275302,HM771318,2006-12-12 14:30:00,016XX N VINE ST,0460,BATTERY,SIMPLE,CHA APARTMENT,3342,False,False,1813,18,43,7,08B,1171741,1910972,2006,2007-04-02 09:14:02,41.911167023,-87.644512341,"(41.911167023, -87.644512341)" +9571206,HX221842,2014-04-12 21:00:00,036XX W ARMITAGE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4457,True,True,2535,25,26,22,08B,1151558,1912999,2014,ERROR,41.91715014,-87.718604687,"(41.91715014, -87.718604687)" +8225563,HT459910,2011-06-02 10:00:00,034XX N OVERHILL AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,4580,False,True,1631,16,36,17,10,1124187,1921874,2011,ERROR,41.941998641,-87.818972224,"(41.941998641, -87.818972224)" +3698614,HK800968,2004-12-06 07:28:00,039XX W 55TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3905,False,False,822,8,23,62,14,1151189,1867799,2004,ERROR,41.793123142,-87.721144397,"(41.793123142, -87.721144397)" +6279492,HP366498,2008-05-31 01:49:48,024XX E 77TH ST,0560,ASSAULT,SIMPLE,STREET,3375,False,False,421,4,7,43,08A,1193751,1854422,2008,2008-10-06 17:19:23,41.755478007,-87.565513603,"(41.755478007, -87.565513603)" +3809022,HL179005,2005-02-12 23:00:00,079XX S SACRAMENTO AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4081,False,False,835,8,18,70,14,1157711,1852042,2005,ERROR,41.749753656,-87.69765575,"(41.749753656, -87.69765575)" +2448823,HH773171,2002-11-11 16:55:00,024XX W 46TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4003,False,False,914,NA,12,58,08B,1160595,1874059,2002,ERROR,41.810112404,-87.686480579,"(41.810112404, -87.686480579)" +8784290,HV458206,2012-09-02 15:00:00,010XX W BRYN MAWR AVE,0820,THEFT,$500 AND UNDER,SIDEWALK,324,False,False,2022,20,48,77,06,1168220,1937410,2012,2012-03-09 10:10:19,41.98379087,-87.656680467,"(41.98379087, -87.656680467)" +5066596,HM672873,2006-10-21 01:10:00,063XX S ELIZABETH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,ALLEY,1796,False,True,724,7,16,67,08B,1169122,1862553,2006,2006-04-11 05:54:38,41.778358245,-87.655537413,"(41.778358245, -87.655537413)" +9049125,HW191204,2013-03-13 19:00:00,031XX N SHEFFIELD AVE,0820,THEFT,$500 AND UNDER,STREET,247,False,False,1933,19,44,6,06,1169094,1921197,2013,ERROR,41.939282865,-87.65393884,"(41.939282865, -87.65393884)" +7764239,HS572350,2010-10-19 16:30:00,048XX N SPAULDING AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3988,True,False,1713,17,39,14,18,1153546,1931968,2010,ERROR,41.969163134,-87.710794575,"(41.969163134, -87.710794575)" +2410766,HH726508,2002-10-19 20:00:00,041XX W GRANVILLE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3555,False,False,1711,NA,39,12,14,1147823,1940905,2002,ERROR,41.993799031,-87.731607315,"(41.993799031, -87.731607315)" +6473955,HP548589,2008-09-02 10:38:27,002XX S LAVERGNE AVE,2017,NARCOTICS,MANU/DELIVER:CRACK,SIDEWALK,4717,True,False,1533,15,28,25,18,1143332,1898687,2008,2008-04-09 10:02:31,41.878034197,-87.749185395,"(41.878034197, -87.749185395)" +3811299,HL180923,2005-02-14 14:30:00,001XX W CERMAK RD,0890,THEFT,FROM BUILDING,RESTAURANT,4526,False,False,2111,9,25,34,06,1175413,1889712,2005,ERROR,41.852746625,-87.631661358,"(41.852746625, -87.631661358)" +8254428,HT488217,2011-09-09 08:20:00,012XX S KILDARE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1355,True,True,1011,10,24,29,08B,1147879,1893831,2011,2011-10-09 08:30:21,41.864622619,-87.732614499,"(41.864622619, -87.732614499)" +3052579,HJ766530,2003-11-18 11:15:00,010XX W NORTH AVE,0312,ROBBERY,ARMED:KNIFE/CUTTING INSTRUMENT,SMALL RETAIL STORE,1967,False,False,1811,18,32,7,03,1169395,1910873,2003,ERROR,41.910946738,-87.653133577,"(41.910946738, -87.653133577)" +2802395,HJ456446,2003-06-26 17:00:00,014XX N MAPLEWOOD AVE,0810,THEFT,OVER $500,STREET,3051,False,False,1423,14,26,24,06,1159110,1909223,2003,2014-04-12 12:43:35,41.906636608,-87.690962425,"(41.906636608, -87.690962425)" +8467589,HV143840,2012-02-03 12:45:00,064XX S ST LAWRENCE AVE,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE-GARAGE,2128,False,False,312,3,20,42,26,1181306,1862283,2012,2012-05-02 07:39:28,41.777345128,-87.610878867,"(41.777345128, -87.610878867)" +8672044,HV346931,2012-06-22 02:30:00,002XX N ASHLAND AVE,0820,THEFT,$500 AND UNDER,STREET,66,False,False,1333,12,27,28,06,1165729,1901839,2012,ERROR,41.88623579,-87.666858935,"(41.88623579, -87.666858935)" +2087824,HH307538,2002-04-14 17:00:00,112XX S CORLISS AV,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),121,False,False,531,NA,NA,NA,06,1184357,1830749,2002,2014-04-12 12:43:35,41.690741459,-87.60067812,"(41.690741459, -87.60067812)" +8209337,HT443374,2011-08-11 18:35:00,079XX S HALSTED ST,031A,ROBBERY,ARMED: HANDGUN,ATM (AUTOMATIC TELLER MACHINE),1050,False,False,621,6,17,71,03,1172299,1852411,2011,ERROR,41.750458051,-87.644188274,"(41.750458051, -87.644188274)" +8256672,HT490030,2011-09-10 01:45:00,116XX S DOTY AVE W,0460,BATTERY,SIMPLE,PARKING LOT/GARAGE(NON.RESID.),2252,False,False,532,5,9,54,08B,1183932,1827765,2011,2011-11-09 06:14:39,41.68256287,-87.602326758,"(41.68256287, -87.602326758)" +5916746,HN714104,2007-11-18 02:00:00,026XX W CORTLAND ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,644,False,False,1421,14,1,22,14,1158683,1912488,2007,ERROR,41.91560479,-87.692441353,"(41.91560479, -87.692441353)" +8191615,HT405749,2011-07-19 18:00:00,009XX W WINONA ST,2032,NARCOTICS,MANU/DELIVER: METHAMPHETAMINES,RESIDENCE,2116,True,False,2024,20,48,3,18,1169251,1934368,2011,2011-01-08 11:42:26,41.975421135,-87.652977507,"(41.975421135, -87.652977507)" +2973180,HJ659852,2003-09-28 22:39:00,097XX S HARVARD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3610,False,True,511,5,21,49,08B,1175644,1839981,2003,ERROR,41.716274337,-87.632301694,"(41.716274337, -87.632301694)" +2653149,HJ262801,2003-03-26 22:50:00,083XX S ELLIS AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,721,False,False,632,NA,8,44,05,1184303,1849697,2003,ERROR,41.742738291,-87.600285187,"(41.742738291, -87.600285187)" +2813880,HJ468442,2003-07-02 17:55:00,103XX S CHARLES ST,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,OTHER,1416,False,False,2212,22,19,72,07,1168443,1836435,2003,ERROR,41.70670143,-87.658777228,"(41.70670143, -87.658777228)" +6853270,HR212470,2009-03-12 20:24:00,014XX W 63RD ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3705,True,False,725,7,16,67,18,1167591,1862923,2009,ERROR,41.779406548,-87.661139539,"(41.779406548, -87.661139539)" +4345470,HL643338,2005-09-29 12:30:00,054XX S WENTWORTH AVE,0860,THEFT,RETAIL THEFT,SMALL RETAIL STORE,3555,True,False,232,2,3,37,06,1175924,1869100,2005,ERROR,41.796174063,-87.6304048,"(41.796174063, -87.6304048)" +3145808,HK138334,2004-01-20 14:45:00,001XX W 104TH ST,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,1034,False,True,512,5,34,49,20,1177238,1836035,2004,ERROR,41.705410205,-87.626582292,"(41.705410205, -87.626582292)" +2564500,HJ117629,2003-01-10 06:30:00,046XX S SACRAMENTO AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,4870,True,False,912,NA,14,58,16,1157201,1873342,2003,ERROR,41.808214288,-87.698948812,"(41.808214288, -87.698948812)" +2168460,HH416094,2002-05-31 17:00:00,063XX S ROCKWELL ST,0460,BATTERY,SIMPLE,STREET,1695,False,False,825,NA,15,66,08B,1160083,1862752,2002,ERROR,41.779095055,-87.688669541,"(41.779095055, -87.688669541)" +3960086,HL326851,2005-04-30 08:50:00,017XX W HOWARD ST,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,1739,False,False,2422,24,49,1,06,1163125,1950306,2005,ERROR,42.019286753,-87.675054325,"(42.019286753, -87.675054325)" +7233563,HR577284,2009-10-03 04:35:00,047XX S KILPATRICK AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2783,False,False,815,8,23,56,14,1145784,1872768,2009,ERROR,41.80686277,-87.740838663,"(41.80686277, -87.740838663)" +5041658,HM636747,2006-10-02 22:15:00,015XX N CALIFORNIA AVE,0560,ASSAULT,SIMPLE,STREET,4574,False,True,1423,14,26,24,08A,1157485,1910358,2006,ERROR,41.90978438,-87.696900782,"(41.90978438, -87.696900782)" +1848119,G681456,2001-11-12 15:00:00,045XX S DAMEN AV,0820,THEFT,$500 AND UNDER,DEPARTMENT STORE,255,True,False,914,NA,NA,NA,06,1163704,1874123,2001,2014-04-12 12:43:35,41.810223247,-87.675075347999993,"(41.810223247, -87.675075348)" +3152831,HK148739,2004-01-26 19:00:00,008XX W 52ND ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4518,False,False,934,9,3,61,07,1171851,1870417,2004,ERROR,41.799878475,-87.64530206,"(41.799878475, -87.64530206)" +3800712,HL168190,2005-02-06 18:00:00,026XX W 74TH ST,0460,BATTERY,SIMPLE,STREET,4990,False,False,835,8,18,66,08B,1159986,1855543,2005,ERROR,41.759314508,-87.689223078,"(41.759314508, -87.689223078)" +4838698,HM450854,2006-07-02 20:22:36,066XX S DAMEN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3325,False,True,726,7,15,67,08B,1164211,1860657,2006,2006-08-07 04:09:46,41.773260178,-87.673594762,"(41.773260178, -87.673594762)" +4567800,HM152622,2006-01-30 01:05:00,062XX S PULASKI RD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2362,True,True,823,8,13,65,08B,1150823,1862618,2006,2006-04-02 03:37:59,41.778912821,-87.72262144,"(41.778912821, -87.72262144)" +4954048,HM567985,2006-08-28 19:00:00,039XX W NORTH AVE,0820,THEFT,$500 AND UNDER,RESTAURANT,63,False,False,2535,25,30,23,06,1149603,1910380,2006,2014-04-12 12:43:35,41.910001584,-87.725855574,"(41.910001584, -87.725855574)" +6442605,HP523830,2008-08-19 19:15:00,022XX N STOCKTON DR,0810,THEFT,OVER $500,STREET,4144,False,False,1814,18,43,7,06,1174044,1915453,2008,ERROR,41.923412022,-87.635918111,"(41.923412022, -87.635918111)" +8013905,HT245828,2011-04-12 01:00:00,117XX S ASHLAND AVE,0325,ROBBERY,VEHICULAR HIJACKING,STREET,3478,False,False,524,5,34,53,03,1167822,1827065,2011,ERROR,41.681001922,-87.66131902,"(41.681001922, -87.66131902)" +2134668,HH365544,2002-05-11 19:52:00,012XX W 69TH ST,2111,NARCOTICS,SALE/DEL HYPODERMIC NEEDLE,STREET,3604,True,False,724,NA,17,67,26,1169493,1859073,2002,ERROR,41.768800662,-87.654278043,"(41.768800662, -87.654278043)" +5610513,HN414573,2007-06-19 04:45:00,050XX W DICKENS AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1912,False,False,2522,25,31,19,14,1142601,1913456,2007,ERROR,41.918575659,-87.75150172,"(41.918575659, -87.75150172)" +8573841,HV248444,2012-04-19 00:45:00,048XX S MICHIGAN AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,1936,False,False,224,2,3,38,14,1178018,1872850,2012,ERROR,41.806417165,-87.622612308,"(41.806417165, -87.622612308)" +1561696,G322059,2001-06-04 02:17:00,051XX S WESTERN AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,OTHER,1450,True,False,915,NA,NA,NA,07,1161425,1870775,2001,ERROR,41.801083515,-87.683527305,"(41.801083515, -87.683527305)" +3927547,HL299959,2005-04-17 00:05:00,106XX S PERRY AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4821,False,False,512,5,34,49,14,1177525,1834390,2005,ERROR,41.700889625,-87.625580837,"(41.700889625, -87.625580837)" +4652877,HM250481,2006-03-24 14:50:00,006XX N AVERS AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,3812,False,False,1122,11,27,23,04B,1150574,1903795,2006,ERROR,41.891912762,-87.722460741,"(41.891912762, -87.722460741)" +5843579,HN653528,2007-10-15 01:00:00,021XX E 83RD ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1948,False,False,414,4,8,46,14,1191722,1850396,2007,ERROR,41.744479768,-87.573079607,"(41.744479768, -87.573079607)" +3203166,HK216335,2004-03-02 13:30:00,003XX N OAKLEY BLVD,0810,THEFT,OVER $500,STREET,4568,False,False,1332,12,27,28,06,1160987,1901959,2004,2014-04-12 12:43:35,41.886664822,-87.684269315,"(41.886664822, -87.684269315)" +1606977,G378687,2001-06-29 10:15:00,0000X W RANDOLPH ST,0560,ASSAULT,SIMPLE,OTHER,3282,False,True,122,NA,NA,NA,08A,1176018,1901322,2001,ERROR,41.884591617,-87.629091254,"(41.884591617, -87.629091254)" +4840076,HM452107,2006-07-03 13:00:00,040XX W 26TH ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA BUS,3532,True,False,1013,10,22,30,11,1150073,1886464,2006,2006-06-07 04:48:17,41.844364255,-87.724751946,"(41.844364255, -87.724751946)" +2414638,HH721521,2002-10-18 01:45:00,057XX S NEWCASTLE AVE,0560,ASSAULT,SIMPLE,RESIDENCE,4205,True,True,811,NA,23,56,08A,1131678,1865389,2002,ERROR,41.786868643,-87.792746288,"(41.786868643, -87.792746288)" +9984576,HY173853,2015-03-06 12:20:00,036XX S PAULINA ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2794,True,True,912,9,11,59,08B,1165657,1880409,2015,ERROR,41.827431448,-87.667733383,"(41.827431448, -87.667733383)" +4690605,HM294209,2006-04-15 23:30:00,035XX W DIVERSEY AVE,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,4065,False,False,1412,14,35,21,06,1152451,1918432,2006,2014-04-12 12:43:35,41.93204114,-87.715179885,"(41.93204114, -87.715179885)" +9817517,HX467213,2014-10-14 12:15:00,023XX S MICHIGAN AVE,0560,ASSAULT,SIMPLE,APARTMENT,592,False,False,131,1,2,33,08A,1177532,1889067,2014,ERROR,41.850928923,-87.623903608,"(41.850928923, -87.623903608)" +4924804,HM540163,2006-08-14 05:20:00,040XX W POTOMAC AVE,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,295,False,False,2534,25,27,23,06,1149398,1908322,2006,2014-04-12 12:43:35,41.90435821,-87.726662138,"(41.90435821, -87.726662138)" +5819771,HN629390,2007-10-04 22:30:00,087XX S SAGINAW AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,2737,False,False,423,4,7,46,04B,1195348,1847736,2007,ERROR,41.737091832,-87.559881365,"(41.737091832, -87.559881365)" +4431290,HL717454,2005-11-05 14:30:00,001XX W LAKE ST,0870,THEFT,POCKET-PICKING,CTA PLATFORM,1953,False,False,113,1,42,32,06,1175460,1901776,2005,ERROR,41.885849966,-87.631126643,"(41.885849966, -87.631126643)" +3020488,HJ684755,2003-10-10 16:45:00,075XX S MORGAN ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,865,True,False,612,6,17,71,18,1170904,1855024,2003,ERROR,41.75765901,-87.649224054,"(41.75765901, -87.649224054)" +7534781,HS338561,2010-06-01 17:30:00,015XX N CICERO AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,DEPARTMENT STORE,1334,False,True,2533,25,37,25,08B,1144129,1910138,2010,2010-06-06 10:23:35,41.909442134,-87.745971135,"(41.909442134, -87.745971135)" +8552792,HV228661,2012-04-03 00:00:00,053XX W FOSTER AVE,1720,OFFENSE INVOLVING CHILDREN,CONTRIBUTE DELINQUENCY OF A CHILD,APARTMENT,3251,False,False,1623,16,45,11,20,1139799,1934106,2012,ERROR,41.975292963,-87.761290293,"(41.975292963, -87.761290293)" +2489010,HH826679,2002-11-30 21:00:00,023XX S LAKE SHORE DR E,0890,THEFT,FROM BUILDING,OTHER,2775,False,False,133,NA,2,33,06,NA,NA,2002,ERROR,NA,NA, +2690599,HJ311147,2003-04-19 21:46:00,059XX S ADA ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4856,False,True,713,NA,16,67,08B,1168389,1865153,2003,ERROR,41.785508785,-87.658149785,"(41.785508785, -87.658149785)" +3823642,HL190768,2005-02-19 15:30:00,028XX N MOZART ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2877,False,False,1411,14,35,21,07,1156853,1918544,2005,ERROR,41.932260245,-87.699000048,"(41.932260245, -87.699000048)" diff --git a/work-with-data/dataprep/data/crime-spring.csv b/work-with-data/dataprep/data/crime-spring.csv new file mode 100644 index 00000000..3750a186 --- /dev/null +++ b/work-with-data/dataprep/data/crime-spring.csv @@ -0,0 +1,11 @@ +ID,Case Number,Date,Block,IUCR,Primary Type,Description,Location Description,Arrest,Domestic,Beat,District,Ward,Community Area,FBI Code,X Coordinate,Y Coordinate,Year,Updated On,Latitude,Longitude,Location +10498554,HZ239907,4/4/2016 23:56,007XX E 111TH ST,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,OTHER,FALSE,FALSE,531,5,9,50,11,1183356,1831503,2016,5/11/2016 15:48,41.69283384,-87.60431945,"(41.692833841, -87.60431945)" +10516598,HZ258664,4/15/2016 17:00,082XX S MARSHFIELD AVE,890,THEFT,FROM BUILDING,RESIDENCE,FALSE,FALSE,614,6,21,71,6,1166776,1850053,2016,5/12/2016 15:48,41.74410697,-87.66449429,"(41.744106973, -87.664494285)" +10519196,HZ261252,4/15/2016 10:00,104XX S SACRAMENTO AVE,1154,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT $300 AND UNDER,RESIDENCE,FALSE,FALSE,2211,22,19,74,11,,,2016,5/12/2016 15:50,,, +10519591,HZ261534,4/15/2016 9:00,113XX S PRAIRIE AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,FALSE,FALSE,531,5,9,49,10,,,2016,5/13/2016 15:51,,, +10534446,HZ277630,4/15/2016 10:00,055XX N KEDZIE AVE,890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",FALSE,FALSE,1712,17,40,13,6,,,2016,5/25/2016 15:59,,, +10535059,HZ278872,4/15/2016 4:30,004XX S KILBOURN AVE,810,THEFT,OVER $500,RESIDENCE,FALSE,FALSE,1131,11,24,26,6,,,2016,5/25/2016 15:59,,, +10499802,HZ240778,4/15/2016 10:00,010XX N MILWAUKEE AVE,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,RESIDENCE,FALSE,FALSE,1213,12,27,24,11,,,2016,5/27/2016 15:45,,, +10522293,HZ264802,4/15/2016 16:00,019XX W DIVISION ST,1110,DECEPTIVE PRACTICE,BOGUS CHECK,RESTAURANT,FALSE,FALSE,1424,14,1,24,11,1163094,1908003,2016,5/16/2016 15:48,41.90320604,-87.67636193,"(41.903206037, -87.676361925)" +10523111,HZ265911,4/15/2016 8:00,061XX N SHERIDAN RD,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,RESIDENCE,FALSE,FALSE,2433,24,48,77,11,,,2016,5/16/2016 15:50,,, +10525877,HZ268138,4/15/2016 15:00,023XX W EASTWOOD AVE,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,,FALSE,FALSE,1911,19,47,4,11,,,2016,5/18/2016 15:50,,, diff --git a/work-with-data/dataprep/data/crime-winter.csv b/work-with-data/dataprep/data/crime-winter.csv new file mode 100644 index 00000000..4c70d468 --- /dev/null +++ b/work-with-data/dataprep/data/crime-winter.csv @@ -0,0 +1,11 @@ +ID,Case Number,Date,Block,IUCR,Primary Type,Description,Location Description,Arrest,Domestic,Beat,District,Ward,Community Area,FBI Code,X Coordinate,Y Coordinate,Year,Updated On,Latitude,Longitude,Location +10378283,HZ114126,1/10/2016 11:00,033XX W IRVING PARK RD,610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,TRUE,FALSE,1724,17,33,16,5,1153593,1926401,2016,5/22/2016 15:51,41.95388599,-87.71077048,"(41.95388599, -87.710770479)" +10382154,HZ118288,1/10/2016 21:00,055XX S FRANCISCO AVE,1754,OFFENSE INVOLVING CHILDREN,AGG SEX ASSLT OF CHILD FAM MBR,RESIDENCE,FALSE,TRUE,824,8,14,63,2,1157983,1867874,2016,6/1/2016 15:51,41.79319349,-87.69622926,"(41.793193489, -87.696229255)" +10374287,HZ110730,1/10/2016 11:50,043XX W ARMITAGE AVE,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,STREET,FALSE,TRUE,2522,25,30,20,26,1146917,1912931,2016,6/7/2016 15:55,41.91705356,-87.73565764,"(41.917053561, -87.735657637)" +10374662,HZ110403,1/10/2016 1:30,073XX S CLAREMONT AVE,497,BATTERY,AGGRAVATED DOMESTIC BATTERY: OTHER DANG WEAPON,STREET,FALSE,TRUE,835,8,18,66,04B,1162007,1855951,2016,2/4/2016 15:44,41.76039236,-87.68180481,"(41.760392356, -87.681804812)" +10374720,HZ110836,1/10/2016 7:30,079XX S RHODES AVE,890,THEFT,FROM BUILDING,OTHER,FALSE,FALSE,624,6,6,44,6,1181279,1852568,2016,2/4/2016 15:44,41.75068679,-87.61127681,"(41.75068679, -87.611276811)" +10375178,HZ110832,1/10/2016 14:20,057XX S KEDZIE AVE,460,BATTERY,SIMPLE,RESTAURANT,FALSE,FALSE,824,8,14,63,08B,1156029,1866379,2016,2/4/2016 15:44,41.78913051,-87.7034346,"(41.78913051, -87.703434602)" +10398695,HZ135279,1/10/2016 23:00,031XX S PARNELL AVE,620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,FALSE,FALSE,915,9,11,60,5,1173138,1884117,2016,2/4/2016 15:44,41.8374442,-87.64017699,"(41.837444199, -87.640176991)" +10402270,HZ138745,1/10/2016 11:00,051XX S ELIZABETH ST,620,BURGLARY,UNLAWFUL ENTRY,APARTMENT,FALSE,FALSE,934,9,16,61,5,,,2016,2/4/2016 6:53,,, +10380619,HZ116583,1/10/2016 9:41,091XX S PAXTON AVE,4387,OTHER OFFENSE,VIOLATE ORDER OF PROTECTION,RESIDENCE,TRUE,TRUE,413,4,7,48,26,1192434,1844707,2016,2/2/2016 15:56,41.72885134,-87.57065553,"(41.728851343, -87.570655525)" +10400131,HZ136171,1/10/2016 18:00,0000X W TERMINAL ST,810,THEFT,OVER $500,AIRPORT BUILDING NON-TERMINAL - SECURE AREA,FALSE,FALSE,1651,16,41,76,6,,,2016,2/2/2016 15:58,,, diff --git a/work-with-data/dataprep/data/crime.dprep b/work-with-data/dataprep/data/crime.dprep new file mode 100644 index 00000000..58a84196 --- /dev/null +++ b/work-with-data/dataprep/data/crime.dprep @@ -0,0 +1,204 @@ +{ + "id": "75637565-60ad-4baa-87d3-396a7930cfe7", + "blocks": [ + { + "id": "ba5a8061-129e-4618-953a-ce3e89c8f2cb", + "type": "Microsoft.DPrep.GetFilesBlock", + "arguments": { + "path": { + "target": 0, + "resourceDetails": [ + { + "path": "./crime-spring.csv" + } + ] + } + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "1b345643-6b60-4ca1-99f9-2a64ae932a23", + "type": "Microsoft.DPrep.ParseDelimitedBlock", + "arguments": { + "columnHeadersMode": 1, + "fileEncoding": 0, + "handleQuotedLineBreaks": false, + "preview": false, + "separator": ",", + "skipRowsMode": 0 + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "12cf73a2-1487-4915-bfa7-c86be7de08c0", + "type": "Microsoft.DPrep.SetColumnTypesBlock", + "arguments": { + "columnConversion": [ + { + "column": { + "type": 2, + "details": { + "selectedColumn": "ID" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "IUCR" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Domestic" + } + }, + "typeProperty": 1 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Beat" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "District" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Ward" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Community Area" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Year" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Longitude" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Arrest" + } + }, + "typeProperty": 1 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "X Coordinate" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Updated On" + } + }, + "typeArguments": { + "dateTimeFormats": [ + "%m/%d/%Y %I:%M:%S %p" + ] + }, + "typeProperty": 4 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Date" + } + }, + "typeArguments": { + "dateTimeFormats": [ + "%m/%d/%Y %I:%M:%S %p" + ] + }, + "typeProperty": 4 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Y Coordinate" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Latitude" + } + }, + "typeProperty": 3 + } + ] + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "dfd62543-9285-412b-a930-0aeaaffde699", + "type": "Microsoft.DPrep.HandlePathColumnBlock", + "arguments": { + "pathColumnOperation": 0 + }, + "isEnabled": true, + "name": null, + "annotation": null + } + ], + "inspectors": [] +} \ No newline at end of file diff --git a/work-with-data/dataprep/data/crime.parquet b/work-with-data/dataprep/data/crime.parquet new file mode 100644 index 0000000000000000000000000000000000000000..dc06b73120f80a4d13b3791adc1f7d12c8ebe9f2 GIT binary patch literal 3607 zcma)C|F3u8=U$UC_VhDu7gTPl~CF)EHUDcvN-%wV>;*fCvbw__w(sbtGW z8>WQHrG(RVbtxw*Su471QmNIpDw|L1pk|+WXU5n));a$1KF{y_yZ@f&`3##8<_e?W zatO|b=EJ7oQxDcdp=|czQOKWROiXk%nS(VUg3G2d-G|DtKG?h@H&^dvLfyf*^nrl*F>@&dI+%Xv@5E&Nx++#+LA+4dN{Gk7BfLoXT8xj=O1_$_?*Gd-8#% z19C-m=wUghsWkI|AG>bX%X6#EMsGBof9kTeu?Fieaai0N{>0hY;ZbzQYnoX^eJi(i zPKfjG1xKx2Ja5@7Pp^hFp5uoH3pJi^V&Hc4q_y+e?@DTPAqHY*&@)I`e#})xXE$_~ zt=u*J#>z*dg=yEr*ZcS=mA{^FEm^p+RcJGC5smrr*+KpKyf!!@;vM=)cG}kXO9Y(X z+=R-6>B5$VJE0q+v%d^ul-_^N!U-N^Y*im>m|*Hf~dJ4L;N+NzY2-^(5EgMXnWRFMZ=m9iH3`;>WAbO_+xB+ z1ukD9b57gjf@Avk?`ii-q{(orsSZXO!=DA$#A%vdiH6@zZdiExAA}SaBDQ^A#zm<@ zzdt-?pGaY|>_q!$)b-B8hF(qFWhHBaMUQDMq!re<#V#}N8I?Bg6{1d`@s4dPmdJXr zoo}Dp)!N#8m0Dz!5q)kS`tWzL476L`mAU?V{O^IuP|e?&IR&%6Rp*$FfjcH|&+3pX zghm%~C5b=?O{I~%E?APk6$d5P%4f}ZQ@Q)Ha_^CjmKV=QpKOcV_BhhQ&%Zh44(4_e z-OpnwbDpte-?T$)kL?L-;O`qL73UA$*PmhR;@G_-QP#LJDytxO%|Jt2@YfqNwr^OK ziYx7%X?;`*H`fox*JLj<>Uk-QByT?|F`=br*!?V8cZ5*gr7X13l+oKl+1I3wy$B5t z|4kXQ7=%VTF>t9O)sd0;dSlTL(ROD7t!0@-T94AMP54Xmou3D-#}eoA{US%i=JZb| zMrqt|!mPC?7ew@S+Z^-T9=SWM=I~;A zxmdT9`g({NK^OQsR6r*Ki)u^`t&w-wvo|zo&%T{a*OoAKdLY)+$s44-H;Ke)P9*X} z4Ab3#@t;ukRnJ3P(>g5J57EJw0t6U+ZIJm%_{W+cfz6buZPJULp%ni2cb(F6c6eeE z{D>ZZ;ezv_y4@XAcG5q`OPQW|sUBjfvj%0%{hJ6U)X{9Xe3})*qZ^0+%H5KAYm>Zvu}wgH z!9H^GM-TjhWFqs7*h_}hSns`2*Q%*0u%7>aSU+MZv|q+e{|%NFn?GBNm<$c^WVIPiQFOqh)} zLkF=?FcEwh25=Jjw9qZ07`}8i(A5KbSQbadSBr5>p@h3xB~WQ$gTe#C)B++~DiCp` zNwn2T34E0tF~;I2a;1UUCWX3)DlvFL?qfG7RZul3@M+Z4gng~Kb4zevgP!^T8L5M^gr&+5K$7_=W zSyN2Sspex8dps2;n0xBsQRb>@!&r(br30eWLH6JsvA+sYVWLJlu>}Zr98GD100Ouy z3>DVXa7bIUzz_sv07${<45^!i5N|M)4`?6+#0Py{AV>qXp;SXa27odHoryoS6#+ZZ zlw34m0C?3<2O3W3Cc>3tD8p!g0IrUt<0frCqR@<{6rceM6gtUN7^TTlGZF~mMu`P7 z09-WI0pnvbj$AYbNRi2eG*zR6h}9J@G66u>bR950PzZDrq8x{S96-aSItUpvt(7te z$W(49I^ZwEr$tL3KmoLQhOP|bBZ)xe5TyVDasc(2>FoV!OcAgZqFjamqjGF90h~Cz zTCg6twE$qRr4B?!8UflN%2pr(@-C`wq$bQcLKUGYsh|>6fZey!nZawPU+XI!4dejo zvC%=gGohtY&;U?Twi61WJt+v3j-zB^fE=8f-dPk=l!FypeQP;|@@2HGd}*TU;&5|v Tb@Fg>Tj-5?1m0sd_(}K&3>y?D literal 0 HcmV?d00001 diff --git a/work-with-data/dataprep/data/crime.txt b/work-with-data/dataprep/data/crime.txt new file mode 100644 index 00000000..d6d8b8d7 --- /dev/null +++ b/work-with-data/dataprep/data/crime.txt @@ -0,0 +1,10 @@ +10140490 HY329907 7/5/2015 23:50 050XX N NEWLAND AVE 820 THEFT +10139776 HY329265 7/5/2015 23:30 011XX W MORSE AVE 460 BATTERY +10140270 HY329253 7/5/2015 23:20 121XX S FRONT AVE 486 BATTERY +10139885 HY329308 7/5/2015 23:19 051XX W DIVISION ST 610 BURGLARY +10140379 HY329556 7/5/2015 23:00 012XX W LAKE ST 930 MOTOR VEHICLE THEFT +10140868 HY330421 7/5/2015 22:54 118XX S PEORIA ST 1320 CRIMINAL DAMAGE +10139762 HY329232 7/5/2015 22:42 026XX W 37TH PL 1020 ARSON +10139722 HY329228 7/5/2015 22:30 016XX S CENTRAL PARK AVE 1811 NARCOTICS +10139774 HY329209 7/5/2015 22:15 048XX N ASHLAND AVE 1310 CRIMINAL DAMAGE +10139697 HY329177 7/5/2015 22:10 058XX S ARTESIAN AVE 1320 CRIMINAL DAMAGE diff --git a/work-with-data/dataprep/data/crime.xlsx b/work-with-data/dataprep/data/crime.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..21f200b4e38cb035371f6a27f7325b6af6302bca GIT binary patch literal 16109 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�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 literal 0 HcmV?d00001 diff --git a/work-with-data/dataprep/data/secrets.dprep b/work-with-data/dataprep/data/secrets.dprep new file mode 100644 index 00000000..bf156e3c --- /dev/null +++ b/work-with-data/dataprep/data/secrets.dprep @@ -0,0 +1,63 @@ +{ + "id": "b308e5b8-9b2a-47f8-9d32-0f542b4a34a4", + "name": "read_csv_duplicate_headers", + "blocks": [ + { + "id": "8d9ec228-6a4b-4abf-afb7-65f58dda1581", + "type": "Microsoft.DPrep.GetFilesBlock", + "arguments": { + "path": { + "target": 1, + "resourceDetails": [ + { + "path": "https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv", + "sas": { + "id": "https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv", + "secretType": "AzureMLSecret" + }, + "storageAccountName": null, + "storageAccountKey": null + } + ] + } + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "4ad0460f-ec65-47c0-a0a4-44345404a462", + "type": "Microsoft.DPrep.ParseDelimitedBlock", + "arguments": { + "columnHeadersMode": 3, + "fileEncoding": 0, + "handleQuotedLineBreaks": false, + "preview": false, + "separator": ",", + "skipRows": 0, + "skipRowsMode": 0 + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "1a3e11ba-5854-48da-aa47-53af61beb782", + "type": "Microsoft.DPrep.DropColumnsBlock", + "arguments": { + "columns": { + "type": 0, + "details": { + "selectedColumns": [ + "Path" + ] + } + } + }, + "isEnabled": true, + "name": null, + "annotation": null + } + ], + "inspectors": [] +} \ No newline at end of file diff --git a/work-with-data/dataprep/data/stream-path.csv b/work-with-data/dataprep/data/stream-path.csv new file mode 100644 index 00000000..175f3801 --- /dev/null +++ b/work-with-data/dataprep/data/stream-path.csv @@ -0,0 +1,11 @@ +Stream Path +https://dataset.blob.core.windows.net/blobstore/container/2019/01/01/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/02/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/03/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/04/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/05/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/06/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/07/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/08/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/09/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/10/train.csv diff --git a/work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb b/work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb new file mode 100644 index 00000000..3fa0e65e --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb @@ -0,0 +1,360 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/add-column-using-expression.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Add Column using Expression\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With Azure ML Data Prep you can add a new column to data with `Dataflow.add_column` by using a Data Prep expression to calculate the value from existing columns. This is similar to using Python to create a [new script column](./custom-python-transforms.ipynb#New-Script-Column) except the Data Prep expressions are more limited and will execute faster. The expressions used are the same as for [filtering rows](./filtering.ipynb#Filtering-rows) and hence have the same functions and operators available.\n", + "

\n", + "Here we add additional columns. First we get input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# loading data\n", + "dflow = dprep.auto_read_file('../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `substring(start, length)`\n", + "Add a new column \"Case Category\" using the `substring(start, length)` expression to extract the prefix from the \"Case Number\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "case_category = dflow.add_column(new_column_name='Case Category',\n", + " prior_column='Case Number',\n", + " expression=dflow['Case Number'].substring(0, 2))\n", + "case_category.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `substring(start)`\n", + "Add a new column \"Case Id\" using the `substring(start)` expression to extract just the number from \"Case Number\" column and then convert it to numeric." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "case_id = dflow.add_column(new_column_name='Case Id',\n", + " prior_column='Case Number',\n", + " expression=dflow['Case Number'].substring(2))\n", + "case_id = case_id.to_number('Case Id')\n", + "case_id.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `length()`\n", + "Using the length() expression, add a new numeric column \"Length\", which contains the length of the string in \"Primary Type\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_length = dflow.add_column(new_column_name='Length',\n", + " prior_column='Primary Type',\n", + " expression=dflow['Primary Type'].length())\n", + "dflow_length.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `to_upper()`\n", + "Using the to_upper() expression, add a new numeric column \"Upper Case\", which contains the length of the string in \"Primary Type\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_to_upper = dflow.add_column(new_column_name='Upper Case',\n", + " prior_column='Primary Type',\n", + " expression=dflow['Primary Type'].to_upper())\n", + "dflow_to_upper.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `to_lower()`\n", + "Using the to_lower() expression, add a new numeric column \"Lower Case\", which contains the length of the string in \"Primary Type\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_to_lower = dflow.add_column(new_column_name='Lower Case',\n", + " prior_column='Primary Type',\n", + " expression=dflow['Primary Type'].to_lower())\n", + "dflow_to_lower.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `RegEx.extract_record()`\n", + "Using the `RegEx.extract_record()` expression, add a new record column \"Stream Date Record\", which contains the name capturing groups in the regex with value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_regex_extract_record = dprep.auto_read_file('../data/stream-path.csv')\n", + "regex = dprep.RegEx('\\/(?\\d{4})\\/(?\\d{2})\\/(?\\d{2})\\/')\n", + "dflow_regex_extract_record = dflow_regex_extract_record.add_column(new_column_name='Stream Date Record',\n", + " prior_column='Stream Path',\n", + " expression=regex.extract_record(dflow_regex_extract_record['Stream Path']))\n", + "dflow_regex_extract_record.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `create_datetime()`\n", + "Using the `create_datetime()` expression, add a new column \"Stream Date\", which contains datetime values constructed from year, month, day values extracted from a record column \"Stream Date Record\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "year = dprep.col('year', dflow_regex_extract_record['Stream Date Record'])\n", + "month = dprep.col('month', dflow_regex_extract_record['Stream Date Record'])\n", + "day = dprep.col('day', dflow_regex_extract_record['Stream Date Record'])\n", + "dflow_create_datetime = dflow_regex_extract_record.add_column(new_column_name='Stream Date',\n", + " prior_column='Stream Date Record',\n", + " expression=dprep.create_datetime(year, month, day))\n", + "dflow_create_datetime.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) + col(column2)`\n", + "Add a new column \"Total\" to show the result of adding the values in the \"FBI Code\" column to the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_total = dflow.add_column(new_column_name='Total',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']+dflow['FBI Code'])\n", + "dflow_total.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) - col(column2)`\n", + "Add a new column \"Subtract\" to show the result of subtracting the values in the \"FBI Code\" column from the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_diff = dflow.add_column(new_column_name='Difference',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']-dflow['FBI Code'])\n", + "dflow_diff.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) * col(column2)`\n", + "Add a new column \"Product\" to show the result of multiplying the values in the \"FBI Code\" column to the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_prod = dflow.add_column(new_column_name='Product',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']*dflow['FBI Code'])\n", + "dflow_prod.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) / col(column2)`\n", + "Add a new column \"True Quotient\" to show the result of true (decimal) division of the values in \"Community Area\" column by the \"FBI Code\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_true_div = dflow.add_column(new_column_name='True Quotient',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']/dflow['FBI Code'])\n", + "dflow_true_div.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) // col(column2)`\n", + "Add a new column \"Floor Quotient\" to show the result of floor (integer) division of the values in \"Community Area\" column by the \"FBI Code\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_floor_div = dflow.add_column(new_column_name='Floor Quotient',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']//dflow['FBI Code'])\n", + "dflow_floor_div.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) % col(column2)`\n", + "Add a new column \"Mod\" to show the result of applying the modulo operation on the \"FBI Code\" column and the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_mod = dflow.add_column(new_column_name='Mod',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']%dflow['FBI Code'])\n", + "dflow_mod.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) ** col(column2)`\n", + "Add a new column \"Power\" to show the result of applying the exponentiation operation when the base is the \"Community Area\" column and the exponent is \"FBI Code\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_pow = dflow.add_column(new_column_name='Power',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']**dflow['FBI Code'])\n", + "dflow_pow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb b/work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb new file mode 100644 index 00000000..51a55e4a --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/append-columns-and-rows.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Append Columns and Rows\n", + "\n", + "Often the data we want does not come in a single dataset: they are coming from different locations, have features that are separated, or are simply not homogeneous. Unsurprisingly, we typically want to work with a single dataset at a time.\n", + "\n", + "Azure ML Data Prep allows the concatenation of two or more dataflows by means of column and row appends.\n", + "\n", + "We will demonstrate this by defining a single dataflow that will pull data from multiple datasets.\n", + "\n", + "## Table of Contents\n", + "[append_columns(dataflows)](#append_columns)
\n", + "[append_rows(dataflows)](#append_rows)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `append_columns(dataflows)`\n", + "We can append data width-wise, which will change some or all existing rows and potentially adding rows (based on an assumption that data in two datasets are aligned on row number).\n", + "\n", + "However we cannot do this if the reference dataflows have clashing schema with the target dataflow. Observe:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_chicago = auto_read_file(path='../data/chicago-aldermen-2015.csv')\n", + "dflow_chicago.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ExecutionError\n", + "try:\n", + " dflow_combined_by_column = dflow.append_columns([dflow_chicago])\n", + " dflow_combined_by_column.head(5)\n", + "except ExecutionError:\n", + " print('Cannot append_columns with schema clash!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As expected, we cannot call `append_columns` with target dataflows that have clashing schema.\n", + "\n", + "We can make the call once we rename or drop the offending columns. In more complex scenarios, we could opt to skip or filter to make rows align before appending columns. Here we will choose to simply drop the clashing column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_combined_by_column = dflow.append_columns([dflow_chicago.drop_columns(['Ward'])])\n", + "dflow_combined_by_column.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that the resultant schema has more columns in the first N records (N being the number of records in `dataflow` and the extra columns being the width of the schema of our reference dataflow, chicago, minus the `Ward` column). From the N+1th record onwards, we will only have a schema width matching that of the `Ward`-less chicago set.\n", + "\n", + "Why is this? As much as possible, the data from the reference dataflow(s) will be attached to existing rows in the target dataflow. If there are not enough rows in the target dataflow to attach to, we simply append them as new rows.\n", + "\n", + "Note that these are appends, not joins (for joins please reference [Join](join.ipynb)), so the append may not be logically correct, but will take effect as long as there are no schema clashes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ward-less data after we skip the first N rows\n", + "dflow_len = dflow.row_count\n", + "dflow_combined_by_column.skip(dflow_len).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `append_rows(dataflows)`\n", + "We can append data length-wise, which will only have the effect of adding new rows. No existing data will be changed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_spring = auto_read_file(path='../data/crime-spring.csv')\n", + "dflow_spring.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_chicago = auto_read_file(path='../data/chicago-aldermen-2015.csv')\n", + "dflow_chicago.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_combined_by_row = dflow.append_rows([dflow_chicago, dflow_spring])\n", + "dflow_combined_by_row.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that neither schema nor data has changed for the target dataflow.\n", + "\n", + "If we skip ahead, we will see our target dataflows' data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# chicago data\n", + "dflow_len = dflow.row_count\n", + "dflow_combined_by_row.skip(dflow_len).head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# crimes spring data\n", + "dflow_chicago_len = dflow_chicago.row_count\n", + "dflow_combined_by_row.skip(dflow_len + dflow_chicago_len).head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/assertions.ipynb b/work-with-data/dataprep/how-to-guides/assertions.ipynb new file mode 100644 index 00000000..b33a30ed --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/assertions.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/assertions.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Assertions\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Frequently, the data we work with while cleaning and preparing data is just a subset of the total data we will need to work with in production. It is also common to be working on a snapshot of a live dataset that is continuously updated and augmented.\n", + "\n", + "In these cases, some of the assumptions we make as part of our cleaning might turn out to be false. Columns that originally only contained numbers within a certain range might actually contain a wider range of values in later executions. These errors often result in either broken pipelines or bad data.\n", + "\n", + "Azure ML Data Prep supports creating assertions on data, which are evaluated as the pipeline is executed. These assertions enable us to verify that our assumptions on the data continue to be accurate and, when not, to handle failures in a clean way." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To demonstrate, we will load a dataset and then add some assertions based on what we can see in the first few rows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "\n", + "dflow = auto_read_file('../data/crime-dirty.csv')\n", + "dflow.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see there are latitude and longitude columns present in this dataset. By definition, these are constrained to specific ranges of values. We can assert that this is indeed the case so that if any records come through with invalid values, we detect them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import value\n", + "\n", + "dflow = dflow.assert_value('Latitude', (value <= 90) & (value >= -90), error_code='InvalidLatitude')\n", + "dflow = dflow.assert_value('Longitude', (value <= 180) & (value >= -180), error_code='InvalidLongitude')\n", + "dflow.keep_columns(['Latitude', 'Longitude']).get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Any assertion failures are represented as Errors in the resulting dataset. From the profile above, you can see that the Error Count for both of these columns is 1. We can use a filter to retrieve the error and see what value caused the assertion to fail." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import col\n", + "\n", + "dflow_error = dflow.filter(col('Latitude').is_error())\n", + "error = dflow_error.head(10)['Latitude'][0]\n", + "print(error.originalValue)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our assertion failed because we were not removing missing values from our data. At this point, we have two options: we can go back and edit our code to avoid this error in the first place or we can resolve it now. In this case, we will just filter these out." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import LocalFileOutput\n", + "dflow_clean = dflow.filter(~dflow['Latitude'].is_error())\n", + "dflow_clean.get_profile()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/auto-read-file.ipynb b/work-with-data/dataprep/how-to-guides/auto-read-file.ipynb new file mode 100644 index 00000000..2a12288c --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/auto-read-file.ipynb @@ -0,0 +1,189 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/auto-read-file.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Auto Read File\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has the ability to load different kinds of text files. The `auto_read_file` entry point can take any text based file (including excel, json and parquet) and auto-detect how to parse the file. It will also attempt to auto-detect the types of each column and apply type transformations to the columns it detects.\n", + "\n", + "The result will be a Dataflow object that has all the steps added that are required to read the given file(s) and convert their columns to the predicted types. No parameters are required beyond the file path or `FileDataSource` object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_auto = dprep.auto_read_file('../data/crime_multiple_separators.csv')\n", + "dflow_auto.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_auto1 = dprep.auto_read_file('../data/crime.xlsx')\n", + "dflow_auto1.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_auto2 = dprep.auto_read_file('../data/crime.parquet')\n", + "dflow_auto2.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the data, we can see that there are two empty columns either side of the 'Completed' column.\n", + "If we compare the dataframe to a few rows from the original file:\n", + "```\n", + "ID |CaseNumber| |Completed|\n", + "10140490 |HY329907| |Y|\n", + "10139776 |HY329265| |Y|\n", + "```\n", + "We can see that the `|`'s have disappeared in the dataframe. This is because `|` is a very common separator character in csv files, so `auto_read_file` guessed it was the column separator. For this data we actually want the `|`'s to remain and instead use space as the column separator.\n", + "\n", + "To achieve this we can use `detect_file_format`. It takes a file path or datasource object and gives back a `FileFormatBuilder` which has learnt some information about the supplied data.\n", + "This is what `auto_read_file` is using behind the scenes to 'learn' the contents of the given file and determine how to parse it. With the `FileFormatBuilder` we can take advantage of the intelligent learning aspect of `auto_read_file` but have the chance to modify some of the learnt information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ffb = dprep.detect_file_format('../data/crime_multiple_separators.csv')\n", + "ffb_2 = dprep.detect_file_format('../data/crime.xlsx')\n", + "ffb_3 = dprep.detect_file_format('../data/crime_fixed_width_file.txt')\n", + "ffb_4 = dprep.detect_file_format('../data/json.json')\n", + "\n", + "print(ffb.file_format)\n", + "print(ffb_2.file_format)\n", + "print(ffb_3.file_format)\n", + "print(type(ffb_4.file_format))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After calling `detect_file_format` we get a `FileFormatBuilder` that has had `learn` called on it. This means the `file_format` attribute will be populated with a `Properties` object, it contains all the information that was learnt about the file. As we can see above different file types have corresponding file_formats detected. \n", + "Continuing with our delimited example we can change any of these values and then call `ffb.to_dataflow()` to create a `Dataflow` that has the steps required to parse the datasource." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ffb.file_format.separator = ' '\n", + "dflow = ffb.to_dataflow()\n", + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The result is our desired dataframe with `|`'s included.\n", + "\n", + "If we refer back to the original data output by `auto_read_file`, the 'ID' column was also detected as numeric and converted to a number data type instead of remaining a string like in the data above.\n", + "We can perform type inference on our new dataflow using the `dataflow.builders` property. This property exposes different builders that can `learn` from a dataflow and `apply` the learning to produce a new dataflow, very similar to the pattern we used above for the `FileFormatBuilder`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ctb = dflow.builders.set_column_types()\n", + "ctb.learn()\n", + "ctb.conversion_candidates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After learning `ctb.conversion_candidates` has been populated with information about the inferred types for each column, it is possible for there to be multiple candidate types per column, in this example there is only one type for each column.\n", + "\n", + "The candidates look correct, we only want to convert `ID` to be an integer column, so applying this `ColumnTypesBuilder` should result in a Dataflow with our columns converted to their respective types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_converted = ctb.to_dataflow()\n", + "\n", + "df_converted = dflow_converted.to_pandas_dataframe()\n", + "df_converted" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/cache.ipynb b/work-with-data/dataprep/how-to-guides/cache.ipynb new file mode 100644 index 00000000..fd47cf0f --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/cache.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/cache.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cache\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A Dataflow can be cached as a file on your disk during a local run by calling `dflow_cached = dflow.cache(directory_path)`. Doing this will run all the steps in the Dataflow, `dflow`, and save the cached data to the specified `directory_path`. The returned Dataflow, `dflow_cached`, has a Caching Step added at the end. Any subsequent runs on on the Dataflow `dflow_cached` will reuse the cached data, and the steps before the Caching Step will not be run again.\n", + "\n", + "Caching avoids running transforms multiple times, which can make local runs more efficient. Here are common places to use Caching:\n", + "- after reading data from remote\n", + "- after expensive transforms, such as Sort\n", + "- after transforms that change the shape of data, such as Sampling, Filter and Summarize\n", + "\n", + "Caching Step will be ignored during scale-out run invoked by `to_spark_dataframe()`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will start by reading in a dataset and applying some transforms to the Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow = dflow.take_sample(probability=0.2, seed=7)\n", + "dflow = dflow.sort_asc(columns='Primary Type')\n", + "dflow = dflow.keep_columns(['ID', 'Case Number', 'Date', 'Primary Type'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we will choose a directory to store the cached data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "cache_dir = str(Path(os.getcwd(), 'dataflow-cache'))\n", + "cache_dir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now call `dflow.cache(directory_path)` to cache the Dataflow to your directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_cached = dflow.cache(directory_path=cache_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we will check steps in the `dflow_cached` to see that all of the previous steps were cached." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[s.step_type for s in dflow_cached._get_steps()]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also check the data stored in the cache directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "os.listdir(cache_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running against `dflow_cached` will reuse the cached data and skip running all of the previous steps again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_cached.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adding additional steps to `dflow_cached` will also reuse the cache data and skip running the steps prior to the Cache Step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_cached_take = dflow_cached.take(10)\n", + "dflow_cached_skip = dflow_cached.skip(10).take(10)\n", + "\n", + "df_cached_take = dflow_cached_take.to_pandas_dataframe()\n", + "df_cached_skip = dflow_cached_skip.to_pandas_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# shutil.rmtree will then clean up the cached data \n", + "import shutil\n", + "shutil.rmtree(path=cache_dir)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/column-manipulations.ipynb b/work-with-data/dataprep/how-to-guides/column-manipulations.ipynb new file mode 100644 index 00000000..bf1836f9 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/column-manipulations.ipynb @@ -0,0 +1,563 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/column-manipulations.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Column Manipulations\n", + "\n", + "Azure ML Data Prep has many methods for manipulating columns, including basic CUD operations and several other more complex manipulations.\n", + "\n", + "This notebook will focus primarily on data-agnostic operations. For all other column manipulation operations, we will link to their specific how-to guide.\n", + "\n", + "## Table of Contents\n", + "[ColumnSelector](#ColumnSelector)
\n", + "[add_column](#add_column)
\n", + "[append_columns](#append_columns)
\n", + "[drop_columns](#drop_columns)
\n", + "[duplicate_column](#duplicate_column)
\n", + "[fuzzy_group_column](#fuzzy_group_column)
\n", + "[keep_columns](#keep_columns)
\n", + "[map_column](#map_column)
\n", + "[new_script_column](#new_script_column)
\n", + "[rename_columns](#rename_columns)
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ColumnSelector\n", + "`ColumnSelector` is a Data Prep class that allows us to select columns by name. The idea is to be able to describe columns generally instead of explicitly, using a search term or regex expression, with various options.\n", + "\n", + "Note that a `ColumnSelector` does not represent the columns they match themselves, but the selector of the described columns. Therefore if we use the same `ColumnSelector` on two different dataflows, we may get different results depending on the columns of each dataflow.\n", + "\n", + "Column manipulations that can utilize `ColumnSelector` will be noted in their respective sections in this book." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All parameters to a `ColumnSelector` are shown here for completeness. We will use `keep_columns` in our example, which will keep only the columns in the dataflow that we tell it to keep.\n", + "\n", + "In the below example, we match all columns with the letter 'i'. Because we set `ignore_case` to false and `match_whole_word` to false, then any column that contains 'i' or 'I' will be selected." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ColumnSelector\n", + "column_selector = ColumnSelector(term=\"i\",\n", + " use_regex=False,\n", + " ignore_case=True,\n", + " match_whole_word=False,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we set `invert` to true, we get the opposite of what we matched earlier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"i\",\n", + " use_regex=False,\n", + " ignore_case=True,\n", + " match_whole_word=False,\n", + " invert=True)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we change the search term to 'I' and set case sensitivity to true, we get only the handful of columns that contain an upper case 'I'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"I\",\n", + " use_regex=False,\n", + " ignore_case=False,\n", + " match_whole_word=False,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And if we set `match_whole_word` to true, we get no results at all as there is no column called 'I'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"I\",\n", + " use_regex=False,\n", + " ignore_case=False,\n", + " match_whole_word=True,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, the `use_regex` flag dictates whether or not to treat the search term as a regex. It can be combined still with the other options.\n", + "\n", + "Here we define all columns that begin with the capital letter 'I'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"I.*\",\n", + " use_regex=True,\n", + " ignore_case=True,\n", + " match_whole_word=True,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## add_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [add-column-using-expression](add-column-using-expression.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## append_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [append-columns-and-rows](append-columns-and-rows.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## drop_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports dropping columns one or more columns in a single statement. Supports `ColumnSelector`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that there are 22 columns to begin with. We will now drop the 'ID' column and observe that the resulting dataflow contains 21 columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dropped = dflow.drop_columns('ID')\n", + "dflow_dropped.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also drop more than one column at once by passing a list of column names." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dropped = dflow_dropped.drop_columns(['IUCR', 'Description'])\n", + "dflow_dropped.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## duplicate_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports duplicating columns one or more columns in a single statement.\n", + "\n", + "Duplicated columns are placed to the immediate right of their source column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We decide which column(s) to duplicate and what the new column name(s) should be with a key value pairing (dictionary)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dupe = dflow.duplicate_column({'ID': 'ID2', 'IUCR': 'IUCR_Clone'})\n", + "dflow_dupe.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## fuzzy_group_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [fuzzy-group](fuzzy-group.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## keep_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports keeping one or more columns in a single statement. The resulting dataflow will contain only the column(s) specified; dropping all the other columns. Supports `ColumnSelector`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_keep = dflow.keep_columns(['ID', 'Date', 'Description'])\n", + "dflow_keep.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to `drop_columns`, we can pass a single column name or a list of them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_keep = dflow_keep.keep_columns('ID')\n", + "dflow_keep.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## map_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports string mapping. For a column containing strings, we can provide specific mappings from an original value to a new value, and then produce a new column that contains the mapped values.\n", + "\n", + "The mapped columns are placed to the immediate right of their source column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ReplacementsValue\n", + "replacements = [ReplacementsValue('THEFT', 'THEFT2'), ReplacementsValue('BATTERY', 'BATTERY!!!')]\n", + "dflow_mapped = dflow.map_column(column='Primary Type', \n", + " new_column_id='Primary Type V2',\n", + " replacements=replacements)\n", + "dflow_mapped.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## new_script_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [custom-python-transforms](custom-python-transforms.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## rename_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports renaming one or more columns in a single statement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We decide which column(s) to rename and what the new column name(s) should be with a key value pairing (dictionary)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_renamed = dflow.rename_columns({'ID': 'ID2', 'IUCR': 'IUCR_Clone'})\n", + "dflow_renamed.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb b/work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb new file mode 100644 index 00000000..bfc4e73f --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb @@ -0,0 +1,473 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/column-type-transforms.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Column Type Transforms\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When consuming a data set, it is highly useful to know as much as possible about the data. Column types can help you understand more about each column, and enable type-specific transformations later. This provides much more insight than treating all data as strings.\n", + "\n", + "In this notebook, you will learn about:\n", + "- [Built-in column types](#types)\n", + "- How to:\n", + " - [Convert to long (integer)](#long)\n", + " - [Convert to double (floating point or decimal number)](#double)\n", + " - [Convert to boolean](#boolean)\n", + " - [Convert to datetime](#datetime)\n", + "- [How to use `ColumnTypesBuilder` to get suggested column types and convert them](#builder)\n", + "- [How to convert column type for multiple columns if types are known](#multiple-columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv('../data/crime-winter.csv')\n", + "dflow = dflow.keep_columns(['Case Number', 'Date', 'IUCR', 'Arrest', 'Longitude', 'Latitude'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Built-in column types" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Currently, Data Prep supports the following column types: string, long (integer), double (floating point or decimal number), boolean, and datetime.\n", + "\n", + "In the previous step, a data set was read in as a Dataflow, with only a few interesting columns kept. We will use this Dataflow to explore column types throughout the notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the first few rows of the Dataflow, you can see that the columns contain different types of data. However, by looking at `dtypes`, you can see that `read_csv()` treats all columns as string columns.\n", + "\n", + "Note that `auto_read_file()` is a data ingestion function that infers column types. Learn more about it [here](./auto-read-file.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Converting to long (integer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"IUCR\" column should only contain integers. You can call `to_long` to convert the column type of \"IUCR\" to `FieldType.INTEGER`. If you look at the data profile ([learn more about data profiles](./data-profile.ipynb)), you will see numeric metrics populated for that column such as mean, variance, quantiles, etc. This is helpful for understanding the shape and distribution of numeric data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion = dflow.to_long('IUCR')\n", + "profile = dflow_conversion.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Converting to double (floating point or decimal number)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"Latitude\" and \"Longitude\" columns should only contain decimal numbers. You can call `to_double` to convert the column type of \"Latitude\" and \"Longitude\" to `FieldType.DECIMAL`. In the data profile, you will see numeric metrics populated for these columns as well. Note that after converting the column types, you can see that there are missing values in these columns. Metrics like this can be helpful for noticing issues with the data set." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion = dflow_conversion.to_number(['Latitude', 'Longitude'])\n", + "profile = dflow_conversion.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Converting to boolean" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"Arrest\" column should only contain boolean values. You can call `to_bool` to convert the column type of \"Arrest\" to `FieldType.BOOLEAN`.\n", + "\n", + "The `to_bool` function allows you to specify which values should map to `True` and which values should map to `False`. To do so, you can provide those values in an array as parameters `true_values` and `false_values`. Additionally, you can specify whether all other values should become `True`, `False` or Error by using the `mismatch_as` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion.to_bool('Arrest', \n", + " true_values=[1],\n", + " false_values=[0],\n", + " mismatch_as=dprep.MismatchAsOption.ASERROR).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the previous conversion, all the values in the \"Arrest\" column became `DataPrepError`, because 'FALSE' didn't match any of the `false_values` nor any of the `true_values`, and all the unmatched values were set to become errors. Let's try the conversion again with different `false_values`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion = dflow_conversion.to_bool('Arrest',\n", + " true_values=['1', 'TRUE'],\n", + " false_values=['0', 'FALSE'],\n", + " mismatch_as=dprep.MismatchAsOption.ASERROR)\n", + "dflow_conversion.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This time, all the string values 'FALSE' have been successfully converted to the boolean value `False`. Take another look at the data profile." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile = dflow_conversion.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"Date\" column should only contain datetime values. You can convert its column type to `FieldType.DateTime` using the `to_datetime` function. Typically, datetime formats can be confusing or inconsistent. Next, we will show you all the tools that can help correctly converting the column to `DateTime`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the first example, directly call `to_datetime` with only the column name. Data Prep will inspect the data in this column and learn what format should be used for the conversion.\n", + "\n", + "Note that if there is data in the column that cannot be converted to datetime, an Error value will be created in that cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion_date = dflow_conversion.to_datetime('Date')\n", + "dflow_conversion_date.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, we can see that '1/10/2016 11:00' was converted using the format `%m/%d/%Y %H:%M`.\n", + "\n", + "The data in this column is actually somewhat ambiguous. Should the dates be 'October 1' or 'January 10'? The function `to_datetime` determines that both are possible, but defaults to month-first (US format).\n", + "\n", + "If the data was supposed to be day-first, you can customize the conversion." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_alternate_conversion = dflow_conversion.to_datetime('Date', date_time_formats=['%d/%m/%Y %H:%M'])\n", + "dflow_alternate_conversion.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using `ColumnTypesBuilder`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can help you automatically detect what are the likely column types.\n", + "\n", + "You can call `dflow.builders.set_column_types()` to get a `ColumnTypesBuilder`. Then, calling `learn()` on it will trigger Data Prep to inspect the data in each column. As a result, you can see the suggested column types for each column (conversion candidates)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.set_column_types()\n", + "builder.learn()\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, Data Prep suggested the correct column types for \"Arrest\", \"Case Number\", \"Latitude\", and \"Longitude\".\n", + "\n", + "However, for \"Date\", it has suggested two possible date formats: month-first, or day-first. The ambiguity must be resolved before you complete the conversion. To use the month-first format, you can call `builder.ambiguous_date_conversions_keep_month_day()`. Otherwise, call `builder.ambiguous_date_conversions_keep_day_month()`. Note that if there were multiple datetime columns with ambiguous date conversions, calling one of these functions will apply the resolution to all of them.\n", + "\n", + "If you want to skip all the ambiguous date column conversions instead, you can call: `builder.ambiguous_date_conversions_drop()`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.ambiguous_date_conversions_keep_month_day()\n", + "builder.conversion_candidates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The conversion candidate for \"IUCR\" is currently `FieldType.INTEGER`. If you know that \"IUCR\" should be floating point (called `FieldType.DECIMAL`), you can tweak the builder to change the conversion candidate for that specific column. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.conversion_candidates['IUCR'] = dprep.FieldType.DECIMAL\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case we are happy with \"IUCR\" as `FieldType.INTEGER`. So we set it back. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.conversion_candidates['IUCR'] = dprep.FieldType.INTEGER\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you are happy with the conversion candidates, you can complete the conversion by calling `builder.to_dataflow()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_converion_using_builder = builder.to_dataflow()\n", + "dflow_converion_using_builder.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Convert column types for multiple columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you already know the column types, you can simply call `dflow.set_column_types()`. This function allows you to specify multiple columns, and the desired column type for each one. Here's how you can convert all five columns at once.\n", + "\n", + "Note that `set_column_types` only supports a subset of column type conversions. For example, we cannot specify the true/false values for a boolean conversion, so the results of this operation is incorrect for the \"Arrest\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion_using_set = dflow.set_column_types({\n", + " 'IUCR': dprep.FieldType.INTEGER,\n", + " 'Latitude': dprep.FieldType.DECIMAL,\n", + " 'Longitude': dprep.FieldType.DECIMAL,\n", + " 'Arrest': dprep.FieldType.BOOLEAN,\n", + " 'Date': (dprep.FieldType.DATE, ['%m/%d/%Y %H:%M']),\n", + "})\n", + "dflow_conversion_using_set.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb b/work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb new file mode 100644 index 00000000..30dcb113 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/custom-python-transforms.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Custom Python Transforms\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There will be scenarios when the easiest thing for you to do is just to write some Python code. This SDK provides three extension points that you can use.\n", + "\n", + "1. New Script Column\n", + "2. New Script Filter\n", + "3. Transform Partition\n", + "\n", + "Each of these are supported in both the scale-up and the scale-out runtime. A key advantage of using these extension points is that you don't need to pull all of the data in order to create a dataframe. Your custom python code will be run just like other transforms, at scale, by partition, and typically in parallel." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial data prep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We start by loading crime data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "col = dprep.col\n", + "\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We trim the dataset down and keep only the columns we are interested in. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.keep_columns(['Case Number','Primary Type', 'Description', 'Latitude', 'Longitude'])\n", + "dflow = dflow.replace_na(columns=['Latitude', 'Longitude'], custom_na_list='')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We look for null values using a filter. We found some, so now we'll look at a way to fill these missing values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.filter(col('Latitude').is_null()).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Transform Partition" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to replace all null values with a 0, so we decide to use a handy pandas function. This code will be run by partition, not on all of the dataset at a time. This means that on a large dataset, this code may run in parallel as the runtime processes the data partition by partition." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pt_dflow = dflow\n", + "dflow = pt_dflow.transform_partition(\"\"\"\n", + "def transform(df, index):\n", + " df['Latitude'].fillna('0',inplace=True)\n", + " df['Longitude'].fillna('0',inplace=True)\n", + " return df\n", + "\"\"\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Transform Partition With File" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Being able to use any python code to manipulate your data as a pandas DataFrame is extremely useful for complex and specific data operations that DataPrep doesn't handle natively. Though the code isn't very testable unfortunately, it's just sitting inside a string.\n", + "So to improve code testability and ease of script writing there is another transform_partiton interface that takes the path to a python script which must contain a function matching the 'transform' signature defined above.\n", + "\n", + "The `script_path` argument should be a relative path to ensure Dataflow portability. Here `map_func.py` contains the same code as in the previous example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = pt_dflow.transform_partition_with_file('../data/map_func.py')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## New Script Column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to create a new column that has both the latitude and longitude. We can achieve it easily using [Data Prep expression](./add-column-using-expression.ipynb), which is faster in execution. Alternatively, We can do this using Python code by using the `new_script_column()` method on the dataflow. Note that we use custom Python code here for demo purpose only. In practise, you should always use Data Prep native functions as a preferred method, and use custom Python code when the functionality is not available in Data Prep. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.new_script_column(new_column_name='coordinates', insert_after='Longitude', script=\"\"\"\n", + "def newvalue(row):\n", + " return '(' + row['Latitude'] + ', ' + row['Longitude'] + ')'\n", + "\"\"\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## New Script Filter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we want to filter the dataset down to only the crimes that incurred over $300 in loss. We can build a Python expression that returns True if we want to keep the row, and False to drop the row." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.new_script_filter(\"\"\"\n", + "def includerow(row):\n", + " val = row['Description']\n", + " return 'OVER $ 300' in val\n", + "\"\"\")\n", + "dflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/data-ingestion.ipynb b/work-with-data/dataprep/how-to-guides/data-ingestion.ipynb new file mode 100644 index 00000000..fc82dbda --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/data-ingestion.ipynb @@ -0,0 +1,978 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/data-ingestion.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data Ingestion\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has the ability to load different types of input data. You can use auto-reading functionality to detect the type of a file, or directly specify a file type and its parameters." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "[Read Lines](#lines)
\n", + "[Read CSV](#csv)
\n", + "[Read Compressed CSV](#compressed-csv)
\n", + "[Read Excel](#excel)
\n", + "[Read Fixed Width Files](#fixed-width)
\n", + "[Read Parquet](#parquet)
\n", + "[Read Part Files Using Globbing](#globbing)
\n", + "[Read JSON](#json)
\n", + "[Read SQL](#sql)
\n", + "[Read PostgreSQL](#postgresql)
\n", + "[Read From Azure Blob](#azure-blob)
\n", + "[Read From ADLS](#adls)
\n", + "[Read Pandas DataFrame](#pandas-df)
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Lines" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One of the simplest ways to read data using Data Prep is to just read it as text lines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_lines(path='../data/crime.txt')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With ingestion done, you can go ahead and start prepping the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read CSV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When reading delimited files, the only required parameter is `path`. Other parameters (e.g. separator, encoding, whether to use headers, etc.) are available to modify default behavior.\n", + "In this case, you can read a file by specifying only its location, then retrieve the first 5 rows to evaluate the result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_duplicate_headers = dprep.read_csv(path='../data/crime_duplicate_headers.csv')\n", + "dflow_duplicate_headers.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the result, you can see that the delimiter and encoding were correctly detected. Column headers were also detected. However, the first line seems to be a duplicate of the column headers. One of the parameters is a number of lines to skip from the files being read. You can use this to filter out the duplicate line." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skip_headers = dprep.read_csv(path='../data/crime_duplicate_headers.csv', skip_rows=1)\n", + "dflow_skip_headers.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the data set contains the correct headers and the extraneous row has been skipped by `read_csv`. Next, look at the data types of the columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skip_headers.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Unfortunately, all of the columns came back as strings. This is because, by default, Data Prep will not change the type of the data. Since the data source is a text file, all values are kept as strings. In this case, however, numeric columns should be parsed as numbers. To do this, set the `infer_column_types` parameter to `True`, which will trigger type inference to be performed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_inferred_types = dprep.read_csv(path='../data/crime_duplicate_headers.csv',\n", + " skip_rows=1,\n", + " infer_column_types=True)\n", + "dflow_inferred_types.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now several of the columns were correctly detected as numbers and their `FieldType` is Decimal.\n", + "\n", + "With ingestion done, the data set is ready to start preparing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow_inferred_types.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Compressed CSV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also read delimited files compressed in an archive. The `archive_options` parameter specifies the type of archive and glob pattern of entries in the archive.\n", + "\n", + "At this moment, only reading from ZIP archives is supported." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ArchiveOptions, ArchiveType\n", + "\n", + "dflow = dprep.read_csv(path='../data/crime.zip',\n", + " archive_options=ArchiveOptions(archive_type=ArchiveType.ZIP, entry_glob='*10-20.csv'))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Excel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also load Excel files using the `read_excel` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_default_sheet = dprep.read_excel(path='../data/crime.xlsx')\n", + "dflow_default_sheet.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, the first sheet of the Excel document has been loaded. You could achieve the same result by specifying the name of the desired sheet explicitly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_second_sheet = dprep.read_excel(path='../data/crime.xlsx', sheet_name='Sheet2')\n", + "dflow_second_sheet.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the table in the second sheet had headers as well as three empty rows, so you can modify the arguments accordingly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skipped_rows = dprep.read_excel(path='../data/crime.xlsx',\n", + " sheet_name='Sheet2',\n", + " use_column_headers=True,\n", + " skip_rows=3)\n", + "dflow_skipped_rows.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow_skipped_rows.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Fixed Width Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For fixed-width files, you can specify a list of offsets. The first column is always assumed to start at offset 0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_fixed_width = dprep.read_fwf('../data/crime.txt', offsets=[8, 17, 26, 33, 56, 58, 74])\n", + "dflow_fixed_width.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the data, you can see that the first row was used as headers. In this particular case, however, there are no headers in the file, so the first row should be treated as data.\n", + "\n", + "Passing in `PromoteHeadersMode.NONE` to the `header` keyword argument avoids header detection and gets the correct data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_no_headers = dprep.read_fwf('../data/crime.txt',\n", + " offsets=[8, 17, 26, 33, 56, 58, 74],\n", + " header=dprep.PromoteHeadersMode.NONE)\n", + "dflow_no_headers.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow_no_headers.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Parquet" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has two different methods for reading data stored as Parquet.\n", + "\n", + "Currently, both methods require the `pyarrow` package to be installed in your Python environment. This can be done via `pip install azureml-dataprep[parquet]`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Parquet File" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For reading single `.parquet` files, or a folder full of only Parquet files, use `read_parquet_file`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_parquet_file('../data/crime.parquet')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Parquet data is explicitly typed so no type inference is needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Parquet Dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A Parquet Dataset is different from a Parquet file in that it could be a folder containing a number of Parquet files within a complex directory structure. It may have a hierarchical structure that partitions the data by value of a column. These more complex forms of Parquet data are commonly produced by Spark/HIVE.\n", + "\n", + "For these more complex data sets, you can use `read_parquet_dataset`, which uses pyarrow to handle complex Parquet layouts. This will also handle single Parquet files, though these are better read using `read_parquet_file`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_parquet_dataset('../data/parquet_dataset')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above data was partitioned by the value of the `Arrest` column. It is a boolean column in the original crime0 data set and hence was partitioned by `Arrest=true` and `Arrest=false`.\n", + "\n", + "The directory structure is printed below for clarity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "for path, dirs, files in os.walk('../data/parquet_dataset'):\n", + " level = path.replace('../data/parquet_dataset', '').count(os.sep)\n", + " indent = ' ' * (level)\n", + " print(indent + os.path.basename(path) + '/')\n", + " fileindent = ' ' * (level + 1)\n", + " for f in files:\n", + " print(fileindent + f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Part Files Using Globbing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports globbing, which allows you to read partitioned files (or any other type of files) in a folder. Globbing is supported by all of the read transformations that take in file paths, such as `read_csv`, `read_lines`, etc. By specifying `../data/crime_partfiles/part-*` in the path, we will read all files start with `part-`in `crime_partfiles` folder and return them in one Dataflow. [`auto_read_file`](./auto-read-file.ipynb) will detect column types of your part files and parse them automatically." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_partfiles = dprep.auto_read_file(path='../data/crime_partfiles/part-*')\n", + "dflow_partfiles.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read JSON" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also load JSON files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_json = dprep.read_json(path='../data/json.json')\n", + "dflow_json.head(15)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you use `read_json`, Data Prep will attempt to extract data from the file into a table. You can also control the file encoding Data Prep should use as well as whether Data Prep should flatten nested JSON arrays.\n", + "\n", + "Choosing the option to flatten nested arrays could result in a much larger number of rows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_flat_arrays = dprep.read_json(path='../data/json.json', flatten_nested_arrays=True)\n", + "dflow_flat_arrays.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read SQL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also fetch data from SQL servers. Currently, only Microsoft SQL Server is supported.\n", + "\n", + "To read data from a SQL server, first create a data source object that contains the connection information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "secret = dprep.register_secret(value=\"dpr3pTestU$er\", id=\"dprepTestUser\")\n", + "ds = dprep.MSSQLDataSource(server_name=\"dprep-sql-test.database.windows.net\",\n", + " database_name=\"dprep-sql-test\",\n", + " user_name=\"dprepTestUser\",\n", + " password=secret)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the password parameter of `MSSQLDataSource` accepts a Secret object. You can get a Secret object in two ways:\n", + "1. Register the secret and its value with the execution engine.\n", + "2. Create the secret with just an id (useful if the secret value was already registered in the execution environment).\n", + "\n", + "Now that you have created a data source object, you can proceed to read data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_sql(ds, \"SELECT top 100 * FROM [SalesLT].[Product]\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow.to_pandas_dataframe()\n", + "df.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read PostgreSQL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also fetch data from Azure PostgreSQL servers.\n", + "\n", + "To read data from a PostgreSQL server, first create a data source object that contains the connection information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "secret = dprep.register_secret(value=\"dpr3pTestU$er\", id=\"dprepPostgresqlUser\")\n", + "ds = dprep.PostgreSQLDataSource(server_name=\"dprep-postgresql-test.postgres.database.azure.com\",\n", + " database_name=\"dprep-postgresql-testdb\",\n", + " user_name=\"dprepPostgresqlReadOnlyUser@dprep-postgresql-test\",\n", + " password=secret)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the password parameter of `PostgreSQLDataSource` accepts a Secret object as well.\n", + "Now that you have created a PostgreSQL data source object, you can proceed to read data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_postgresql(ds, \"SELECT * FROM public.people\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read from Azure Blob" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can read files stored in public Azure Blob by directly passing a file url. To read file from a protected Blob, pass SAS (Shared Access Signature) URI with both resource URI and SAS token in the path." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv', skip_rows=1)\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read from ADLS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two ways the Data Prep API can acquire the necessary OAuth token to access Azure DataLake Storage:\n", + "1. Retrieve the access token from a recent login session of the user's [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) login.\n", + "2. Use a ServicePrincipal (SP) and a certificate as a secret." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using Access Token from a recent Azure CLI session" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On your local machine, run the following command:\n", + "```\n", + "az login\n", + "```\n", + "If your user account is a member of more than one Azure tenant, you need to specify the tenant, either in the AAD url hostname form '.onmicrosoft.com' or the tenantId GUID. The latter can be retrieved as follows:\n", + "```\n", + "az account show --query tenantId\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "dflow = read_csv(path = DataLakeDataSource(path='adl://dpreptestfiles.azuredatalakestore.net/farmers-markets.csv', tenant='microsoft.onmicrosoft.com'))\n", + "head = dflow.head(5)\n", + "head\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a ServicePrincipal via Azure CLI" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A ServicePrincipal and the corresponding certificate can be created via [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest).\n", + "This particular SP is configured as Reader, with its scope reduced to just the ADLS account 'dpreptestfiles'.\n", + "```\n", + "az account set --subscription \"Data Wrangling development\"\n", + "az ad sp create-for-rbac -n \"SP-ADLS-dpreptestfiles\" --create-cert --role reader --scopes /subscriptions/35f16a99-532a-4a47-9e93-00305f6c40f2/resourceGroups/dpreptestfiles/providers/Microsoft.DataLakeStore/accounts/dpreptestfiles\n", + "```\n", + "This command emits the appId and the path to the certificate file (usually in the home folder). The .crt file contains both the public certificate and the private key in PEM format.\n", + "\n", + "Extract the thumbprint with:\n", + "```\n", + "openssl x509 -in adls-dpreptestfiles.crt -noout -fingerprint\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configure ADLS Account for ServicePrincipal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To configure the ACL for the ADLS filesystem, use the objectId of the user or, here, ServicePrincipal:\n", + "```\n", + "az ad sp show --id \"8dd38f34-1fcb-4ff9-accd-7cd60b757174\" --query objectId\n", + "```\n", + "Configure Read and Execute access for the ADLS file system. Since the underlying HDFS ACL model doesn't support inheritance, folders and files need to be ACL-ed individually.\n", + "```\n", + "az dls fs access set-entry --account dpreptestfiles --acl-spec \"user:e37b9b1f-6a5e-4bee-9def-402b956f4e6f:r-x\" --path /\n", + "az dls fs access set-entry --account dpreptestfiles --acl-spec \"user:e37b9b1f-6a5e-4bee-9def-402b956f4e6f:r--\" --path /farmers-markets.csv\n", + "```\n", + "\n", + "References:\n", + "- [az ad sp](https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest)\n", + "- [az dls fs access](https://docs.microsoft.com/en-us/cli/azure/dls/fs/access?view=azure-cli-latest)\n", + "- [ACL model for ADLS](https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/data-lake-store/data-lake-store-access-control.md)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "certThumbprint = 'C2:08:9D:9E:D1:74:FC:EB:E9:7E:63:96:37:1C:13:88:5E:B9:2C:84'\n", + "certificate = ''\n", + "with open('../data/adls-dpreptestfiles.crt', 'rt', encoding='utf-8') as crtFile:\n", + " certificate = crtFile.read()\n", + "\n", + "servicePrincipalAppId = \"8dd38f34-1fcb-4ff9-accd-7cd60b757174\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Acquire an OAuth Access Token" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use the adal package (via: `pip install adal`) to create an authentication context on the MSFT tenant and acquire an OAuth access token. Note that for ADLS, the `resource` in the token request must be for 'datalake.azure.net', which is different from most other Azure resources." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import adal\n", + "from azureml.dataprep.api.datasources import DataLakeDataSource\n", + "\n", + "ctx = adal.AuthenticationContext('https://login.microsoftonline.com/microsoft.onmicrosoft.com')\n", + "token = ctx.acquire_token_with_client_certificate('https://datalake.azure.net/', servicePrincipalAppId, certificate, certThumbprint)\n", + "dflow = dprep.read_csv(path = DataLakeDataSource(path='adl://dpreptestfiles.azuredatalakestore.net/crime-spring.csv', accessToken=token['accessToken']))\n", + "dflow.to_pandas_dataframe().head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Pandas DataFrame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are situations where you may already have some data in the form of a pandas DataFrame.\n", + "The steps taken to get to this DataFrame may be non-trivial or not easy to convert to Data Prep Steps. The `read_pandas_dataframe` reader can take a DataFrame and use it as the data source for a Dataflow.\n", + "\n", + "You can pass in a path to a directory (that doesn't exist yet) for Data Prep to store the contents of the DataFrame; otherwise, a temporary directory will be made in the system's temp folder. The files written to this directory will be named `part-00000` and so on; they are written out in Data Prep's internal row-based file format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_excel(path='../data/crime.xlsx')\n", + "dflow = dflow.drop_columns(columns=['Column1'])\n", + "df = dflow.to_pandas_dataframe()\n", + "df.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After loading in the data you can now do `read_pandas_dataframe`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import shutil\n", + "cache_dir = 'dflow_df'\n", + "shutil.rmtree(cache_dir, ignore_errors=True)\n", + "dflow_df = dprep.read_pandas_dataframe(df, cache_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_df.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/data-profile.ipynb b/work-with-data/dataprep/how-to-guides/data-profile.ipynb new file mode 100644 index 00000000..97b42ee1 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/data-profile.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/data-profile.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data Profile\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A DataProfile collects summary statistics on each column of the data produced by a Dataflow. This can be used to:\n", + "- Understand the input data.\n", + "- Determine which columns might need further preparation.\n", + "- Verify that data preparation operations produced the desired result.\n", + "\n", + "`Dataflow.get_profile()` executes the Dataflow, calculates profile information, and returns a newly constructed DataProfile." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.auto_read_file('../data/crime-spring.csv')\n", + "\n", + "profile = dflow.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A DataProfile contains a collection of ColumnProfiles, indexed by column name. Each ColumnProfile has attributes for the calculated column statistics. For non-numeric columns, profiles include only basic statistics like min, max, and error count. For numeric columns, profiles also include statistical moments and estimated quantiles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['Beat']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also extract and filter data from profiles by using list and dict comprehensions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "variances = [c.variance for c in profile.columns.values() if c.variance]\n", + "variances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_types = {c.name: c.type for c in profile.columns.values()}\n", + "column_types" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If a column has fewer than a thousand unique values, its ColumnProfile contains a summary of values with their respective counts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['Primary Type'].value_counts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Numeric ColumnProfiles include an estimated histogram of the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['District'].histogram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To configure the number of bins in the histogram, you can pass an integer as the `number_of_histogram_bins` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile_more_bins = dflow.get_profile(number_of_histogram_bins=5)\n", + "profile_more_bins.columns['District'].histogram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For columns containing data of mixed types, the ColumnProfile also provides counts of each type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['X Coordinate'].type_counts" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/datastore.ipynb b/work-with-data/dataprep/how-to-guides/datastore.ipynb new file mode 100644 index 00000000..d6c99a0e --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/datastore.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/datastore.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reading from and Writing to Datastores" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A datastore is a reference that points to an Azure storage service like a blob container for example. It belongs to a workspace and a workspace can have many datastores.\n", + "\n", + "A data path points to a path on the underlying Azure storage service the datastore references. For example, given a datastore named `blob` that points to an Azure blob container, a data path can point to `/test/data/titanic.csv` in the blob container." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read data from Datastore" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports reading data from a `Datastore` or a `DataPath` or a `DataReference`. \n", + "\n", + "Passing in a datastore into all the `read_*` methods of Data Prep will result in reading everything in the underlying Azure storage service. To read a specific folder or file in the underlying storage, you have to pass in a data reference." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace, Datastore\n", + "from azureml.data.datapath import DataPath\n", + "\n", + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, get or create a workspace. Feel free to replace `subscription_id`, `resource_group`, and `workspace_name` with other values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subscription_id = '35f16a99-532a-4a47-9e93-00305f6c40f2'\n", + "resource_group = 'DataStoreTest'\n", + "workspace_name = 'dataprep-centraleuap'\n", + "\n", + "workspace = Workspace(subscription_id=subscription_id, resource_group=resource_group, workspace_name=workspace_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "workspace.datastores" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can now read a crime data set from the datastore. If you are using your own workspace, the `crime0-10.csv` will not be there by default. You will have to upload the data to the datastore yourself." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='dataprep_blob')\n", + "dflow = dprep.read_csv(path=datastore.path('crime0-10.csv'))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also read from an Azure SQL database. To do that, you will first get an Azure SQL database datastore instance and pass it to Data Prep for reading." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='test_sql')\n", + "dflow_sql = dprep.read_sql(data_source=datastore, query='SELECT * FROM team')\n", + "dflow_sql.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also read from a PostgreSQL database. To do that, you will first get a PostgreSQL database datastore instance and pass it to Data Prep for reading." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='postgre_test')\n", + "dflow_sql = dprep.read_postgresql(data_source=datastore, query='SELECT * FROM public.people')\n", + "dflow_sql.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write data to Datastore" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also write a dataflow to a datastore. The code below will write the file you read in earlier to the folder in the datastore." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dest_datastore = Datastore(workspace, 'dataprep_blob_key')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.write_to_csv(directory_path=dest_datastore.path('output/crime0-10')).run_local()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you can read all the files in the `dataprep_adls` datastore which references an Azure Data Lake store." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='dataprep_adls')\n", + "dflow_adls = dprep.read_csv(path=DataPath(datastore, path_on_datastore='/input/crime0-10.csv'))\n", + "dflow_adls.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb b/work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb new file mode 100644 index 00000000..5d1db4ee --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/derive-column-by-example.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Derive Column By Example\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One of the more advanced tools in Data Prep is the ability to derive columns by providing examples of desired results and letting Data Prep generate code to achieve the intended derivation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv(path = '../data/crime-spring.csv')\n", + "df = dflow.head(5)\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, this is a fairly simple file, but let's assume that we need to be able to join this with a dataset where date and time come in a format 'Apr 4, 2016 | 10PM-12AM'.\n", + "\n", + "Let's wrangle the data into the shape we need." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.derive_column_by_example(source_columns = ['Date'], new_column_name = 'date_timerange')\n", + "builder.add_example(source_data = df.iloc[0], example_value = 'Apr 4, 2016 10PM-12AM')\n", + "builder.preview() # will preview top 10 rows" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The code above first creates a builder for the derived column by providing an array of source columns to consider ('DATE') and name for the new column to be added.\n", + "\n", + "Then, we provide the first example by passing in the first row (index 0) of the DataFrame printed above and giving an expected value for the derived column.\n", + "\n", + "Finally, we call `builder.preview()` and observe the derived column next to the source column." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Everything looks good here. However, we just noticed that it's not quite what we wanted. We forgot to separate date and time range by '|' to generate the format we need.\n", + "\n", + "To fix that, we will add another example. This time, instead of passing in a row from the preview, we just construct a dictionary of column name to value for the source_data parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.add_example(source_data = {'Date': '4/15/2016 10:00'}, example_value = 'Apr 15, 2016 | 10AM-12PM')\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This clearly had negative effects, as now the only rows that have any values in derived column are the ones that match exactly with the examples we have provided.\n", + "\n", + "Let's look at the examples:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "examples = builder.list_examples()\n", + "examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we can see that we have provided inconsistent examples. To fix the issue, we need to replace the first example with a correct one (including '|' between date and time).\n", + "\n", + "We can achieve this by deleting examples that are incorrect (by either passing in example_row from examples DataFrame, or by just passing in example_id value) and then adding new modified examples back." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.delete_example(example_id = -1)\n", + "builder.add_example(examples.iloc[0], 'Apr 4, 2016 | 10PM-12AM')\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now this looks correct and we can finally call to_dataflow() on the builder, which would return a dataflow with the desired derived columns added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = builder.to_dataflow()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/external-references.ipynb b/work-with-data/dataprep/how-to-guides/external-references.ipynb new file mode 100644 index 00000000..579d9087 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/external-references.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/external-references.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# External References\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to opening existing Dataflows in code and modifying them, it is also possible to create and persist Dataflows that reference another Dataflow that has been persisted to a .dprep file. In this case, executing this Dataflow will load and execute the referenced Dataflow dynamically, and then execute the steps in the referencing Dataflow." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To demonstrate, we will create a Dataflow that loads and transforms some data. After that, we will persist this Dataflow to disk. To learn more about saving and opening .dprep files, see: [Opening and Saving Dataflows](./open-save-dataflows.ipynb)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "import tempfile\n", + "import os\n", + "\n", + "dflow = dprep.auto_read_file('../data/crime.txt')\n", + "dflow = dflow.drop_errors(['Column7', 'Column8', 'Column9'], dprep.ColumnRelationship.ANY)\n", + "dflow_path = os.path.join(tempfile.gettempdir(), 'package.dprep')\n", + "dflow.save(dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have a .dprep file, we can create a new Dataflow that references it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_new = dprep.Dataflow.reference(dprep.ExternalReference(dflow_path))\n", + "dflow_new.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When executed, the new Dataflow returns the same results as the one we saved to the .dprep file. Since this reference is resolved on execution, updating the referenced Dataflow results in the changes being visible when re-executing the referencing Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.take(5)\n", + "dflow.save(dflow_path)\n", + "\n", + "dflow_new.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, even though we did not modify `dflow_new`, it now returns only 5 records, as the referenced Dataflow was updated with the result from `dflow.take(5)`." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/filtering.ipynb b/work-with-data/dataprep/how-to-guides/filtering.ipynb new file mode 100644 index 00000000..545fd3ca --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/filtering.ipynb @@ -0,0 +1,220 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/filtering.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Filtering\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep has the ability to filter out columns or rows using `Dataflow.drop_columns` or `Dataflow.filter`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# initial set up\n", + "import azureml.dataprep as dprep\n", + "from datetime import datetime\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filtering columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter columns, use `Dataflow.drop_columns`. This method takes a list of columns to drop or a more complex argument called `ColumnSelector`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering columns with list of strings" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example, `drop_columns` takes a list of strings. Each string should exactly match the desired column to drop." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.drop_columns(['ID', 'Location Description', 'Ward', 'Community Area', 'FBI Code'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering columns with regex" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, a `ColumnSelector` can be used to drop columns that match a regex expression. In this example, we drop all the columns that match the expression `Column*|.*longitud|.*latitude`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.drop_columns(dprep.ColumnSelector('Column*|.*longitud|.*latitude', True, True))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filtering rows" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter rows, use `DataFlow.filter`. This method takes an `Expression` as an argument, and returns a new dataflow with the rows in which the expression evaluates to `True`. Expressions are built by indexing the `Dataflow` with a column name (`dataflow['myColumn']`) and regular operators (`>`, `<`, `>=`, `<=`, `==`, `!=`)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering rows with simple expressions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Index into the Dataflow specifying the column name as a string argument `dataflow['column_name']` and in combination with one of the following standard operators `>, <, >=, <=, ==, !=`, build an expression such as `dataflow['District'] > 9`. Finally, pass the built expression into the `Dataflow.filter` function.\n", + "\n", + "In this example, `dataflow.filter(dataflow['District'] > 9)` returns a new dataflow with the rows in which the value of \"District\" is greater than '10' \n", + "\n", + "*Note that \"District\" is first converted to numeric, which allows us to build an expression comparing it against other numeric values.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_number(['District'])\n", + "dflow = dflow.filter(dflow['District'] > 9)\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering rows with complex expressions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter using complex expressions, combine one or more simple expressions with the operators `&`, `|`, and `~`. Please note that the precedence of these operators is lower than that of the comparison operators; therefore, you'll need to use parentheses to group clauses together. \n", + "\n", + "In this example, `Dataflow.filter` returns a new dataflow with the rows in which \"Primary Type\" equals 'DECEPTIVE PRACTICE' and \"District\" is greater than or equal to '10'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_number(['District'])\n", + "dflow = dflow.filter((dflow['Primary Type'] == 'DECEPTIVE PRACTICE') & (dflow['District'] >= 10))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to filter rows combining more than one expression builder to create a nested expression.\n", + "\n", + "*Note that `'Date'` and `'Updated On'` are first converted to datetime, which allows us to build an expression comparing it against other datetime values.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_datetime(['Date', 'Updated On'], ['%Y-%m-%d %H:%M:%S'])\n", + "dflow = dflow.to_number(['District', 'Y Coordinate'])\n", + "comparison_date = datetime(2016,4,13)\n", + "dflow = dflow.filter(\n", + " ((dflow['Date'] > comparison_date) | (dflow['Updated On'] > comparison_date))\n", + " | ((dflow['Y Coordinate'] > 1900000) & (dflow['District'] > 10.0)))\n", + "dflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb b/work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb new file mode 100644 index 00000000..53f309a7 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/fuzzy-group.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fuzzy Grouping\n", + "\n", + "Unprepared data often represents the same entity with multiple values; examples include different spellings, varying capitalizations, and abbreviations. This is common when working with data gathered from multiple sources or through human input. One way to canonicalize and reconcile these variants is to use Data Prep's fuzzy_group_column (also known as \"text clustering\") functionality.\n", + "\n", + "Data Prep inspects a column to determine clusters of similar values. A new column is added in which clustered values are replaced with the canonical value of its cluster, thus significantly reducing the number of distinct values. You can control the degree of similarity required for values to be clustered together, override canonical form, and set clusters if automatic clustering did not provide the desired results.\n", + "\n", + "Let's explore the capabilities of `fuzzy_group_column` by first reading in a dataset and inspecting it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_json(path='../data/json.json')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see above, the column `inspections.business.city` contains several forms of the city name \"San Francisco\".\n", + "Let's add a column with values replaced by the automatically detected canonical form. To do so call fuzzy_group_column() on an existing Dataflow:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_clean = dflow.fuzzy_group_column(source_column='inspections.business.city',\n", + " new_column_name='city_grouped',\n", + " similarity_threshold=0.8,\n", + " similarity_score_column_name='similarity_score')\n", + "dflow_clean.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The arguments `source_column` and `new_column_name` are required, whereas the others are optional.\n", + "If `similarity_threshold` is provided, it will be used to control the required similarity level for the values to be grouped together.\n", + "If `similarity_score_column_name` is provided, a second new column will be added to show similarity score between every pair of original and canonical values.\n", + "\n", + "In the resulting data set, you can see that all the different variations of representing \"San Francisco\" in the data were normalized to the same string, \"San Francisco\".\n", + "\n", + "But what if you want more control over what gets grouped, what doesn't, and what the canonical value should be? \n", + "\n", + "To get more control over grouping, canonical values, and exceptions, you need to use the `FuzzyGroupBuilder` class.\n", + "Let's see what it has to offer below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.fuzzy_group_column(source_column='inspections.business.city',\n", + " new_column_name='city_grouped',\n", + " similarity_threshold=0.8,\n", + " similarity_score_column_name='similarity_score')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# calling learn() to get fuzzy groups\n", + "builder.learn()\n", + "builder.groups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here you can see that `fuzzy_group_column` detected one group with four values that all map to \"San Francisco\" as the canonical value.\n", + "You can see the effects of changing the similarity threshold next:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.similarity_threshold = 0.9\n", + "builder.learn()\n", + "builder.groups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that you are using a similarity threshold of `0.9`, two distinct groups of values are generated.\n", + "\n", + "Let's tweak some of the detected groups before completing the builder and getting back the Dataflow with the resulting fuzzy grouped column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.similarity_threshold = 0.8\n", + "builder.learn()\n", + "groups = builder.groups\n", + "groups" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# change the canonical value for the first group\n", + "groups[0]['canonicalValue'] = 'SANFRAN'\n", + "duplicates = groups[0]['duplicates']\n", + "# remove the last duplicate value from the cluster\n", + "duplicates = duplicates[:-1]\n", + "# assign modified duplicate array back\n", + "groups[0]['duplicates'] = duplicates\n", + "# assign modified groups back to builder\n", + "builder.groups = groups\n", + "builder.groups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, the canonical value is modified to be used for the single fuzzy group and removed 'S.F.' from this group's duplicates list.\n", + "\n", + "You can mutate the copy of the `groups` list from the builder (be careful to keep the structure of objects inside this list). After getting the desired groups in the list, you can update the builder with it.\n", + "\n", + "Now you can get a dataflow with the FuzzyGroup step in it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_clean = builder.to_dataflow()\n", + "\n", + "df = dflow_clean.to_pandas_dataframe()\n", + "df" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb b/work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb new file mode 100644 index 00000000..a1b9e46e --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/impute-missing-values.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Impute missing values\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep has the ability to impute missing values in specified columns. In this case, we will attempt to impute the missing _Latitude_ and _Longitude_ values in the input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# loading input data\n", + "dflow = dprep.read_csv(path= '../data/crime-spring.csv')\n", + "dflow = dflow.keep_columns(['ID', 'Arrest', 'Latitude', 'Longitude'])\n", + "dflow = dflow.to_number(['Latitude', 'Longitude'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The third record from input data has _Latitude_ and _Longitude_ missing. To impute those missing values, we can use `ImputeMissingValuesBuilder` to learn a fixed program which imputes the columns with either a calculated `MIN`, `MAX` or `MEAN` value or a `CUSTOM` value. When `group_by_columns` is specified, missing values will be imputed by group with `MIN`, `MAX` and `MEAN` calculated per group." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Firstly, let us quickly see check the `MEAN` value of _Latitude_ column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_mean = dflow.summarize(group_by_columns=['Arrest'],\n", + " summary_columns=[dprep.SummaryColumnsValue(column_id='Latitude',\n", + " summary_column_name='Latitude_MEAN',\n", + " summary_function=dprep.SummaryFunction.MEAN)])\n", + "dflow_mean = dflow_mean.filter(dprep.col('Arrest') == 'FALSE')\n", + "dflow_mean.head(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `MEAN` value of _Latitude_ looks good. So we will impute _Latitude_ with it. As for `Longitude`, we will impute it using `42` based on external knowledge." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# impute with MEAN\n", + "impute_mean = dprep.ImputeColumnArguments(column_id='Latitude',\n", + " impute_function=dprep.ReplaceValueFunction.MEAN)\n", + "# impute with custom value 42\n", + "impute_custom = dprep.ImputeColumnArguments(column_id='Longitude',\n", + " custom_impute_value=42)\n", + "# get instance of ImputeMissingValuesBuilder\n", + "impute_builder = dflow.builders.impute_missing_values(impute_columns=[impute_mean, impute_custom],\n", + " group_by_columns=['Arrest'])\n", + "# call learn() to learn a fixed program to impute missing values\n", + "impute_builder.learn()\n", + "# call to_dataflow() to get a dataflow with impute step added\n", + "dflow_imputed = impute_builder.to_dataflow()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# check impute result\n", + "dflow_imputed.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the result above, the missing _Latitude_ has been imputed with the `MEAN` value of `Arrest=='false'` group, and the missing _Longitude_ has been imputed with `42`." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/join.ipynb b/work-with-data/dataprep/how-to-guides/join.ipynb new file mode 100644 index 00000000..2f8c2e47 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/join.ipynb @@ -0,0 +1,265 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/join.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Join\n", + "\n", + "In Data Prep you can easily join two Dataflows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, get the left side of the data into a shape that is ready for the join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get the first Dataflow and derive desired key column\n", + "dflow_left = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/testfiles/BostonWeather.csv')\n", + "dflow_left = dflow_left.derive_column_by_example(source_columns='DATE', new_column_name='date_timerange',\n", + " example_data=[('11/11/2015 0:54', 'Nov 11, 2015 | 12AM-2AM'),\n", + " ('2/1/2015 0:54', 'Feb 1, 2015 | 12AM-2AM'),\n", + " ('1/29/2015 20:54', 'Jan 29, 2015 | 8PM-10PM')])\n", + "dflow_left = dflow_left.drop_columns(['DATE'])\n", + "\n", + "# convert types and summarize data\n", + "dflow_left = dflow_left.set_column_types(type_conversions={'HOURLYDRYBULBTEMPF': dprep.TypeConverter(dprep.FieldType.DECIMAL)})\n", + "dflow_left = dflow_left.filter(expression=~dflow_left['HOURLYDRYBULBTEMPF'].is_error())\n", + "dflow_left = dflow_left.summarize(group_by_columns=['date_timerange'],summary_columns=[dprep.SummaryColumnsValue('HOURLYDRYBULBTEMPF', dprep.api.engineapi.typedefinitions.SummaryFunction.MEAN, 'HOURLYDRYBULBTEMPF_Mean')] )\n", + "\n", + "# cache the result so the steps above are not executed every time we pull on the data\n", + "import os\n", + "from pathlib import Path\n", + "cache_dir = str(Path(os.getcwd(), 'dataflow-cache'))\n", + "dflow_left.cache(directory_path=cache_dir)\n", + "dflow_left.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's prepare the data for the right side of the join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get the second Dataflow and desired key column\n", + "dflow_right = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/bike-share/*-hubway-tripdata.csv')\n", + "dflow_right = dflow_right.keep_columns(['starttime', 'start station id'])\n", + "dflow_right = dflow_right.derive_column_by_example(source_columns='starttime', new_column_name='l_date_timerange',\n", + " example_data=[('2015-01-01 00:21:44', 'Jan 1, 2015 | 12AM-2AM')])\n", + "dflow_right = dflow_right.drop_columns('starttime')\n", + "\n", + "# cache the results\n", + "dflow_right.cache(directory_path=cache_dir)\n", + "dflow_right.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are three ways you can join two Dataflows in Data Prep:\n", + "1. Create a `JoinBuilder` object for interactive join configuration.\n", + "2. Call ```join()``` on one of the Dataflows and pass in the other along with all other arguments.\n", + "3. Call ```Dataflow.join()``` method and pass in two Dataflows along with all other arguments.\n", + "\n", + "We will explore the builder object as it simplifies the determination of correct arguments. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# construct a builder for joining dataflow_l with dataflow_r\n", + "join_builder = dflow_left.builders.join(right_dataflow=dflow_right, left_column_prefix='l', right_column_prefix='r')\n", + "\n", + "join_builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So far the builder has no properties set except default values.\n", + "From here you can set each of the options and preview its effect on the join result or use Data Prep to determine some of them.\n", + "\n", + "Let's start with determining appropriate column prefixes for left and right side of the join and lists of columns that would not conflict and therefore don't need to be prefixed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "join_builder.detect_column_info()\n", + "join_builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can see that Data Prep has performed a pull on both Dataflows to determine the column names in them. Given that `dataflow_r` already had a column starting with `l_` new prefix got generated which would not collide with any column names that are already present.\n", + "Additionally columns in each Dataflow that won't conflict during join would remain unprefixed.\n", + "This apprach to column naming is crucial for join robustness to schema changes in the data. Let's say that at some time in future the data consumed by left Dataflow will also have `l_date_timerange` column in it.\n", + "Configured as above the join will still run as expected and the new column will be prefixed with `l2_` ensuring that ig column `l_date_timerange` was consumed by some other future transformation it remains unaffected.\n", + "\n", + "Note: `KEY_generated` is appended to both lists and is reserved for Data Prep use in case Autojoin is performed.\n", + "\n", + "### Autojoin\n", + "Autojoin is a Data prep feature that determines suitable join arguments given data on both sides. In some cases Autojoin can even derive a key column from a number of available columns in the data.\n", + "Here is how you can use Autojoin:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# generate join suggestions\n", + "join_builder.generate_suggested_join()\n", + "\n", + "# list generated suggestions\n", + "join_builder.list_join_suggestions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's select the first suggestion and preview the result of the join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# apply first suggestion\n", + "join_builder.apply_suggestion(0)\n", + "\n", + "join_builder.preview(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, get our new joined Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_autojoined = join_builder.to_dataflow().drop_columns(['l_date_timerange'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Joining two Dataflows without pulling the data\n", + "\n", + "If you don't want to pull on data and know what join should look like, you can always use the join method on the Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_joined = dprep.Dataflow.join(left_dataflow=dflow_left,\n", + " right_dataflow=dflow_right,\n", + " join_key_pairs=[('date_timerange', 'l_date_timerange')],\n", + " left_column_prefix='l2_',\n", + " right_column_prefix='r_')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_joined.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_joined = dflow_joined.filter(expression=dflow_joined['r_start station id'] == '67')\n", + "df = dflow_joined.to_pandas_dataframe()\n", + "df" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/label-encoder.ipynb b/work-with-data/dataprep/how-to-guides/label-encoder.ipynb new file mode 100644 index 00000000..bc7b78c1 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/label-encoder.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/label-encoder.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Label Encoder\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has the ability to encode labels with values between 0 and (number of classes - 1) using `label_encode`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "from datetime import datetime\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use `label_encode` from a Dataflow, simply specify the source column and the new column name. `label_encode` will figure out all the distinct values or classes in the source column, and it will return a new Dataflow with a new column containing the labels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.label_encode(source_column='Primary Type', new_column_name='Primary Type Label')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To have more control over the encoded labels, create a builder with `dataflow.builders.label_encode`.\n", + "The builder allows you to preview and modify the encoded labels before generating a new Dataflow with the results. \n", + "To get started, create a builder object with `dataflow.builders.label_encode` specifying the source column and the new column name. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.label_encode(source_column='Location Description', new_column_name='Location Description Label')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To generate the encoded labels, call the `learn` method on the builder object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.learn()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To check the result, access the generated labels through the property `encoded_labels`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.encoded_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To modify the generated results, just assign a new value to `encoded_labels`. The following example adds a missing label not found in the sample data. `builder.encoded_labels` is saved into a variable `encoded_labels`, modified, and assigned back to `builder.encoded_labels`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "encoded_labels = builder.encoded_labels\n", + "encoded_labels['TOWNHOUSE'] = 6\n", + "\n", + "builder.encoded_labels = encoded_labels\n", + "builder.encoded_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the desired results are achieved, call `builder.to_dataflow` to get the new Dataflow with the encoded labels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataflow = builder.to_dataflow()\n", + "dataflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb b/work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb new file mode 100644 index 00000000..a7e5fd65 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/min-max-scaler.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Min-Max Scaler\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The min-max scaler scales all values in a column to a desired range (typically [0, 1]). This is also known as feature scaling or unity-based normalization. Min-max scaling is commonly used to normalize numeric columns in a data set for machine learning algorithms." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, load a data set containing information about crime in Chicago. Keep only a few columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv('../data/crime-spring.csv')\n", + "dflow = dflow.keep_columns(columns=['ID', 'District', 'FBI Code'])\n", + "dflow = dflow.to_number(columns=['District', 'FBI Code'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using `get_profile()`, you can see the shape of the numeric columns such as the minimum, maximum, count, and number of error values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To apply min-max scaling, call the function `min_max_scaler` on the Dataflow and specify the column name. This will trigger a full data scan over the column to determine the min and max values and perform the scaling. Note that the min and max values of the column are preserved at this point. If the same dataflow steps are performed over a different dataset, the min-max scaler must be re-executed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_district = dflow.min_max_scale(column='District')\n", + "dflow_district.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Look at the data profile to see that the \"District\" column is now scaled; the min is 0 and the max is 1. Any error values and missing values from the source column are preserved." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_district.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also specify a custom range for the scaling. Instead of [0, 1], let's choose [-10, 10]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_district_range = dflow.min_max_scale(column='District', range_min=-10, range_max=10)\n", + "dflow_district_range.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In some cases, you may want to manually provide the min and max of the data in the source column. For example, you may want to avoid a full data scan because the dataset is large and we already know the min and max. You can provide the known min and max to the `min_max_scaler` function. The column will be scaled using the provided values. For example, if you want to scale the `FBI Code` column with 6 (`data_min`) becoming 0 (`range_min`), the program will scan the data to get `data_max`, which will become 1 (`range_max`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_fbi = dflow.min_max_scale(column='FBI Code', data_min=6)\n", + "dflow_fbi.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using a Min-Max Scaler builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For more flexibility when constructing the arguments for the min-max scaling, you can use a Min-Max Scaler builder." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.min_max_scale(column='District')\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calling `builder.learn()` will trigger a full data scan to see what `data_min` and `data_max` are. You can choose whether to use these values or set custom values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.learn()\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you want to provide custom values for any of the arguments, you can update the builder object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.range_max = 10\n", + "builder.data_min = 6\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you are satisfied with the arguments, you will call `builder.to_dataflow()` to get the result. Note that the min and max values of the source column is preserved by the builder at this point. If you need to get the true `data_min` and `data_max` values again, you will need to set those arguments on the builder to `None` and then call `builder.learn()` again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_builder = builder.to_dataflow()\n", + "dflow_builder.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb b/work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb new file mode 100644 index 00000000..72918540 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/one-hot-encoder.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# One Hot Encoder\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep has the ability to perform one hot encoding on a selected column using `one_hot_encode`. The result Dataflow will have a new binary column for each categorical label encountered in the selected column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use `one_hot_encode` from a Dataflow, simply specify the source column. `one_hot_encode` will figure out all the distinct values or categorical labels in the source column using the current data, and it will return a new Dataflow with a new binary column for each categorical label. Note that the categorical labels are remembered in the Dataflow step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_result = dflow.one_hot_encode(source_column='Location Description')\n", + "dflow_result.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, all the new columns will use the `source_column` name as a prefix. However, if you would like to specify your own prefix, simply pass a `prefix` string as a second parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_result = dflow.one_hot_encode(source_column='Location Description', prefix='LOCATION_')\n", + "dflow_result.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To have more control over the categorical labels, create a builder using `dataflow.builders.one_hot_encode`. The builder allows to preview and modify the categorical labels before generating a new Dataflow with the results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.one_hot_encode(source_column='Location Description', prefix='LOCATION_')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To generate the categorical labels, call the `learn` method on the builder object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.learn()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To preview the categorical labels, simply access them through the property `categorical_labels` on the builder object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.categorical_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To modify the generated `categorical_labels`, assign a new value to `categorical_labels` or modify the existing one. The following example adds a missing label not found on the sample data to `categorical_labels`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.categorical_labels.append('TOWNHOUSE')\n", + "builder.categorical_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the desired results are achieved, call `builder.to_dataflow` to get the new Dataflow with the encoded labels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_result = builder.to_dataflow()\n", + "dflow_result.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb b/work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb new file mode 100644 index 00000000..92064377 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/open-save-dataflows.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Opening and Saving Dataflows\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you have built a Dataflow, you can save it to a `.dprep` file. This persists all of the information in your Dataflow including steps you've added, examples and programs from by-example steps, computed aggregations, etc.\n", + "\n", + "You can also open `.dprep` files to access any Dataflows you have previously persisted." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Open\n", + "\n", + "Use the `open()` method of the Dataflow class to load existing `.dprep` files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "dflow_path = os.path.join(os.getcwd(), '..', 'data', 'crime.dprep')\n", + "print(dflow_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import Dataflow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = Dataflow.open(dflow_path)\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Edit\n", + "\n", + "After a Dataflow is loaded, it can be further edited as needed. In this example, a filter is added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import col" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.filter(col('Description') != 'SIMPLE')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Save\n", + "\n", + "Use the `save()` method of the Dataflow class to write out the `.dprep` file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tempfile\n", + "temp_dir = tempfile._get_default_tempdir()\n", + "temp_file_name = next(tempfile._get_candidate_names())\n", + "temp_dflow_path = os.path.join(temp_dir, temp_file_name + '.dprep')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.save(temp_dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Round-trip\n", + "\n", + "This illustrates the ability to load the edited Dataflow back in and use it, in this case to get a pandas DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_to_open = Dataflow.open(temp_dflow_path)\n", + "df = dflow_to_open.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if os.path.isfile(temp_dflow_path):\n", + " os.remove(temp_dflow_path)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb b/work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb new file mode 100644 index 00000000..883bc5c8 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/quantile-transformation.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantile Transformation\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "DataPrep has the ability to perform quantile transformation to a numeric column. This transformation can transform the data into a normal or uniform distribution. Values bigger than the learnt boundaries will simply be clipped to the learnt boundaries when applying quantile transformation.\n", + "\n", + "Let's load a sample of the median income of california households in different suburbs from the 1990 census data. From the data profile, we can see that the minimum value and maximum value is 0.9946 and 15 respectively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.read_csv(path='../data/median_income.csv').set_column_types(type_conversions={\n", + " 'median_income': dprep.TypeConverter(dprep.FieldType.DECIMAL)\n", + "})\n", + "dflow.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now apply quantile transformation to `median_income` and see how that affects the data. We will apply quantile transformation twice, one that maps the data to a Uniform(0, 1) distribution, one that maps it to a Normal(0, 1) distribution.\n", + "\n", + "From the data profile, we can see that the min and max of the uniform median income is strictly between 0 and 1 and the mean and standard deviation of the normal median income is close to 0 and 1 respectively.\n", + "\n", + "*Note: for normal distribution, we will clip the values at the ends as the 0th percentile and the 100th percentile are -Inf and Inf respectively.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.quantile_transform(source_column='median_income', new_column='median_income_uniform', quantiles_count=5)\n", + "dflow = dflow.quantile_transform(source_column='median_income', new_column='median_income_normal', \n", + " quantiles_count=5, output_distribution=\"Normal\")\n", + "dflow.get_profile()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/random-split.ipynb b/work-with-data/dataprep/how-to-guides/random-split.ipynb new file mode 100644 index 00000000..4f87af22 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/random-split.ipynb @@ -0,0 +1,145 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/random-split.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Random Split\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep provides the functionality of splitting a data set into two. When training a machine learning model, it is often desirable to train the model on a subset of data, then validate the model on a different subset." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `random_split(percentage, seed=None)` function in Data Prep takes in a Dataflow and randomly splitting it into two distinct subsets (approximately by the percentage specified)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `seed` parameter is optional. If a seed is not provided, a stable one is generated, ensuring that the results for a specific Dataflow remain consistent. Different calls to `random_split` will receive different seeds." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To demonstrate, you can go through the following example. First, you can read the first 10,000 lines from a file. Since the contents of the file don't matter, just the first two columns can be used for a simple example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/testfiles/crime0.csv').take(10000)\n", + "dflow = dflow.keep_columns(['ID', 'Date'])\n", + "profile = dflow.get_profile()\n", + "print('Row count: %d' % (profile.columns['ID'].count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, you can call `random_split` with the percentage set to 10% (the actual split ratio will be an approximation of `percentage`). You can take a look at the row count of the first returned Dataflow. You should see that `dflow_test` has approximately 1,000 rows (10% of 10,000)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(dflow_test, dflow_train) = dflow.random_split(percentage=0.1)\n", + "profile_test = dflow_test.get_profile()\n", + "print('Row count of \"test\": %d' % (profile_test.columns['ID'].count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you can take a look at the row count of the second returned Dataflow. The row count of `dflow_test` and `dflow_train` sums exactly to 10,000, because `random_split` results in two subsets that make up the original Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile_train = dflow_train.get_profile()\n", + "print('Row count of \"train\": %d' % (profile_train.columns['ID'].count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To specify a fixed seed, simply provide it to the `random_split` function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(dflow_test, dflow_train) = dflow.random_split(percentage=0.1, seed=12345)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb b/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb new file mode 100644 index 00000000..e8d62acf --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Replace DataSource Reference\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A common practice when performing DataPrep is to build up a script or set of cleaning operations on a smaller example file locally. This is quicker and easier than dealing with large amounts of data initially.\n", + "\n", + "After building a Dataflow that performs the desired steps, it's time to run it against the larger dataset, which may be stored in the cloud, or even locally just in a different file. This is where we can use `Dataflow.replace_datasource` to get a Dataflow identical to the one built on the small data, but referencing the newly specified DataSource." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.read_csv('../data/crime-spring.csv')\n", + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we have the first 10 rows of a dataset called 'Crime'. The original dataset is over 100MB (admittedly not that large of a dataset but this is just an example).\n", + "\n", + "We'll perform a few cleaning operations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dropped = dflow.drop_columns(['Location', 'Updated On', 'X Coordinate', 'Y Coordinate', 'Description'])\n", + "sctb = dflow_dropped.builders.set_column_types()\n", + "sctb.learn(inference_arguments=dprep.InferenceArguments(day_first=False))\n", + "dflow_typed = sctb.to_dataflow()\n", + "dflow_typed.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have a Dataflow with all our desired steps, we're ready to run against the 'full' dataset stored in Azure Blob.\n", + "All we need to do is pass the BlobDataSource into `replace_datasource` and we'll get back an identical Dataflow with the new DataSource substituted in." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_replaced = dflow_typed.replace_datasource(dprep.BlobDataSource('https://dpreptestfiles.blob.core.windows.net/testfiles/crime0.csv'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "'replaced_dflow' will now pull data from the 168MB (729734 rows) version of Crime0.csv stored in Azure Blob!\n", + "\n", + "NOTE: Dataflows can also be created by referencing a different Dataflow. Instead of using `replace_datasource`, there is a corresponding `replace_reference` method.\n", + "\n", + "We should be careful now since pulling all that data down and putting it in a pandas dataframe isn't an ideal way to inspect the result of our Dataflow. So instead, to see that our steps are being applied to all the new data, we can add a `take_sample` step, which will select records at random (based on a given probability) to be returned.\n", + "\n", + "The probability below takes the ~730000 rows down to a more inspectable ~73, though the number will vary each time `to_pandas_dataframe()` is run, since they are being randomly selected based on the probability." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_random_sample= dflow_replaced.take_sample(probability=0.0001)\n", + "sample = dflow_random_sample.to_pandas_dataframe()\n", + "sample" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb b/work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb new file mode 100644 index 00000000..04dad995 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/replace-fill-error.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Replace, Fill, Error\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use the methods in this notebook to change values in your dataset.\n", + "\n", + "* replace - use this method to replace a value with another value. You can also use this to replace null with a value, or a value with null\n", + "* error - use this method to replace a value with an error.\n", + "* fill_nulls - this method lets you fill all nulls in a column with a certain value.\n", + "* fill_errors - this method lets you fill all errors in a column with a certain value." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv('../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_datetime('Date', ['%m/%d/%Y %H:%M'])\n", + "dflow = dflow.to_number(['IUCR', 'District', 'FBI Code'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Replace " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### String\n", + "Use `replace` to swap a string value with another string value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.replace('Primary Type', 'THEFT', 'STOLEN')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use `replace` to remove a certain string value from the column, replacing it with null. Note that Pandas shows null values as None." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.replace('Primary Type', 'DECEPTIVE PRACTICE', None)\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Numeric\n", + "Use `replace` to swap a numeric value with another numeric value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.replace('District', 5, 1)\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Date\n", + "Use `replace` to swap in a new Date for an existing Date in the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import datetime, timezone\n", + "dflow = dflow.replace('Date', \n", + " datetime(2016, 4, 15, 9, 0, tzinfo=timezone.utc), \n", + " datetime(2018, 7, 4, 0, 0, tzinfo=timezone.utc))\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Error \n", + "\n", + "The `error` method lets you create Error values. You can pass to this function the value that you want to find, along with the Error code to use in any Errors created." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.error('IUCR', 890, 'Invalid value')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fill Nulls \n", + "\n", + "Use the `fill_nulls` method to replace all null values in columns with another value. This is similar to Panda's fillna() method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.fill_nulls('Primary Type', 'N/A')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fill Errors \n", + "\n", + "Use the `fill_errors` method to replace all error values in columns with another value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.fill_errors('IUCR', -1)\n", + "head = dflow.head(5)\n", + "head" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/secrets.ipynb b/work-with-data/dataprep/how-to-guides/secrets.ipynb new file mode 100644 index 00000000..c77169c9 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/secrets.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/secrets.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Providing Secrets\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Currently, secrets are only persisted for the lifetime of the engine process. Even if the dataflow is saved to a file, the secrets are not persisted in the dprep file. If you started a new session (i.e. start a new engine process), loaded a dataflow and wanted to run it, you will need to call `use_secrets` to register the required secrets to use during execution, otherwise the execution will fail as the required secrets are not available.\n", + "\n", + "In this notebook, we will:\n", + "1. Loading a previously saved dataflow\n", + "2. Call `get_missing_secrets` to determine the missing secrets\n", + "3. Call `use_secrets` and pass in the missing secrets to register it with the engine for this session\n", + "4. Call `head` to see the a preview of the data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's load the previously saved dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.Dataflow.open(file_path='../data/secrets.dprep')\n", + "dflow" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can call `get_missing_secrets` to see which required secrets are missing in the engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.get_missing_secrets()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can now read the secrets from an environment variable, put it in a secret dictionary, and call `use_secrets` with the secrets. This will register the secrets in the engine so you don't need to provide them again in this session.\n", + "\n", + "_Note: It is a bad practice to have secrets in files that will be checked into source control._" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sas = os.environ['SCENARIOS_SECRETS']\n", + "secrets = {\n", + " 'https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv': sas\n", + "}\n", + "dflow.use_secrets(secrets=secrets)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can now call `head` without passing in `secrets` and the engine will successfully execute. Here is a preview of the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/semantic-types.ipynb b/work-with-data/dataprep/how-to-guides/semantic-types.ipynb new file mode 100644 index 00000000..266353df --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/semantic-types.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/semantic-types.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Semantic Types\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Some string values can be recognized as semantic types. For example, email addresses, US zip codes or IP addresses have specific formats that can be recognized, and then split in specific ways.\n", + "\n", + "When getting a DataProfile you can optionally ask to collect counts of values recognized as semantic types. [`Dataflow.get_profile()`](./data-profile.ipynb) executes the Dataflow, calculates profile information, and returns a newly constructed DataProfile. Semantic type counts can be included in the data profile by calling `get_profile` with the `include_stype_counts` argument set to true.\n", + "\n", + "The `stype_counts` property of the DataProfile will then include entries for columns where some semantic types were recognized for some values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.read_json(path='../data/json.json')\n", + "\n", + "profile = dflow.get_profile(include_stype_counts=True)\n", + "\n", + "print(\"row count: \" + str(profile.row_count))\n", + "profile.stype_counts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To see all the supported semantic types, you can examine the `SType` enumeration. More types will be added over time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[t.name for t in dprep.SType]\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can filter the found semantic types down to just those where all non-empty values matched. The `DataProfile.stype_counts` gives a list of semantic type counts for each column, where at least some matches were found. Those lists are in desecending order of count, so here we consider only the first in each list, as that will be the one with the highest count of values that match.\n", + "\n", + "In this example, the column `inspections.business.postal_code` looks to be a US zip code." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stypes_counts = profile.stype_counts\n", + "all_match = [\n", + " (column, stypes_counts[column][0].stype)\n", + " for column in stypes_counts\n", + " if profile.row_count - profile.columns[column].empty_count == stypes_counts[column][0].count\n", + "]\n", + "all_match" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use semantic types to compute new columns. The new columns are the values split up into elements, or canonicalized.\n", + "\n", + "Here we reduce our data down to just the `postal` column so we can better see what a `split_stype` operation can do." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_postal = dflow.keep_columns(['inspections.business.postal_code']).rename_columns({'inspections.business.postal_code': 'postal'})\n", + "dflow_postal.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With `SType.ZipCode`, values are split into their basic five digit zip code and the plus-four add-on of the Zip+4 format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_split = dflow_postal.split_stype('postal', dprep.SType.ZIPCODE)\n", + "dflow_split.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`split_stype` also allows you to specify the fields of the stype to use and the name of the new columns. For example, if you just needed to strip the plus four from our zip codes, you could use this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_no_plus4 = dflow_postal.split_stype('postal', dprep.SType.ZIPCODE, ['zip'], ['zipNoPlus4'])\n", + "dflow_no_plus4.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb b/work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb new file mode 100644 index 00000000..9b709207 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb @@ -0,0 +1,220 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/split-column-by-example.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Split column by example\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "DataPrep also offers you a way to easily split a column into multiple columns.\n", + "The SplitColumnByExampleBuilder class lets you generate a proper split program that will work even when the cases are not trivial, like in example below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_lines(path='../data/crime.txt')\n", + "df = dflow.head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df['Line'].iloc[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see above, you can't split this particular file by space character as it will create too many columns.\n", + "That's where split_column_by_example could be quite useful." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.split_column_by_example('Line', keep_delimiters=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Couple things to take note of here. No examples were given, and yet DataPrep was able to generate quite reasonable split program. \n", + "We have passed keep_delimiters=True so we can see all the data split into columns. In practice, though, delimiters are rarely useful, so let's exclude them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.keep_delimiters = False\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This looks pretty good already, except that one case number is split into 2 columns. Taking the first row as an example, we want to keep case number as \"HY329907\" instead of \"HY\" and \"329907\" seperately. \n", + "If we request generation of suggested examples we will get a list of examples that require input." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "suggestions = builder.generate_suggested_examples()\n", + "suggestions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "suggestions.iloc[0]['Line']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Having retrieved source value we can now provide an example of desired split.\n", + "Notice that we chose not to split date and time but rather keep them together in one column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.add_example(example=(suggestions['Line'].iloc[0], ['10140490','HY329907','7/5/2015 23:50','050XX N NEWLAND AVE','820','THEFT']))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see from the preview, some of the crime types (`Line_6`) do not show up as expected. Let's try to add one more example. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.add_example(example=(df['Line'].iloc[1],['10139776','HY329265','7/5/2015 23:30','011XX W MORSE AVE','460','BATTERY']))\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This looks just like what we need. Let's get a dataflow with splited columns and drop original column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = builder.to_dataflow()\n", + "dflow = dflow.drop_columns(['Line'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we have successfully split the data into useful columns through examples. " + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb b/work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb new file mode 100644 index 00000000..d1abb62f --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb @@ -0,0 +1,217 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/subsetting-sampling.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sampling and Subsetting\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once a Dataflow has been created, it is possible to act on only a subset of the records contained in it. This can help when working with very large datasets or when only a portion of the records is truly relevant." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Head\n", + "\n", + "The `head` method will take the number of records specified, run them through the transformations in the Dataflow, and then return the result as a Pandas dataframe." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.read_csv('../data/crime_duplicate_headers.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Take\n", + "\n", + "The `take` method adds a step to the Dataflow that will keep the number of records specified (counting from the beginning) and drop the rest. Unlike `head`, which does not modify the Dataflow, all operations applied on a Dataflow on which `take` has been applied will affect only the records kept." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_top_five = dflow.take(5)\n", + "dflow_top_five.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Skip\n", + "\n", + "It is also possible to skip a certain number of records in a Dataflow, such that transformations are only applied after a specific point. Depending on the underlying data source, a Dataflow with a `skip` step might still have to scan through the data in order to skip past the records." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skip_top_one = dflow_top_five.skip(1)\n", + "dflow_skip_top_one.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Take Sample\n", + "\n", + "In addition to taking records from the top of the dataset, it's also possible to take a random sample of the dataset. This is done through the `take_sample(probability, seed=None)` method. This method will scan through all of the records available in the Dataflow and include them based on the probability specified. The `seed` parameter is optional. If a seed is not provided, a stable one is generated, ensuring that the results for a specific Dataflow remain consistent. Different calls to `take_sample` will receive different seeds." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_sampled = dflow.take_sample(0.1)\n", + "dflow_sampled.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`skip`, `take`, and `take_sample` can all be combined. With this, we can achieve behaviors like getting a random 10% sample fo the middle N records of a dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "seed = 1\n", + "dflow_nested_sample = dflow.skip(1).take(5).take_sample(0.5, seed)\n", + "dflow_nested_sample.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Take Stratified Sample\n", + "Besides sampling all by a probability, we also have stratified sampling, provided the strata and strata weights, the probability to sample each stratum with.\n", + "This is done through the `take_stratified_sample(columns, fractions, seed=None)` method.\n", + "For all records, we will group each record by the columns specified to stratify, and based on the stratum x weight information in `fractions`, include said record.\n", + "\n", + "Seed behavior is same as in `take_sample`.\n", + "\n", + "If a stratum is not specified or the record cannot be grouped by said stratum, we default the weight to sample by to 0 (it will not be included).\n", + "\n", + "The order of `fractions` must match the order of `columns`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fractions = {}\n", + "fractions[('ASSAULT',)] = 0.5\n", + "fractions[('BATTERY',)] = 0.2\n", + "fractions[('ARSON',)] = 0.5\n", + "fractions[('THEFT',)] = 1.0\n", + "\n", + "columns = ['Primary Type']\n", + "\n", + "single_strata_sample = dflow.take_stratified_sample(columns=columns, fractions = fractions, seed = 42)\n", + "single_strata_sample.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Stratified sampling on multiple columns is also supported." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fractions = {}\n", + "fractions[('ASSAULT', '560')] = 0.5\n", + "fractions[('BATTERY', '460')] = 0.2\n", + "fractions[('ARSON', '1020')] = 0.5\n", + "fractions[('THEFT', '820')] = 1.0\n", + "\n", + "columns = ['Primary Type', 'IUCR']\n", + "\n", + "multi_strata_sample = dflow.take_stratified_sample(columns=columns, fractions = fractions, seed = 42)\n", + "multi_strata_sample.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Caching\n", + "It is usually a good idea to cache the sampled Dataflow for later uses.\n", + "\n", + "See [here](cache.ipynb) for more details about caching." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/summarize.ipynb b/work-with-data/dataprep/how-to-guides/summarize.ipynb new file mode 100644 index 00000000..56a37bee --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/summarize.ipynb @@ -0,0 +1,590 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/summarize.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Summarize\n", + "\n", + "Azure ML Data Prep can help summarize your data by providing you a synopsis based on aggregates over specific columns.\n", + "\n", + "## Table of Contents\n", + "[Overview](#overview)
\n", + "[Summmary Functions](#summary)
\n", + "* [SummaryFunction.MIN](#min)
\n", + "* [SummaryFunction.MAX](#max)
\n", + "* [SummaryFunction.MEAN](#mean)
\n", + "* [SummaryFunction.MEDIAN](#median)
\n", + "* [SummaryFunction.VAR](#var)
\n", + "* [SummaryFunction.SD](#sd)
\n", + "* [SummaryFunction.COUNT](#count)
\n", + "* [SummaryFunction.SUM](#sum)
\n", + "* [SummaryFunction.SKEWNESS](#skewness)
\n", + "* [SummaryFunction.KURTOSIS](#kurtosis)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overview\n", + "Before we drill down into each aggregate function, let us observe `summarize` end to end.\n", + "\n", + "We will start by reading some data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we count (`SummaryFunction.COUNT`) the number of rows with column ID with non-null values grouped by Primary Type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_summarize.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we choose to not group by anything, we will instead get a single record over the entire dataset. Here we will get the number of rows that have the column ID with non-null values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_nogroup = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='ID Count', \n", + " summary_function=dprep.SummaryFunction.COUNT)])\n", + "dflow_summarize_nogroup.head(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Conversely, we can group by multiple columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_2group = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type & Location Description ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type', 'Location Description'])\n", + "dflow_summarize_2group.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In a similar vein, we can compute multiple aggregates in a single summary. Each aggregate function is independent and it is possible to aggregate the same column multiple times." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_multi_agg = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT),\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type Min ID', \n", + " summary_function=dprep.SummaryFunction.MIN),\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Date',\n", + " summary_column_name='Primary Type Max Date', \n", + " summary_function=dprep.SummaryFunction.MAX)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_summarize_multi_agg.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we wanted this summary data back into our original data set, we can make use of `join_back` and optionally `join_back_columns_prefix` for easy naming distinctions. Summary columns will be added to the end. `group_by_columns` is not necessary for using `join_back`, however the behavior will be more like an append instead of a join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_join = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type'],\n", + " join_back=True,\n", + " join_back_columns_prefix='New_')\n", + "dflow_summarize_join.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary Functions\n", + "Here we will go over all the possible aggregates in Data Prep.\n", + "The most up to date set of functions can be found by enumerating the `SummaryFunction` enum." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "[x.name for x in dprep.SummaryFunction]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MIN\n", + "Data Prep can aggregate and find the minimum value of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Date',\n", + " summary_column_name='Primary Type Min Date', \n", + " summary_function=dprep.SummaryFunction.MIN)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MAX\n", + "Data Prep can find the maximum value of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Date',\n", + " summary_column_name='Primary Type Max Date', \n", + " summary_function=dprep.SummaryFunction.MAX)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MEAN\n", + "Data Prep can find the statistical mean of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Mean', \n", + " summary_function=dprep.SummaryFunction.MEAN)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MEDIAN\n", + "Data Prep can find the median value of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Median', \n", + " summary_function=dprep.SummaryFunction.MEDIAN)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.VAR\n", + "Data Prep can find the statistical variance of a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Variance', \n", + " summary_function=dprep.SummaryFunction.VAR)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that despite there being two cases of BATTERY, one of them is missing geographical location, thus only CRIMINAL DAMAGE can yield variance information. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.SD\n", + "Data Prep can find the standard deviation of a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Standard Deviation', \n", + " summary_function=dprep.SummaryFunction.SD)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to when we calculate variance, despite there being two cases of BATTERY, one of them is missing geographical location, thus only CRIMINAL DAMAGE can yield variance information. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.COUNT\n", + "Data Prep can count the number of rows that have a column with non-null values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Count', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that despite there being two cases of BATTERY, one of them is missing geographical location, thus when we group by Primary Type, we only get a count of one for Latitude." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.SUM\n", + "Data Prep can aggregate and sum the values of a column. Our dataset does not have many numerical facts, but here we sum IDs grouped by Primary Type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Sum', \n", + " summary_function=dprep.SummaryFunction.SUM)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.SKEWNESS\n", + "Data Prep can calculate the skewness of data in a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Skewness', \n", + " summary_function=dprep.SummaryFunction.SKEWNESS)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.KURTOSIS\n", + "Data Prep can calculate the kurtosis of data in a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Kurtosis', \n", + " summary_function=dprep.SummaryFunction.KURTOSIS)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb b/work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb new file mode 100644 index 00000000..e92c1e1c --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb @@ -0,0 +1,192 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/working-with-file-streams.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working With File Streams\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to loading and parsing tabular data (see [here](./data-ingestion.ipynb) for more details), Data Prep also supports a variety of operations on raw file streams. \n", + "\n", + "File streams are usually created by calling `Dataflow.get_files`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.Dataflow.get_files(path='../data/*.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The result of this operation is a Dataflow with a single column named \"Path\". This column contains values of type `StreamInfo`, each of which represents a different file matched by the search pattern specified when calling `get_files`. The string representation of a `StreamInfo` follows this pattern:\n", + "\n", + "StreamInfo(_Location_://_ResourceIdentifier_\\[_Arguments_\\])\n", + "\n", + "Location is the type of storage where the stream is located (e.g. Azure Blob, Local, or ADLS); ResouceIdentifier is the name of the file within that storage, such as a file path; and Arguments is a list of arguments required to load and read the file.\n", + "\n", + "On their own, `StreamInfo` objects are not particularly useful; however, you can use them as input to other functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieving File Names\n", + "\n", + "In the example above, we matched a set of CSV files by using a search pattern and got back a column with several `StreamInfo` objects, each representing a different file. Now, we will extract the file path and name for each of these values into a new string column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.add_column(expression=dprep.get_stream_name(dflow['Path']),\n", + " new_column_name='FilePath',\n", + " prior_column='Path')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `get_stream_name` function will return the full name of the file referenced by a `StreamInfo`. In the case of a local file, this will be an absolute path. From here, you can use the `derive_column_by_example` method to extract just the file name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "first_file_path = dflow.head(1)['FilePath'][0]\n", + "first_file_name = os.path.basename(first_file_path)\n", + "dflow = dflow.derive_column_by_example(new_column_name='FileName',\n", + " source_columns=['FilePath'],\n", + " example_data=(first_file_path, first_file_name))\n", + "dflow = dflow.drop_columns(['FilePath'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing Streams\n", + "\n", + "Whenever you have a column containing `StreamInfo` objects, it's possible to write these out to any of the locations Data Prep supports. You can do this by calling `Dataflow.write_streams`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.write_streams(streams_column='Path', base_path=dprep.LocalFileOutput('./test_out/')).run_local()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `base_path` parameter specifies the location the files will be written to. By default, the name of the file will be the resource identifier of the stream with any invalid characters replaced by `_`. In the case of streams referencing local files, this would be the full path of the original file. You can also specify the desired file names by referencing a column containing them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.write_streams(streams_column='Path',\n", + " base_path=dprep.LocalFileOutput('./test_out/'),\n", + " file_names_column='FileName').run_local()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using this functionality, you can transfer files from any source to any destination supported by Data Prep. In addition, since the streams are just values in the Dataflow, you can use all of the functionality available.\n", + "\n", + "Here, for example, we will write out only the files that start with the prefix \"crime-\". The resulting file names will have the prefix stripped and will be written to a folder named \"crime\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "prefix = 'crime-'\n", + "dflow = dflow.filter(dflow['FileName'].starts_with(prefix))\n", + "dflow = dflow.add_column(expression=dflow['FileName'].substring(len(prefix)),\n", + " new_column_name='CleanName',\n", + " prior_column='FileName')\n", + "dflow.write_streams(streams_column='Path',\n", + " base_path=dprep.LocalFileOutput('./test_out/crime/'),\n", + " file_names_column='CleanName').run_local()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/writing-data.ipynb b/work-with-data/dataprep/how-to-guides/writing-data.ipynb new file mode 100644 index 00000000..bfbe3865 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/writing-data.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/writing-data.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Writing Data\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is possible to write out the data at any point in a Dataflow. These writes are added as steps to the resulting Dataflow and will be executed every time the Dataflow is executed. Since there are no limitations to how many write steps there are in a pipeline, this makes it easy to write out intermediate results for troubleshooting or to be picked up by other pipelines.\n", + "\n", + "It is important to note that the execution of each write results in a full pull of the data in the Dataflow. For example, a Dataflow with three write steps will read and process every record in the dataset three times." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing to Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data can be written to files in any of our supported locations (Local File System, Azure Blob Storage, and Azure Data Lake Storage). In order to parallelize the write, the data is written to multiple partition files. A sentinel file named SUCCESS is also output once the write has completed. This makes it possible to identify when an intermediate write has completed without having to wait for the whole pipeline to complete.\n", + "\n", + "> When running a Dataflow in Spark, attempting to execute a write to an existing folder will fail. It is important to ensure the folder is empty or use a different target location per execution.\n", + "\n", + "The following file formats are currently supported:\n", + "- Delimited Files (CSV, TSV, etc.)\n", + "- Parquet Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll start by loading data into a Dataflow which will be re-used with different formats." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.auto_read_file('../data/crime.txt')\n", + "dflow = dflow.to_number('Column2')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delimited Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we create a dataflow with a write step.\n", + "\n", + "This operation is lazy until we invoke `run_local` (or any operation that forces execution like `to_pandas_dataframe`), only then will we execute the write operation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_write = dflow.write_to_csv(directory_path=dprep.LocalFileOutput('./test_out/'))\n", + "\n", + "dflow_write.run_local()\n", + "\n", + "dflow_written_files = dprep.read_csv('./test_out/part-*')\n", + "dflow_written_files.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data we wrote out contains several errors in the numeric columns due to numbers that we were unable to parse. When written out to CSV, these are replaced with the string \"ERROR\" by default. We can parameterize this as part of our write call. In the same vein, it is also possible to set what string to use to represent null values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_write_errors = dflow.write_to_csv(directory_path=dprep.LocalFileOutput('./test_out/'), \n", + " error='BadData',\n", + " na='NA')\n", + "dflow_write_errors.run_local()\n", + "dflow_written = dprep.read_csv('./test_out/part-*')\n", + "dflow_written.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Parquet Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to `write_to_csv`, `write_to_parquet` returns a new Dataflow with a Write Parquet Step which hasn't been executed yet.\n", + "\n", + "Then we run the Dataflow with `run_local`, which executes the write operation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_write_parquet = dflow.write_to_parquet(directory_path=dprep.LocalFileOutput('./test_parquet_out/'),\n", + " error='MiscreantData')\n", + "\n", + "dflow_write_parquet.run_local()\n", + "\n", + "dflow_written_parquet = dprep.read_parquet_file('./test_parquet_out/part-*')\n", + "dflow_written_parquet.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb b/work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb new file mode 100644 index 00000000..729af0db --- /dev/null +++ b/work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Getting started with Azure ML Data Prep SDK\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Wonder how you can make the most of the Azure ML Data Prep SDK? In this \"Getting Started\" guide, we'll demonstrate how to do your normal data wrangling with this SDK and showcase a few highlights that make this SDK shine. Using a sample of this [Kaggle crime dataset](https://www.kaggle.com/currie32/crimes-in-chicago/home) as an example, we'll cover how to:\n", + "\n", + "* [Read in data](#Read)\n", + "* [Profile your data](#Profile)\n", + "* [Append rows](#Append)\n", + "* [Apply common data science transforms](#Data-science-transforms)\n", + " * [Summarize](#Summarize)\n", + " * [Join](#Join)\n", + " * [Filter](#Filter)\n", + " * [Replace](#Replace)\n", + "* [Consume your cleaned dataset](#Consume)\n", + "* [Explore advanced features](#Explore)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "from os import path\n", + "from tempfile import mkdtemp\n", + "\n", + "import pandas as pd\n", + "import azureml.dataprep as dprep\n", + "\n", + "# Paths for datasets\n", + "file_crime_dirty = '../../data/crime-dirty.csv'\n", + "file_crime_spring = '../../data/crime-spring.csv'\n", + "file_crime_winter = '../../data/crime-winter.csv'\n", + "file_aldermen = '../../data/chicago-aldermen-2015.csv'\n", + "\n", + "# Seed\n", + "RAND_SEED = 7251" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read in data\n", + "\n", + "Azure ML Data Prep supports many different file reading formats (i.e. CSV, Excel, Parquet) and the ability to infer column types automatically. To see how powerful the `auto_read_file` capability is, let's take a peek at the `dirty-crime.csv`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dprep.read_csv(path=file_crime_dirty).head(7)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A common occurrence in many datasets is to have a column of values with commas; in our case, the last column represents location in the form of longitude-latitude pair. The default CSV reader interprets this comma as a delimiter and thus splits the data into two columns. Furthermore, it incorrectly reads in the header as the column name. Normally, we would need to `skip` the header and specify the delimiter as `|`, but our `auto_read_file` eliminates that work:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime_dirty = dprep.auto_read_file(path=file_crime_dirty)\n", + "\n", + "crime_dirty.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ if you'd like to specify the file type and adjust how you want to read files in, you can see the list of our specialized file readers and how to use them [here](../../how-to-guides/data-ingestion.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profile your data\n", + "\n", + "Let's understand what our data looks like. Azure ML Data Prep facilitates this process by offering data profiles that help us glimpse into column types and column summary statistics. Notice that our auto file reader automatically guessed the column type:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime_dirty.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Append rows\n", + "\n", + "What if your data is split across multiple files? We support the ability to append multiple datasets column-wise and row-wise. Here, we demonstrate how you can coalesce datasets row-wise:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Datasets with the same schema as crime_dirty\n", + "crime_winter = dprep.auto_read_file(path=file_crime_winter)\n", + "crime_spring = dprep.auto_read_file(path=file_crime_spring)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime = (crime_dirty.append_rows(dataflows=[crime_winter, crime_spring]))\n", + "\n", + "crime.take_sample(probability=0.25, seed=RAND_SEED).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ you can learn how to append column-wise and how to deal with appending data with different schemas [here](../../how-to-guides/append-columns-and-rows.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Apply common data science transforms\n", + "\n", + "Azure ML Data Prep supports almost all common data science transforms found in other industry-standard data science libraries. Here, we'll explore the ability to `summarize`, `join`, `filter`, and `replace`. \n", + "\n", + "__Advanced features:__\n", + "* We also provide \"smart\" transforms not found in pandas that use machine learning to [derive new columns](../../how-to-guides/derive-column-by-example.ipynb), [split columns](../../how-to-guides/split-column-by-example.ipynb), and [fuzzy grouping](../../how-to-guides/fuzzy-group.ipynb).\n", + "* Finally, we also help featurize your dataset to prepare it for machine learning; learn more about our featurizers like [one-hot encoder](../../how-to-guides/one-hot-encoder.ipynb), [label encoder](../../how-to-guides/label-encoder.ipynb), [min-max scaler](../../how-to-guides/min-max-scaler.ipynb), and [random (train-test) split](../../how-to-guides/random-split.ipynb).\n", + "* Our complete list of example Notebooks for transforms can be found in our [How-to Guides](../../how-to-guides)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Summarize\n", + "\n", + "Let's see which wards had the most crimes in our sample dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime_summary = (crime\n", + " .summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID', \n", + " summary_column_name='total_ward_crimes', \n", + " summary_function=dprep.SummaryFunction.COUNT\n", + " )\n", + " ],\n", + " group_by_columns=['Ward']\n", + " )\n", + ")\n", + "\n", + "(crime_summary\n", + " .sort(sort_order=[('total_ward_crimes', True)])\n", + " .head(5)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Join\n", + "\n", + "Let's annotate each observation with more information about the ward where the crime occurred. Let's do so by joining `crime` with a dataset which lists the current aldermen for each ward:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aldermen = dprep.auto_read_file(path=file_aldermen)\n", + "\n", + "aldermen.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime.join(\n", + " left_dataflow=crime,\n", + " right_dataflow=aldermen,\n", + " join_key_pairs=[\n", + " ('Ward', 'Ward')\n", + " ]\n", + ").head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ [Learn more](../../how-to-guides/join.ipynb) about how you can do all variants of `join`, like inner-, left-, right-, anti-, and semi-joins." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filter\n", + "\n", + "Let's look at theft crimes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "theft = crime.filter(crime['Primary Type'] == 'THEFT')\n", + "\n", + "theft.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Replace\n", + "\n", + "Notice that our `theft` dataset has empty strings in column `Location`. Let's replace those with a missing value:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "theft_replaced = (theft\n", + " .replace_na(\n", + " columns=['Location'], \n", + " use_empty_string_as_na=True\n", + " )\n", + ")\n", + "\n", + "theft_replaced.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ [Learn more](../../how-to-guides/replace-fill-error.ipynb) about more advanced `replace` and `fill` capabilities." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Consume your cleaned dataset\n", + "\n", + "Azure ML Data Prep allows you to \"choose your own adventure\" once you're done wrangling. You can:\n", + "\n", + "1. Write to a pandas dataframe\n", + "2. Execute on Spark\n", + "3. Consume directly in Azure Machine Learning models\n", + "\n", + "In this quickstart guide, we'll show how you can export to a pandas dataframe.\n", + "\n", + "__Advanced features:__ \n", + "* One of the beautiful features of Azure ML Data Prep is that you only need to write your code once and choose whether to scale up or out.\n", + "* You can directly consume your new DataFlow in model builders like Azure Machine Learning's [automated machine learning](https://github.com/Azure/MachineLearningNotebooks/blob/master/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "theft_replaced.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Explore advanced features\n", + "\n", + "Congratulations on finishing your introduction to the Azure ML Data Prep SDK! If you'd like more detailed tutorials on how to construct machine learning datasets or dive deeper into all of its functionality, you can find more information in our detailed notebooks [here](https://github.com/Microsoft/PendletonDocs). There, we cover topics including how to:\n", + "\n", + "* [Cache your Dataflow to speed up your iterations](../../how-to-guides/cache.ipynb)\n", + "* [Add your custom Python transforms](../../how-to-guides/custom-python-transforms.ipynb)\n", + "* [Impute missing values](../../how-to-guides/impute-missing-values.ipynb)\n", + "* [Sample your data](../../how-to-guides/subsetting-sampling.ipynb)\n", + "* [Reference and link between Dataflows](../../how-to-guides/join.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/tutorials/getting-started/getting-started.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.2" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/datasets/README.md b/work-with-data/datasets/README.md new file mode 100644 index 00000000..26b22054 --- /dev/null +++ b/work-with-data/datasets/README.md @@ -0,0 +1,159 @@ +# Azure Machine Learning Datasets (preview) + +Azure Machine Learning Datasets (preview) make it easier to access and work with your data. Datasets manage data in various scenarios such as model training and pipeline creation. Using the Azure Machine Learning SDK, you can access underlying storage, explore and prepare data, manage the life cycle of different Dataset definitions, and compare between Datasets used in training and in production. + +## Create and Register Datasets + +It's easy to create Datasets from either local files, or Azure Datastores. + +```Python +from azureml.core.workspace import Workspace +from azureml.core.datastore import Datastore +from azureml.core.dataset import Dataset + +datastore_name = 'your datastore name' + +# get existing workspace +workspace = Workspace.from_config() + +# get Datastore from the workspace +dstore = Datastore.get(workspace, datastore_name) + +# create an in-memory Dataset on your local machine +dataset = Dataset.from_delimited_files(dstore.path('data/src/crime.csv')) +``` + +To consume Datasets across various scenarios in Azure Machine Learning service such as automated machine learning, model training and pipeline creation, you need to register the Datasets with your workspace. By doing so, you can also share and reuse the Datasets within your organization. + +```Python +dataset = dataset.register(workspace = workspace, + name = 'dataset_crime', + description = 'Training data' + ) +``` + +## Sampling + +Sampling can be particular useful with Datasets that are too large to efficiently analyze in full. It enables data scientists to work with a manageable amount of data to build and train machine learning models. At this time, the [`sample()`](https://docs.microsoft.com//python/api/azureml-core/azureml.core.dataset(class)?view=azure-ml-py#sample-sample-strategy--arguments-) method from the Dataset class supports Top N, Simple Random, and Stratified sampling strategies. + +After sampling, you can convert your sampled Dataset to pandas DataFrame for training. By using the native [`sample()`](https://docs.microsoft.com//python/api/azureml-core/azureml.core.dataset(class)?view=azure-ml-py#sample-sample-strategy--arguments-) method from the Dataset class, you will load the sampled data on the fly instead of loading full data into memory. + +### Top N sample + +For Top N sampling, the first n records of your Dataset are your sample. This is helpful if you are just trying to get an idea of what your data records look like or to see what fields are in your data. + +```Python +top_n_sample_dataset = dataset.sample('top_n', {'n': 5}) +top_n_sample_dataset.to_pandas_dataframe() +``` + +### Simple random sample + +In Simple Random sampling, every member of the data population has an equal chance of being selected as a part of the sample. In the `simple_random` sample strategy, the records from your Dataset are selected based on the probability specified and returns a modified Dataset. The seed parameter is optional. + +```Python +simple_random_sample_dataset = dataset.sample('simple_random', {'probability':0.3, 'seed': seed}) +simple_random_sample_dataset.to_pandas_dataframe() +``` + +### Stratified sample + +Stratified samples ensure that certain groups of a population are represented in the sample. In the `stratified` sample strategy, the population is divided into strata, or subgroups, based on similarities, and records are randomly selected from each strata according to the strata weights indicated by the `fractions` parameter. + +In the following example, we group each record by the specified columns, and include said record based on the strata X weight information in `fractions`. If a strata is not specified or the record cannot be grouped, the default weight to sample is 0. + +```Python +# take 50% of records with `Primary Type` as `THEFT` and 20% of records with `Primary Type` as `DECEPTIVE PRACTICE` into sample Dataset +fractions = {} +fractions[('THEFT',)] = 0.5 +fractions[('DECEPTIVE PRACTICE',)] = 0.2 + +sample_dataset = dataset.sample('stratified', {'columns': ['Primary Type'], 'fractions': fractions, 'seed': seed}) + +sample_dataset.to_pandas_dataframe() +``` + +## Explore with summary statistics + + Detect anomalies, missing values, or error counts with the [`get_profile()`](https://docs.microsoft.com/python/api/azureml-core/azureml.core.dataset.dataset?view=azure-ml-py#get-profile-arguments-none--generate-if-not-exist-true--workspace-none--compute-target-none-) method. This function gets the profile and summary statistics of your data, which in turn helps determine the necessary data preparation operations to apply. + +```Python +# get pre-calculated profile +# if there is no precalculated profile available or the precalculated profile is not up-to-date, this method will generate a new profile of the Dataset +dataset.get_profile() +``` + +||Type|Min|Max|Count|Missing Count|Not Missing Count|Percent missing|Error Count|Empty count|0.1% Quantile|1% Quantile|5% Quantile|25% Quantile|50% Quantile|75% Quantile|95% Quantile|99% Quantile|99.9% Quantile|Mean|Standard Deviation|Variance|Skewness|Kurtosis +-|----|---|---|-----|-------------|-----------------|---------------|-----------|-----------|-------------|-----------|-----------|------------|------------|------------|------------|------------|--------------|----|------------------|--------|--------|-------- +ID|FieldType.INTEGER|1.04986e+07|1.05351e+07|10.0|0.0|10.0|0.0|0.0|0.0|1.04986e+07|1.04992e+07|1.04986e+07|1.05166e+07|1.05209e+07|1.05259e+07|1.05351e+07|1.05351e+07|1.05351e+07|1.05195e+07|12302.7|1.51358e+08|-0.495701|-1.02814 +Case Number|FieldType.STRING|HZ239907|HZ278872|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Date|FieldType.DATE|2016-04-04 23:56:00+00:00|2016-04-15 17:00:00+00:00|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Block|FieldType.STRING|004XX S KILBOURN AVE|113XX S PRAIRIE AVE|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +IUCR|FieldType.INTEGER|810|1154|10.0|0.0|10.0|0.0|0.0|0.0|810|850|810|890|1136|1153|1154|1154|1154|1058.5|137.285|18847.2|-0.785501|-1.3543 +Primary Type|FieldType.STRING|DECEPTIVE PRACTICE|THEFT|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Description|FieldType.STRING|BOGUS CHECK|OVER $500|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Location Description|FieldType.STRING||SCHOOL, PUBLIC, BUILDING|10.0|0.0|10.0|0.0|0.0|1.0|||||||||||||| +Arrest|FieldType.BOOLEAN|False|False|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Domestic|FieldType.BOOLEAN|False|False|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Beat|FieldType.INTEGER|531|2433|10.0|0.0|10.0|0.0|0.0|0.0|531|531|531|614|1318.5|1911|2433|2433|2433|1371.1|692.094|478994|0.105418|-1.60684 +District|FieldType.INTEGER|5|24|10.0|0.0|10.0|0.0|0.0|0.0|5|5|5|6|13|19|24|24|24|13.5|6.94822|48.2778|0.0930109|-1.62325 +Ward|FieldType.INTEGER|1|48|10.0|0.0|10.0|0.0|0.0|0.0|1|5|1|9|22.5|40|48|48|48|24.5|16.2635|264.5|0.173723|-1.51271 +Community Area|FieldType.INTEGER|4|77|10.0|0.0|10.0|0.0|0.0|0.0|4|8.5|4|24|37.5|71|77|77|77|41.2|26.6366|709.511|0.112157|-1.73379 +FBI Code|FieldType.INTEGER|6|11|10.0|0.0|10.0|0.0|0.0|0.0|6|6|6|6|11|11|11|11|11|9.4|2.36643|5.6|-0.702685|-1.59582 +X Coordinate|FieldType.INTEGER|1.16309e+06|1.18336e+06|10.0|7.0|3.0|0.7|0.0|0.0|1.16309e+06|1.16309e+06|1.16309e+06|1.16401e+06|1.16678e+06|1.17921e+06|1.18336e+06|1.18336e+06|1.18336e+06|1.17108e+06|10793.5|1.165e+08|0.335126|-2.33333 +Y Coordinate|FieldType.INTEGER|1.8315e+06|1.908e+06|10.0|7.0|3.0|0.7|0.0|0.0|1.8315e+06|1.8315e+06|1.8315e+06|1.83614e+06|1.85005e+06|1.89352e+06|1.908e+06|1.908e+06|1.908e+06|1.86319e+06|39905.2|1.59243e+09|0.293465|-2.33333 +Year|FieldType.INTEGER|2016|2016|10.0|0.0|10.0|0.0|0.0|0.0|2016|2016|2016|2016|2016|2016|2016|2016|2016|2016|0|0|NaN|NaN +Updated On|FieldType.DATE|2016-05-11 15:48:00+00:00|2016-05-27 15:45:00+00:00|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Latitude|FieldType.DECIMAL|41.6928|41.9032|10.0|7.0|3.0|0.7|0.0|0.0|41.6928|41.6928|41.6928|41.7057|41.7441|41.8634|41.9032|41.9032|41.9032|41.78|0.109695|0.012033|0.292478|-2.33333 +Longitude|FieldType.DECIMAL|-87.6764|-87.6043|10.0|7.0|3.0|0.7|0.0|0.0|-87.6764|-87.6764|-87.6764|-87.6734|-87.6645|-87.6194|-87.6043|-87.6043|-87.6043|-87.6484|0.0386264|0.001492|0.344429|-2.33333 +Location|FieldType.STRING||(41.903206037, -87.676361925)|10.0|0.0|10.0|0.0|0.0|7.0|||||||||||||| + + +## Training with Dataset + +Now that you have registered your Dataset, you can call up the Dataset and convert it to pandas DataFrame or Spark DataFrame easily in your train.py script. + +```Python +# Sample train.py script +import azureml.core +import pandas as pd +import datetime +import shutil +from azureml.core import Workspace, Datastore, Dataset, Experiment, Run +from sklearn.model_selection import train_test_split +from azureml.core.compute import ComputeTarget, AmlCompute +from azureml.core.compute_target import ComputeTargetException +from sklearn.tree import DecisionTreeClassifier + +run = Run.get_context() +workspace = run.experiment.workspace + +# Access Dataset registered with the workspace by name +dataset_name = 'training_data' +dataset = Dataset.get(workspace=workspace, name=dataset_name) + +ds_def = dataset.get_definition() +dataset_val, dataset_train = ds_def.random_split(percentage=0.3) +y_df = dataset_train.keep_columns(['HasDetections']).to_pandas_dataframe() +x_df = dataset_train.drop_columns(['HasDetections']).to_pandas_dataframe() +y_val = dataset_val.keep_columns(['HasDetections']).to_pandas_dataframe() +x_val = dataset_val.drop_columns(['HasDetections']).to_pandas_dataframe() + +data = {"train": {"X": x_df, "y": y_df}, + "validation": {"X": x_val, "y": y_val}} + +clf = DecisionTreeClassifier().fit(data["train"]["X"], data["train"]["y"]) +print('Accuracy of Decision Tree classifier on training set: {:.2f}'.format(clf.score(x_df, y_df))) +print('Accuracy of Decision Tree classifier on validation set: {:.2f}'.format(clf.score(x_val, y_val))) +``` + +For an end-to-end tutorial, you may refer to [Dataset tutorial](datasets-tutorial.ipynb). You will learn how to: +- Explore and prepare data for training the model. +- Register the Dataset in your workspace for easy access in training. +- Take snapshots of data to ensure models can be trained with the same data every time. +- Use registered Dataset in your training script. +- Create and use multiple Dataset definitions to ensure that updates to the definition don't break existing pipelines/scripts. + + + +![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/datasets/README.png) \ No newline at end of file diff --git a/work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb b/work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb new file mode 100644 index 00000000..b28adf5c --- /dev/null +++ b/work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb @@ -0,0 +1,376 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial: Learn how to use Datasets in Azure ML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, you learn how to use Azure ML Datasets to train a regression model with the Azure Machine Learning SDK for Python. You will\n", + "\n", + "* Explore and prepare data for training the model\n", + "* Register the Dataset in your workspace to share it with others\n", + "* Create and use multiple Dataset definitions to ensure that updates to the definition don't break existing pipelines/scripts\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, you:\n", + "\n", + "☑ Setup a Python environment and import packages\n", + "\n", + "☑ Load the Titanic data from your Azure Blob Storage. (The [original data](https://www.kaggle.com/c/titanic/data) can be found on Kaggle)\n", + "\n", + "☑ Explore and cleanse the data to remove anomalies\n", + "\n", + "☑ Register the Dataset in your workspace, allowing you to use it in model training \n", + "\n", + "☑ Make changes to the dataset's definition without breaking the production model or the daily data pipeline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pre-requisites:\n", + "Skip to Set up your development environment to read through the notebook steps, or use the instructions below to get the notebook and run it on Azure Notebooks or your own notebook server. To run the notebook you will need:\n", + "\n", + "A Python 3.6 notebook server with the following installed:\n", + "* The Azure Machine Learning SDK for Python\n", + "* The Azure Machine Learning Data Prep SDK for Python\n", + "* The tutorial notebook\n", + "\n", + "Data and train.py script to store in your Azure Blob Storage Account.\n", + " * [Titanic data](./train-dataset/Titanic.csv)\n", + " * [train.py](./train-dataset/train.py)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create and register Datasets you need:\n", + "\n", + " * An Azure subscription. If you don\u00e2\u20ac\u2122t have an Azure subscription, create a free account before you begin. Try the [free or paid version of Azure Machine Learning service](https://aka.ms/AMLFree) today.\n", + "\n", + " * An Azure Machine Learning service workspace. See the [Create an Azure Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/setup-create-workspace?branch=release-build-amls).\n", + "\n", + " * The Azure Machine Learning SDK for Python (version 1.0.21 or later). To install or update to the latest version of the SDK, see [Install or update the SDK](https://docs.microsoft.com/python/api/overview/azure/ml/install?view=azure-ml-py).\n", + "\n", + "\n", + "For more information on how to set up your workspace, see the [Create an Azure Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/setup-create-workspace?branch=release-build-amls).\n", + "\n", + "The first part that needs to be done is setting up your python environment. You will need to import all of your python packages including `azureml.dataprep` and `azureml.core.dataset`. Then access your workspace through your Azure subscription and set up your compute target. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "import azureml.core\n", + "import pandas as pd\n", + "import logging\n", + "import os\n", + "import shutil\n", + "from azureml.core import Workspace, Datastore, Dataset\n", + "\n", + "# Get existing workspace from config.json file in the same folder as the tutorial notebook\n", + "# You can download the config file from your workspace\n", + "workspace = Workspace.from_config()\n", + "print(\"Workspace\")\n", + "print(workspace)\n", + "print(\"Compute targets\")\n", + "print(workspace.compute_targets)\n", + "\n", + "# Get compute target that has already been attached to the workspace\n", + "# Pick the right compute target from the list of computes attached to your workspace\n", + "\n", + "compute_target_name = 'datasetBugBash'\n", + "remote_compute_target = workspace.compute_targets[compute_target_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To load data to your dataset, you will access the data through your datastore. After you create your dataset, you can use `get_profile()` to see your data's statistics.\n", + "\n", + "We will now upload the [original data](https://www.kaggle.com/c/titanic/data) to the default datastore(blob) within your workspace.." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = workspace.get_default_datastore()\n", + "datastore.upload_files(files=['./train-dataset/Titanic.csv'],\n", + " target_path='train-dataset/',\n", + " overwrite=True,\n", + " show_progress=True)\n", + "\n", + "dataset = Dataset.auto_read_files(path=datastore.path('train-dataset/Titanic.csv'))\n", + "\n", + "#Display Dataset Profile of the Titanic Dataset\n", + "dataset.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To predict if a person survived the Titanic's sinking or not, the columns that are relevant to train the model are 'Survived','Pclass', 'Sex','SibSp', and 'Parch'. You can update your dataset's deinition and only keep these columns you will need. You will also need to convert values (\"male\",\"female\") in the \"Sex\" column to 0 or 1, because the algorithm in the train.py file will be using numeric values instead of strings.\n", + "\n", + "For more examples of preparing data with Datasets, see [Explore and prepare data with the Dataset class](aka.ms/azureml/howto/exploreandpreparedata)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds_def = dataset.get_definition()\n", + "ds_def = ds_def.keep_columns(['Survived','Pclass', 'Sex','SibSp', 'Parch', 'Fare'])\n", + "ds_def = ds_def.replace('Sex','male', 0)\n", + "ds_def = ds_def.replace('Sex','female', 1)\n", + "ds_def.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you have cleaned your data, you can register your dataset in your workspace. \n", + "\n", + "Registering your dataset allows you to easily have access to your processed data and share it with other people in your organization using the same workspace. It can be accessed in any notebook or script that is connected to your workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = dataset.update_definition(ds_def, 'Cleaned Data')\n", + "\n", + "dataset.generate_profile(compute_target='local').get_result()\n", + "\n", + "dataset_name = 'clean_Titanic_tutorial'\n", + "dataset = dataset.register(workspace=workspace,\n", + " name=dataset_name,\n", + " description='training dataset',\n", + " tags = {'year':'2019', 'month':'Apr'},\n", + " exist_ok=True)\n", + "workspace.datasets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The following code snippet will train your model locally using the train.py script." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Experiment, RunConfiguration\n", + "\n", + "experiment_name = 'training-datasets'\n", + "experiment = Experiment(workspace = workspace, name = experiment_name)\n", + "project_folder = './train-dataset/'\n", + "\n", + "# create a new RunConfig object\n", + "run_config = RunConfiguration()\n", + "\n", + "run_config.environment.python.user_managed_dependencies = True\n", + "\n", + "from azureml.core import Run\n", + "from azureml.core import ScriptRunConfig\n", + "\n", + "src = ScriptRunConfig(source_directory=project_folder, \n", + " script='train.py', \n", + " run_config=run_config) \n", + "run = experiment.submit(config=src)\n", + "run.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also use the same script with your dataset for your Pipeline's Python Script Step.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline, PipelineData\n", + "from azureml.pipeline.steps import PythonScriptStep\n", + "from azureml.data.data_reference import DataReference\n", + "\n", + "trainStep = PythonScriptStep(script_name=\"train.py\",\n", + " compute_target=remote_compute_target,\n", + " source_directory=project_folder)\n", + "\n", + "pipeline = Pipeline(workspace=workspace,\n", + " steps=trainStep)\n", + "\n", + "pipeline_run = experiment.submit(pipeline)\n", + "pipeline_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can make changes to the dataset's definition without breaking the production model or the daily data pipeline. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can call get_definitions to see that there are several versions. After each change to a dataset's version, another one is added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset.get_definitions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.get(workspace=workspace, name=dataset_name)\n", + "ds_def = dataset.get_definition()\n", + "ds_def = ds_def.drop_columns(['Fare'])\n", + "dataset = dataset.update_definition(ds_def, 'Dropping Fare as PClass and Fare are strongly correlated')\n", + "dataset.generate_profile(compute_target='local').get_result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dataset definitions can be deprecated when usage is no longer recommended and a replacement is available. When a deprecated dataset definition is used in an AML Experimentation/Pipeline scenario, a warning message gets returned but execution will not be blocked. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Deprecate dataset definition 1 by the 2nd definition\n", + "ds_def = dataset.get_definition('1')\n", + "ds_def.deprecate(deprecate_by_dataset_id=dataset._id, deprecated_by_definition_version='2')\n", + "dataset.get_definitions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dataset definitions can be archived when definitions are not supposed to be used for any reasons (such as underlying data no longer available). When an archived dataset definition is used in an AML Experimentation/Pipeline scenario, execution will be blocked with error. No further actions can be performed on archived Dataset definitions, but the references will be kept intact. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Archive the deprecated dataset definition #1\n", + "ds_def = dataset.get_definition('1')\n", + "ds_def.archive()\n", + "dataset.get_definitions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also reactivate any defition that you archived for later use." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds_def = dataset.get_definition('1')\n", + "ds_def.reactivate()\n", + "dataset.get_definitions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You have now finished using a dataset from start to finish of your experiment!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/datasets/datasets-tutorial/datasets-tutorial.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "cforbe" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv b/work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv new file mode 100644 index 00000000..5cc466e9 --- /dev/null +++ b/work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv @@ -0,0 +1,892 @@ +PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked +1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S +2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,PC 17599,71.2833,C85,C +3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,STON/O2. 3101282,7.925,,S +4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,113803,53.1,C123,S +5,0,3,"Allen, Mr. William Henry",male,35,0,0,373450,8.05,,S +6,0,3,"Moran, Mr. James",male,,0,0,330877,8.4583,,Q +7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,17463,51.8625,E46,S +8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,349909,21.075,,S +9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,347742,11.1333,,S +10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,237736,30.0708,,C +11,1,3,"Sandstrom, Miss. Marguerite Rut",female,4,1,1,PP 9549,16.7,G6,S +12,1,1,"Bonnell, Miss. Elizabeth",female,58,0,0,113783,26.55,C103,S +13,0,3,"Saundercock, Mr. William Henry",male,20,0,0,A/5. 2151,8.05,,S +14,0,3,"Andersson, Mr. Anders Johan",male,39,1,5,347082,31.275,,S +15,0,3,"Vestrom, Miss. Hulda Amanda Adolfina",female,14,0,0,350406,7.8542,,S +16,1,2,"Hewlett, Mrs. (Mary D Kingcome) ",female,55,0,0,248706,16,,S +17,0,3,"Rice, Master. Eugene",male,2,4,1,382652,29.125,,Q +18,1,2,"Williams, Mr. Charles Eugene",male,,0,0,244373,13,,S +19,0,3,"Vander Planke, Mrs. Julius (Emelia Maria Vandemoortele)",female,31,1,0,345763,18,,S +20,1,3,"Masselmani, Mrs. Fatima",female,,0,0,2649,7.225,,C +21,0,2,"Fynney, Mr. Joseph J",male,35,0,0,239865,26,,S +22,1,2,"Beesley, Mr. Lawrence",male,34,0,0,248698,13,D56,S +23,1,3,"McGowan, Miss. Anna ""Annie""",female,15,0,0,330923,8.0292,,Q +24,1,1,"Sloper, Mr. William Thompson",male,28,0,0,113788,35.5,A6,S +25,0,3,"Palsson, Miss. Torborg Danira",female,8,3,1,349909,21.075,,S +26,1,3,"Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)",female,38,1,5,347077,31.3875,,S +27,0,3,"Emir, Mr. Farred Chehab",male,,0,0,2631,7.225,,C +28,0,1,"Fortune, Mr. Charles Alexander",male,19,3,2,19950,263,C23 C25 C27,S +29,1,3,"O'Dwyer, Miss. Ellen ""Nellie""",female,,0,0,330959,7.8792,,Q +30,0,3,"Todoroff, Mr. Lalio",male,,0,0,349216,7.8958,,S +31,0,1,"Uruchurtu, Don. Manuel E",male,40,0,0,PC 17601,27.7208,,C +32,1,1,"Spencer, Mrs. William Augustus (Marie Eugenie)",female,,1,0,PC 17569,146.5208,B78,C +33,1,3,"Glynn, Miss. Mary Agatha",female,,0,0,335677,7.75,,Q +34,0,2,"Wheadon, Mr. Edward H",male,66,0,0,C.A. 24579,10.5,,S +35,0,1,"Meyer, Mr. Edgar Joseph",male,28,1,0,PC 17604,82.1708,,C +36,0,1,"Holverson, Mr. Alexander Oskar",male,42,1,0,113789,52,,S +37,1,3,"Mamee, Mr. Hanna",male,,0,0,2677,7.2292,,C +38,0,3,"Cann, Mr. Ernest Charles",male,21,0,0,A./5. 2152,8.05,,S +39,0,3,"Vander Planke, Miss. Augusta Maria",female,18,2,0,345764,18,,S +40,1,3,"Nicola-Yarred, Miss. Jamila",female,14,1,0,2651,11.2417,,C +41,0,3,"Ahlin, Mrs. Johan (Johanna Persdotter Larsson)",female,40,1,0,7546,9.475,,S +42,0,2,"Turpin, Mrs. William John Robert (Dorothy Ann Wonnacott)",female,27,1,0,11668,21,,S +43,0,3,"Kraeff, Mr. Theodor",male,,0,0,349253,7.8958,,C +44,1,2,"Laroche, Miss. Simonne Marie Anne Andree",female,3,1,2,SC/Paris 2123,41.5792,,C +45,1,3,"Devaney, Miss. Margaret Delia",female,19,0,0,330958,7.8792,,Q +46,0,3,"Rogers, Mr. William John",male,,0,0,S.C./A.4. 23567,8.05,,S +47,0,3,"Lennon, Mr. Denis",male,,1,0,370371,15.5,,Q +48,1,3,"O'Driscoll, Miss. Bridget",female,,0,0,14311,7.75,,Q +49,0,3,"Samaan, Mr. Youssef",male,,2,0,2662,21.6792,,C +50,0,3,"Arnold-Franchi, Mrs. Josef (Josefine Franchi)",female,18,1,0,349237,17.8,,S +51,0,3,"Panula, Master. Juha Niilo",male,7,4,1,3101295,39.6875,,S +52,0,3,"Nosworthy, Mr. Richard Cater",male,21,0,0,A/4. 39886,7.8,,S +53,1,1,"Harper, Mrs. Henry Sleeper (Myna Haxtun)",female,49,1,0,PC 17572,76.7292,D33,C +54,1,2,"Faunthorpe, Mrs. Lizzie (Elizabeth Anne Wilkinson)",female,29,1,0,2926,26,,S +55,0,1,"Ostby, Mr. Engelhart Cornelius",male,65,0,1,113509,61.9792,B30,C +56,1,1,"Woolner, Mr. Hugh",male,,0,0,19947,35.5,C52,S +57,1,2,"Rugg, Miss. Emily",female,21,0,0,C.A. 31026,10.5,,S +58,0,3,"Novel, Mr. Mansouer",male,28.5,0,0,2697,7.2292,,C +59,1,2,"West, Miss. Constance Mirium",female,5,1,2,C.A. 34651,27.75,,S +60,0,3,"Goodwin, Master. William Frederick",male,11,5,2,CA 2144,46.9,,S +61,0,3,"Sirayanian, Mr. Orsen",male,22,0,0,2669,7.2292,,C +62,1,1,"Icard, Miss. Amelie",female,38,0,0,113572,80,B28, +63,0,1,"Harris, Mr. Henry Birkhardt",male,45,1,0,36973,83.475,C83,S +64,0,3,"Skoog, Master. Harald",male,4,3,2,347088,27.9,,S +65,0,1,"Stewart, Mr. Albert A",male,,0,0,PC 17605,27.7208,,C +66,1,3,"Moubarek, Master. Gerios",male,,1,1,2661,15.2458,,C +67,1,2,"Nye, Mrs. (Elizabeth Ramell)",female,29,0,0,C.A. 29395,10.5,F33,S +68,0,3,"Crease, Mr. Ernest James",male,19,0,0,S.P. 3464,8.1583,,S +69,1,3,"Andersson, Miss. Erna Alexandra",female,17,4,2,3101281,7.925,,S +70,0,3,"Kink, Mr. Vincenz",male,26,2,0,315151,8.6625,,S +71,0,2,"Jenkin, Mr. Stephen Curnow",male,32,0,0,C.A. 33111,10.5,,S +72,0,3,"Goodwin, Miss. Lillian Amy",female,16,5,2,CA 2144,46.9,,S +73,0,2,"Hood, Mr. Ambrose Jr",male,21,0,0,S.O.C. 14879,73.5,,S +74,0,3,"Chronopoulos, Mr. Apostolos",male,26,1,0,2680,14.4542,,C +75,1,3,"Bing, Mr. Lee",male,32,0,0,1601,56.4958,,S +76,0,3,"Moen, Mr. Sigurd Hansen",male,25,0,0,348123,7.65,F G73,S +77,0,3,"Staneff, Mr. Ivan",male,,0,0,349208,7.8958,,S +78,0,3,"Moutal, Mr. Rahamin Haim",male,,0,0,374746,8.05,,S +79,1,2,"Caldwell, Master. Alden Gates",male,0.83,0,2,248738,29,,S +80,1,3,"Dowdell, Miss. Elizabeth",female,30,0,0,364516,12.475,,S +81,0,3,"Waelens, Mr. Achille",male,22,0,0,345767,9,,S +82,1,3,"Sheerlinck, Mr. Jan Baptist",male,29,0,0,345779,9.5,,S +83,1,3,"McDermott, Miss. Brigdet Delia",female,,0,0,330932,7.7875,,Q +84,0,1,"Carrau, Mr. Francisco M",male,28,0,0,113059,47.1,,S +85,1,2,"Ilett, Miss. Bertha",female,17,0,0,SO/C 14885,10.5,,S +86,1,3,"Backstrom, Mrs. Karl Alfred (Maria Mathilda Gustafsson)",female,33,3,0,3101278,15.85,,S +87,0,3,"Ford, Mr. William Neal",male,16,1,3,W./C. 6608,34.375,,S +88,0,3,"Slocovski, Mr. Selman Francis",male,,0,0,SOTON/OQ 392086,8.05,,S +89,1,1,"Fortune, Miss. Mabel Helen",female,23,3,2,19950,263,C23 C25 C27,S +90,0,3,"Celotti, Mr. Francesco",male,24,0,0,343275,8.05,,S +91,0,3,"Christmann, Mr. Emil",male,29,0,0,343276,8.05,,S +92,0,3,"Andreasson, Mr. Paul Edvin",male,20,0,0,347466,7.8542,,S +93,0,1,"Chaffee, Mr. Herbert Fuller",male,46,1,0,W.E.P. 5734,61.175,E31,S +94,0,3,"Dean, Mr. Bertram Frank",male,26,1,2,C.A. 2315,20.575,,S +95,0,3,"Coxon, Mr. Daniel",male,59,0,0,364500,7.25,,S +96,0,3,"Shorney, Mr. Charles Joseph",male,,0,0,374910,8.05,,S +97,0,1,"Goldschmidt, Mr. George B",male,71,0,0,PC 17754,34.6542,A5,C +98,1,1,"Greenfield, Mr. William Bertram",male,23,0,1,PC 17759,63.3583,D10 D12,C +99,1,2,"Doling, Mrs. John T (Ada Julia Bone)",female,34,0,1,231919,23,,S +100,0,2,"Kantor, Mr. Sinai",male,34,1,0,244367,26,,S +101,0,3,"Petranec, Miss. Matilda",female,28,0,0,349245,7.8958,,S +102,0,3,"Petroff, Mr. Pastcho (""Pentcho"")",male,,0,0,349215,7.8958,,S +103,0,1,"White, Mr. Richard Frasar",male,21,0,1,35281,77.2875,D26,S +104,0,3,"Johansson, Mr. Gustaf Joel",male,33,0,0,7540,8.6542,,S +105,0,3,"Gustafsson, Mr. Anders Vilhelm",male,37,2,0,3101276,7.925,,S +106,0,3,"Mionoff, Mr. Stoytcho",male,28,0,0,349207,7.8958,,S +107,1,3,"Salkjelsvik, Miss. Anna Kristine",female,21,0,0,343120,7.65,,S +108,1,3,"Moss, Mr. Albert Johan",male,,0,0,312991,7.775,,S +109,0,3,"Rekic, Mr. Tido",male,38,0,0,349249,7.8958,,S +110,1,3,"Moran, Miss. Bertha",female,,1,0,371110,24.15,,Q +111,0,1,"Porter, Mr. Walter Chamberlain",male,47,0,0,110465,52,C110,S +112,0,3,"Zabour, Miss. Hileni",female,14.5,1,0,2665,14.4542,,C +113,0,3,"Barton, Mr. David John",male,22,0,0,324669,8.05,,S +114,0,3,"Jussila, Miss. Katriina",female,20,1,0,4136,9.825,,S +115,0,3,"Attalah, Miss. Malake",female,17,0,0,2627,14.4583,,C +116,0,3,"Pekoniemi, Mr. Edvard",male,21,0,0,STON/O 2. 3101294,7.925,,S +117,0,3,"Connors, Mr. Patrick",male,70.5,0,0,370369,7.75,,Q +118,0,2,"Turpin, Mr. William John Robert",male,29,1,0,11668,21,,S +119,0,1,"Baxter, Mr. Quigg Edmond",male,24,0,1,PC 17558,247.5208,B58 B60,C +120,0,3,"Andersson, Miss. Ellis Anna Maria",female,2,4,2,347082,31.275,,S +121,0,2,"Hickman, Mr. Stanley George",male,21,2,0,S.O.C. 14879,73.5,,S +122,0,3,"Moore, Mr. Leonard Charles",male,,0,0,A4. 54510,8.05,,S +123,0,2,"Nasser, Mr. Nicholas",male,32.5,1,0,237736,30.0708,,C +124,1,2,"Webber, Miss. Susan",female,32.5,0,0,27267,13,E101,S +125,0,1,"White, Mr. Percival Wayland",male,54,0,1,35281,77.2875,D26,S +126,1,3,"Nicola-Yarred, Master. Elias",male,12,1,0,2651,11.2417,,C +127,0,3,"McMahon, Mr. Martin",male,,0,0,370372,7.75,,Q +128,1,3,"Madsen, Mr. Fridtjof Arne",male,24,0,0,C 17369,7.1417,,S +129,1,3,"Peter, Miss. Anna",female,,1,1,2668,22.3583,F E69,C +130,0,3,"Ekstrom, Mr. Johan",male,45,0,0,347061,6.975,,S +131,0,3,"Drazenoic, Mr. Jozef",male,33,0,0,349241,7.8958,,C +132,0,3,"Coelho, Mr. Domingos Fernandeo",male,20,0,0,SOTON/O.Q. 3101307,7.05,,S +133,0,3,"Robins, Mrs. Alexander A (Grace Charity Laury)",female,47,1,0,A/5. 3337,14.5,,S +134,1,2,"Weisz, Mrs. Leopold (Mathilde Francoise Pede)",female,29,1,0,228414,26,,S +135,0,2,"Sobey, Mr. Samuel James Hayden",male,25,0,0,C.A. 29178,13,,S +136,0,2,"Richard, Mr. Emile",male,23,0,0,SC/PARIS 2133,15.0458,,C +137,1,1,"Newsom, Miss. Helen Monypeny",female,19,0,2,11752,26.2833,D47,S +138,0,1,"Futrelle, Mr. Jacques Heath",male,37,1,0,113803,53.1,C123,S +139,0,3,"Osen, Mr. Olaf Elon",male,16,0,0,7534,9.2167,,S +140,0,1,"Giglio, Mr. Victor",male,24,0,0,PC 17593,79.2,B86,C +141,0,3,"Boulos, Mrs. Joseph (Sultana)",female,,0,2,2678,15.2458,,C +142,1,3,"Nysten, Miss. Anna Sofia",female,22,0,0,347081,7.75,,S +143,1,3,"Hakkarainen, Mrs. Pekka Pietari (Elin Matilda Dolck)",female,24,1,0,STON/O2. 3101279,15.85,,S +144,0,3,"Burke, Mr. Jeremiah",male,19,0,0,365222,6.75,,Q +145,0,2,"Andrew, Mr. Edgardo Samuel",male,18,0,0,231945,11.5,,S +146,0,2,"Nicholls, Mr. Joseph Charles",male,19,1,1,C.A. 33112,36.75,,S +147,1,3,"Andersson, Mr. August Edvard (""Wennerstrom"")",male,27,0,0,350043,7.7958,,S +148,0,3,"Ford, Miss. Robina Maggie ""Ruby""",female,9,2,2,W./C. 6608,34.375,,S +149,0,2,"Navratil, Mr. Michel (""Louis M Hoffman"")",male,36.5,0,2,230080,26,F2,S +150,0,2,"Byles, Rev. Thomas Roussel Davids",male,42,0,0,244310,13,,S +151,0,2,"Bateman, Rev. Robert James",male,51,0,0,S.O.P. 1166,12.525,,S +152,1,1,"Pears, Mrs. Thomas (Edith Wearne)",female,22,1,0,113776,66.6,C2,S +153,0,3,"Meo, Mr. Alfonzo",male,55.5,0,0,A.5. 11206,8.05,,S +154,0,3,"van Billiard, Mr. Austin Blyler",male,40.5,0,2,A/5. 851,14.5,,S +155,0,3,"Olsen, Mr. Ole Martin",male,,0,0,Fa 265302,7.3125,,S +156,0,1,"Williams, Mr. Charles Duane",male,51,0,1,PC 17597,61.3792,,C +157,1,3,"Gilnagh, Miss. Katherine ""Katie""",female,16,0,0,35851,7.7333,,Q +158,0,3,"Corn, Mr. Harry",male,30,0,0,SOTON/OQ 392090,8.05,,S +159,0,3,"Smiljanic, Mr. Mile",male,,0,0,315037,8.6625,,S +160,0,3,"Sage, Master. Thomas Henry",male,,8,2,CA. 2343,69.55,,S +161,0,3,"Cribb, Mr. John Hatfield",male,44,0,1,371362,16.1,,S +162,1,2,"Watt, Mrs. James (Elizabeth ""Bessie"" Inglis Milne)",female,40,0,0,C.A. 33595,15.75,,S +163,0,3,"Bengtsson, Mr. John Viktor",male,26,0,0,347068,7.775,,S +164,0,3,"Calic, Mr. Jovo",male,17,0,0,315093,8.6625,,S +165,0,3,"Panula, Master. Eino Viljami",male,1,4,1,3101295,39.6875,,S +166,1,3,"Goldsmith, Master. Frank John William ""Frankie""",male,9,0,2,363291,20.525,,S +167,1,1,"Chibnall, Mrs. (Edith Martha Bowerman)",female,,0,1,113505,55,E33,S +168,0,3,"Skoog, Mrs. William (Anna Bernhardina Karlsson)",female,45,1,4,347088,27.9,,S +169,0,1,"Baumann, Mr. John D",male,,0,0,PC 17318,25.925,,S +170,0,3,"Ling, Mr. Lee",male,28,0,0,1601,56.4958,,S +171,0,1,"Van der hoef, Mr. Wyckoff",male,61,0,0,111240,33.5,B19,S +172,0,3,"Rice, Master. Arthur",male,4,4,1,382652,29.125,,Q +173,1,3,"Johnson, Miss. Eleanor Ileen",female,1,1,1,347742,11.1333,,S +174,0,3,"Sivola, Mr. Antti Wilhelm",male,21,0,0,STON/O 2. 3101280,7.925,,S +175,0,1,"Smith, Mr. James Clinch",male,56,0,0,17764,30.6958,A7,C +176,0,3,"Klasen, Mr. Klas Albin",male,18,1,1,350404,7.8542,,S +177,0,3,"Lefebre, Master. Henry Forbes",male,,3,1,4133,25.4667,,S +178,0,1,"Isham, Miss. Ann Elizabeth",female,50,0,0,PC 17595,28.7125,C49,C +179,0,2,"Hale, Mr. Reginald",male,30,0,0,250653,13,,S +180,0,3,"Leonard, Mr. Lionel",male,36,0,0,LINE,0,,S +181,0,3,"Sage, Miss. Constance Gladys",female,,8,2,CA. 2343,69.55,,S +182,0,2,"Pernot, Mr. Rene",male,,0,0,SC/PARIS 2131,15.05,,C +183,0,3,"Asplund, Master. Clarence Gustaf Hugo",male,9,4,2,347077,31.3875,,S +184,1,2,"Becker, Master. Richard F",male,1,2,1,230136,39,F4,S +185,1,3,"Kink-Heilmann, Miss. Luise Gretchen",female,4,0,2,315153,22.025,,S +186,0,1,"Rood, Mr. Hugh Roscoe",male,,0,0,113767,50,A32,S +187,1,3,"O'Brien, Mrs. Thomas (Johanna ""Hannah"" Godfrey)",female,,1,0,370365,15.5,,Q +188,1,1,"Romaine, Mr. Charles Hallace (""Mr C Rolmane"")",male,45,0,0,111428,26.55,,S +189,0,3,"Bourke, Mr. John",male,40,1,1,364849,15.5,,Q +190,0,3,"Turcin, Mr. Stjepan",male,36,0,0,349247,7.8958,,S +191,1,2,"Pinsky, Mrs. (Rosa)",female,32,0,0,234604,13,,S +192,0,2,"Carbines, Mr. William",male,19,0,0,28424,13,,S +193,1,3,"Andersen-Jensen, Miss. Carla Christine Nielsine",female,19,1,0,350046,7.8542,,S +194,1,2,"Navratil, Master. Michel M",male,3,1,1,230080,26,F2,S +195,1,1,"Brown, Mrs. James Joseph (Margaret Tobin)",female,44,0,0,PC 17610,27.7208,B4,C +196,1,1,"Lurette, Miss. Elise",female,58,0,0,PC 17569,146.5208,B80,C +197,0,3,"Mernagh, Mr. Robert",male,,0,0,368703,7.75,,Q +198,0,3,"Olsen, Mr. Karl Siegwart Andreas",male,42,0,1,4579,8.4042,,S +199,1,3,"Madigan, Miss. Margaret ""Maggie""",female,,0,0,370370,7.75,,Q +200,0,2,"Yrois, Miss. Henriette (""Mrs Harbeck"")",female,24,0,0,248747,13,,S +201,0,3,"Vande Walle, Mr. Nestor Cyriel",male,28,0,0,345770,9.5,,S +202,0,3,"Sage, Mr. Frederick",male,,8,2,CA. 2343,69.55,,S +203,0,3,"Johanson, Mr. Jakob Alfred",male,34,0,0,3101264,6.4958,,S +204,0,3,"Youseff, Mr. Gerious",male,45.5,0,0,2628,7.225,,C +205,1,3,"Cohen, Mr. Gurshon ""Gus""",male,18,0,0,A/5 3540,8.05,,S +206,0,3,"Strom, Miss. Telma Matilda",female,2,0,1,347054,10.4625,G6,S +207,0,3,"Backstrom, Mr. Karl Alfred",male,32,1,0,3101278,15.85,,S +208,1,3,"Albimona, Mr. Nassef Cassem",male,26,0,0,2699,18.7875,,C +209,1,3,"Carr, Miss. Helen ""Ellen""",female,16,0,0,367231,7.75,,Q +210,1,1,"Blank, Mr. Henry",male,40,0,0,112277,31,A31,C +211,0,3,"Ali, Mr. Ahmed",male,24,0,0,SOTON/O.Q. 3101311,7.05,,S +212,1,2,"Cameron, Miss. Clear Annie",female,35,0,0,F.C.C. 13528,21,,S +213,0,3,"Perkin, Mr. John Henry",male,22,0,0,A/5 21174,7.25,,S +214,0,2,"Givard, Mr. Hans Kristensen",male,30,0,0,250646,13,,S +215,0,3,"Kiernan, Mr. Philip",male,,1,0,367229,7.75,,Q +216,1,1,"Newell, Miss. Madeleine",female,31,1,0,35273,113.275,D36,C +217,1,3,"Honkanen, Miss. Eliina",female,27,0,0,STON/O2. 3101283,7.925,,S +218,0,2,"Jacobsohn, Mr. Sidney Samuel",male,42,1,0,243847,27,,S +219,1,1,"Bazzani, Miss. Albina",female,32,0,0,11813,76.2917,D15,C +220,0,2,"Harris, Mr. Walter",male,30,0,0,W/C 14208,10.5,,S +221,1,3,"Sunderland, Mr. Victor Francis",male,16,0,0,SOTON/OQ 392089,8.05,,S +222,0,2,"Bracken, Mr. James H",male,27,0,0,220367,13,,S +223,0,3,"Green, Mr. George Henry",male,51,0,0,21440,8.05,,S +224,0,3,"Nenkoff, Mr. Christo",male,,0,0,349234,7.8958,,S +225,1,1,"Hoyt, Mr. Frederick Maxfield",male,38,1,0,19943,90,C93,S +226,0,3,"Berglund, Mr. Karl Ivar Sven",male,22,0,0,PP 4348,9.35,,S +227,1,2,"Mellors, Mr. William John",male,19,0,0,SW/PP 751,10.5,,S +228,0,3,"Lovell, Mr. John Hall (""Henry"")",male,20.5,0,0,A/5 21173,7.25,,S +229,0,2,"Fahlstrom, Mr. Arne Jonas",male,18,0,0,236171,13,,S +230,0,3,"Lefebre, Miss. Mathilde",female,,3,1,4133,25.4667,,S +231,1,1,"Harris, Mrs. Henry Birkhardt (Irene Wallach)",female,35,1,0,36973,83.475,C83,S +232,0,3,"Larsson, Mr. Bengt Edvin",male,29,0,0,347067,7.775,,S +233,0,2,"Sjostedt, Mr. Ernst Adolf",male,59,0,0,237442,13.5,,S +234,1,3,"Asplund, Miss. Lillian Gertrud",female,5,4,2,347077,31.3875,,S +235,0,2,"Leyson, Mr. Robert William Norman",male,24,0,0,C.A. 29566,10.5,,S +236,0,3,"Harknett, Miss. Alice Phoebe",female,,0,0,W./C. 6609,7.55,,S +237,0,2,"Hold, Mr. Stephen",male,44,1,0,26707,26,,S +238,1,2,"Collyer, Miss. Marjorie ""Lottie""",female,8,0,2,C.A. 31921,26.25,,S +239,0,2,"Pengelly, Mr. Frederick William",male,19,0,0,28665,10.5,,S +240,0,2,"Hunt, Mr. George Henry",male,33,0,0,SCO/W 1585,12.275,,S +241,0,3,"Zabour, Miss. Thamine",female,,1,0,2665,14.4542,,C +242,1,3,"Murphy, Miss. Katherine ""Kate""",female,,1,0,367230,15.5,,Q +243,0,2,"Coleridge, Mr. Reginald Charles",male,29,0,0,W./C. 14263,10.5,,S +244,0,3,"Maenpaa, Mr. Matti Alexanteri",male,22,0,0,STON/O 2. 3101275,7.125,,S +245,0,3,"Attalah, Mr. Sleiman",male,30,0,0,2694,7.225,,C +246,0,1,"Minahan, Dr. William Edward",male,44,2,0,19928,90,C78,Q +247,0,3,"Lindahl, Miss. Agda Thorilda Viktoria",female,25,0,0,347071,7.775,,S +248,1,2,"Hamalainen, Mrs. William (Anna)",female,24,0,2,250649,14.5,,S +249,1,1,"Beckwith, Mr. Richard Leonard",male,37,1,1,11751,52.5542,D35,S +250,0,2,"Carter, Rev. Ernest Courtenay",male,54,1,0,244252,26,,S +251,0,3,"Reed, Mr. James George",male,,0,0,362316,7.25,,S +252,0,3,"Strom, Mrs. Wilhelm (Elna Matilda Persson)",female,29,1,1,347054,10.4625,G6,S +253,0,1,"Stead, Mr. William Thomas",male,62,0,0,113514,26.55,C87,S +254,0,3,"Lobb, Mr. William Arthur",male,30,1,0,A/5. 3336,16.1,,S +255,0,3,"Rosblom, Mrs. Viktor (Helena Wilhelmina)",female,41,0,2,370129,20.2125,,S +256,1,3,"Touma, Mrs. Darwis (Hanne Youssef Razi)",female,29,0,2,2650,15.2458,,C +257,1,1,"Thorne, Mrs. Gertrude Maybelle",female,,0,0,PC 17585,79.2,,C +258,1,1,"Cherry, Miss. Gladys",female,30,0,0,110152,86.5,B77,S +259,1,1,"Ward, Miss. Anna",female,35,0,0,PC 17755,512.3292,,C +260,1,2,"Parrish, Mrs. (Lutie Davis)",female,50,0,1,230433,26,,S +261,0,3,"Smith, Mr. Thomas",male,,0,0,384461,7.75,,Q +262,1,3,"Asplund, Master. Edvin Rojj Felix",male,3,4,2,347077,31.3875,,S +263,0,1,"Taussig, Mr. Emil",male,52,1,1,110413,79.65,E67,S +264,0,1,"Harrison, Mr. William",male,40,0,0,112059,0,B94,S +265,0,3,"Henry, Miss. Delia",female,,0,0,382649,7.75,,Q +266,0,2,"Reeves, Mr. David",male,36,0,0,C.A. 17248,10.5,,S +267,0,3,"Panula, Mr. Ernesti Arvid",male,16,4,1,3101295,39.6875,,S +268,1,3,"Persson, Mr. Ernst Ulrik",male,25,1,0,347083,7.775,,S +269,1,1,"Graham, Mrs. William Thompson (Edith Junkins)",female,58,0,1,PC 17582,153.4625,C125,S +270,1,1,"Bissette, Miss. Amelia",female,35,0,0,PC 17760,135.6333,C99,S +271,0,1,"Cairns, Mr. Alexander",male,,0,0,113798,31,,S +272,1,3,"Tornquist, Mr. William Henry",male,25,0,0,LINE,0,,S +273,1,2,"Mellinger, Mrs. (Elizabeth Anne Maidment)",female,41,0,1,250644,19.5,,S +274,0,1,"Natsch, Mr. Charles H",male,37,0,1,PC 17596,29.7,C118,C +275,1,3,"Healy, Miss. Hanora ""Nora""",female,,0,0,370375,7.75,,Q +276,1,1,"Andrews, Miss. Kornelia Theodosia",female,63,1,0,13502,77.9583,D7,S +277,0,3,"Lindblom, Miss. Augusta Charlotta",female,45,0,0,347073,7.75,,S +278,0,2,"Parkes, Mr. Francis ""Frank""",male,,0,0,239853,0,,S +279,0,3,"Rice, Master. Eric",male,7,4,1,382652,29.125,,Q +280,1,3,"Abbott, Mrs. Stanton (Rosa Hunt)",female,35,1,1,C.A. 2673,20.25,,S +281,0,3,"Duane, Mr. Frank",male,65,0,0,336439,7.75,,Q +282,0,3,"Olsson, Mr. Nils Johan Goransson",male,28,0,0,347464,7.8542,,S +283,0,3,"de Pelsmaeker, Mr. Alfons",male,16,0,0,345778,9.5,,S +284,1,3,"Dorking, Mr. Edward Arthur",male,19,0,0,A/5. 10482,8.05,,S +285,0,1,"Smith, Mr. Richard William",male,,0,0,113056,26,A19,S +286,0,3,"Stankovic, Mr. Ivan",male,33,0,0,349239,8.6625,,C +287,1,3,"de Mulder, Mr. Theodore",male,30,0,0,345774,9.5,,S +288,0,3,"Naidenoff, Mr. Penko",male,22,0,0,349206,7.8958,,S +289,1,2,"Hosono, Mr. Masabumi",male,42,0,0,237798,13,,S +290,1,3,"Connolly, Miss. Kate",female,22,0,0,370373,7.75,,Q +291,1,1,"Barber, Miss. Ellen ""Nellie""",female,26,0,0,19877,78.85,,S +292,1,1,"Bishop, Mrs. Dickinson H (Helen Walton)",female,19,1,0,11967,91.0792,B49,C +293,0,2,"Levy, Mr. Rene Jacques",male,36,0,0,SC/Paris 2163,12.875,D,C +294,0,3,"Haas, Miss. Aloisia",female,24,0,0,349236,8.85,,S +295,0,3,"Mineff, Mr. Ivan",male,24,0,0,349233,7.8958,,S +296,0,1,"Lewy, Mr. Ervin G",male,,0,0,PC 17612,27.7208,,C +297,0,3,"Hanna, Mr. Mansour",male,23.5,0,0,2693,7.2292,,C +298,0,1,"Allison, Miss. Helen Loraine",female,2,1,2,113781,151.55,C22 C26,S +299,1,1,"Saalfeld, Mr. Adolphe",male,,0,0,19988,30.5,C106,S +300,1,1,"Baxter, Mrs. James (Helene DeLaudeniere Chaput)",female,50,0,1,PC 17558,247.5208,B58 B60,C +301,1,3,"Kelly, Miss. Anna Katherine ""Annie Kate""",female,,0,0,9234,7.75,,Q +302,1,3,"McCoy, Mr. Bernard",male,,2,0,367226,23.25,,Q +303,0,3,"Johnson, Mr. William Cahoone Jr",male,19,0,0,LINE,0,,S +304,1,2,"Keane, Miss. Nora A",female,,0,0,226593,12.35,E101,Q +305,0,3,"Williams, Mr. Howard Hugh ""Harry""",male,,0,0,A/5 2466,8.05,,S +306,1,1,"Allison, Master. Hudson Trevor",male,0.92,1,2,113781,151.55,C22 C26,S +307,1,1,"Fleming, Miss. Margaret",female,,0,0,17421,110.8833,,C +308,1,1,"Penasco y Castellana, Mrs. Victor de Satode (Maria Josefa Perez de Soto y Vallejo)",female,17,1,0,PC 17758,108.9,C65,C +309,0,2,"Abelson, Mr. Samuel",male,30,1,0,P/PP 3381,24,,C +310,1,1,"Francatelli, Miss. Laura Mabel",female,30,0,0,PC 17485,56.9292,E36,C +311,1,1,"Hays, Miss. Margaret Bechstein",female,24,0,0,11767,83.1583,C54,C +312,1,1,"Ryerson, Miss. Emily Borie",female,18,2,2,PC 17608,262.375,B57 B59 B63 B66,C +313,0,2,"Lahtinen, Mrs. William (Anna Sylfven)",female,26,1,1,250651,26,,S +314,0,3,"Hendekovic, Mr. Ignjac",male,28,0,0,349243,7.8958,,S +315,0,2,"Hart, Mr. Benjamin",male,43,1,1,F.C.C. 13529,26.25,,S +316,1,3,"Nilsson, Miss. Helmina Josefina",female,26,0,0,347470,7.8542,,S +317,1,2,"Kantor, Mrs. Sinai (Miriam Sternin)",female,24,1,0,244367,26,,S +318,0,2,"Moraweck, Dr. Ernest",male,54,0,0,29011,14,,S +319,1,1,"Wick, Miss. Mary Natalie",female,31,0,2,36928,164.8667,C7,S +320,1,1,"Spedden, Mrs. Frederic Oakley (Margaretta Corning Stone)",female,40,1,1,16966,134.5,E34,C +321,0,3,"Dennis, Mr. Samuel",male,22,0,0,A/5 21172,7.25,,S +322,0,3,"Danoff, Mr. Yoto",male,27,0,0,349219,7.8958,,S +323,1,2,"Slayter, Miss. Hilda Mary",female,30,0,0,234818,12.35,,Q +324,1,2,"Caldwell, Mrs. Albert Francis (Sylvia Mae Harbaugh)",female,22,1,1,248738,29,,S +325,0,3,"Sage, Mr. George John Jr",male,,8,2,CA. 2343,69.55,,S +326,1,1,"Young, Miss. Marie Grice",female,36,0,0,PC 17760,135.6333,C32,C +327,0,3,"Nysveen, Mr. Johan Hansen",male,61,0,0,345364,6.2375,,S +328,1,2,"Ball, Mrs. (Ada E Hall)",female,36,0,0,28551,13,D,S +329,1,3,"Goldsmith, Mrs. Frank John (Emily Alice Brown)",female,31,1,1,363291,20.525,,S +330,1,1,"Hippach, Miss. Jean Gertrude",female,16,0,1,111361,57.9792,B18,C +331,1,3,"McCoy, Miss. Agnes",female,,2,0,367226,23.25,,Q +332,0,1,"Partner, Mr. Austen",male,45.5,0,0,113043,28.5,C124,S +333,0,1,"Graham, Mr. George Edward",male,38,0,1,PC 17582,153.4625,C91,S +334,0,3,"Vander Planke, Mr. Leo Edmondus",male,16,2,0,345764,18,,S +335,1,1,"Frauenthal, Mrs. Henry William (Clara Heinsheimer)",female,,1,0,PC 17611,133.65,,S +336,0,3,"Denkoff, Mr. Mitto",male,,0,0,349225,7.8958,,S +337,0,1,"Pears, Mr. Thomas Clinton",male,29,1,0,113776,66.6,C2,S +338,1,1,"Burns, Miss. Elizabeth Margaret",female,41,0,0,16966,134.5,E40,C +339,1,3,"Dahl, Mr. Karl Edwart",male,45,0,0,7598,8.05,,S +340,0,1,"Blackwell, Mr. Stephen Weart",male,45,0,0,113784,35.5,T,S +341,1,2,"Navratil, Master. Edmond Roger",male,2,1,1,230080,26,F2,S +342,1,1,"Fortune, Miss. Alice Elizabeth",female,24,3,2,19950,263,C23 C25 C27,S +343,0,2,"Collander, Mr. Erik Gustaf",male,28,0,0,248740,13,,S +344,0,2,"Sedgwick, Mr. Charles Frederick Waddington",male,25,0,0,244361,13,,S +345,0,2,"Fox, Mr. Stanley Hubert",male,36,0,0,229236,13,,S +346,1,2,"Brown, Miss. Amelia ""Mildred""",female,24,0,0,248733,13,F33,S +347,1,2,"Smith, Miss. Marion Elsie",female,40,0,0,31418,13,,S +348,1,3,"Davison, Mrs. Thomas Henry (Mary E Finck)",female,,1,0,386525,16.1,,S +349,1,3,"Coutts, Master. William Loch ""William""",male,3,1,1,C.A. 37671,15.9,,S +350,0,3,"Dimic, Mr. Jovan",male,42,0,0,315088,8.6625,,S +351,0,3,"Odahl, Mr. Nils Martin",male,23,0,0,7267,9.225,,S +352,0,1,"Williams-Lambert, Mr. Fletcher Fellows",male,,0,0,113510,35,C128,S +353,0,3,"Elias, Mr. Tannous",male,15,1,1,2695,7.2292,,C +354,0,3,"Arnold-Franchi, Mr. Josef",male,25,1,0,349237,17.8,,S +355,0,3,"Yousif, Mr. Wazli",male,,0,0,2647,7.225,,C +356,0,3,"Vanden Steen, Mr. Leo Peter",male,28,0,0,345783,9.5,,S +357,1,1,"Bowerman, Miss. Elsie Edith",female,22,0,1,113505,55,E33,S +358,0,2,"Funk, Miss. Annie Clemmer",female,38,0,0,237671,13,,S +359,1,3,"McGovern, Miss. Mary",female,,0,0,330931,7.8792,,Q +360,1,3,"Mockler, Miss. Helen Mary ""Ellie""",female,,0,0,330980,7.8792,,Q +361,0,3,"Skoog, Mr. Wilhelm",male,40,1,4,347088,27.9,,S +362,0,2,"del Carlo, Mr. Sebastiano",male,29,1,0,SC/PARIS 2167,27.7208,,C +363,0,3,"Barbara, Mrs. (Catherine David)",female,45,0,1,2691,14.4542,,C +364,0,3,"Asim, Mr. Adola",male,35,0,0,SOTON/O.Q. 3101310,7.05,,S +365,0,3,"O'Brien, Mr. Thomas",male,,1,0,370365,15.5,,Q +366,0,3,"Adahl, Mr. Mauritz Nils Martin",male,30,0,0,C 7076,7.25,,S +367,1,1,"Warren, Mrs. Frank Manley (Anna Sophia Atkinson)",female,60,1,0,110813,75.25,D37,C +368,1,3,"Moussa, Mrs. (Mantoura Boulos)",female,,0,0,2626,7.2292,,C +369,1,3,"Jermyn, Miss. Annie",female,,0,0,14313,7.75,,Q +370,1,1,"Aubart, Mme. Leontine Pauline",female,24,0,0,PC 17477,69.3,B35,C +371,1,1,"Harder, Mr. George Achilles",male,25,1,0,11765,55.4417,E50,C +372,0,3,"Wiklund, Mr. Jakob Alfred",male,18,1,0,3101267,6.4958,,S +373,0,3,"Beavan, Mr. William Thomas",male,19,0,0,323951,8.05,,S +374,0,1,"Ringhini, Mr. Sante",male,22,0,0,PC 17760,135.6333,,C +375,0,3,"Palsson, Miss. Stina Viola",female,3,3,1,349909,21.075,,S +376,1,1,"Meyer, Mrs. Edgar Joseph (Leila Saks)",female,,1,0,PC 17604,82.1708,,C +377,1,3,"Landergren, Miss. Aurora Adelia",female,22,0,0,C 7077,7.25,,S +378,0,1,"Widener, Mr. Harry Elkins",male,27,0,2,113503,211.5,C82,C +379,0,3,"Betros, Mr. Tannous",male,20,0,0,2648,4.0125,,C +380,0,3,"Gustafsson, Mr. Karl Gideon",male,19,0,0,347069,7.775,,S +381,1,1,"Bidois, Miss. Rosalie",female,42,0,0,PC 17757,227.525,,C +382,1,3,"Nakid, Miss. Maria (""Mary"")",female,1,0,2,2653,15.7417,,C +383,0,3,"Tikkanen, Mr. Juho",male,32,0,0,STON/O 2. 3101293,7.925,,S +384,1,1,"Holverson, Mrs. Alexander Oskar (Mary Aline Towner)",female,35,1,0,113789,52,,S +385,0,3,"Plotcharsky, Mr. Vasil",male,,0,0,349227,7.8958,,S +386,0,2,"Davies, Mr. Charles Henry",male,18,0,0,S.O.C. 14879,73.5,,S +387,0,3,"Goodwin, Master. Sidney Leonard",male,1,5,2,CA 2144,46.9,,S +388,1,2,"Buss, Miss. Kate",female,36,0,0,27849,13,,S +389,0,3,"Sadlier, Mr. Matthew",male,,0,0,367655,7.7292,,Q +390,1,2,"Lehmann, Miss. Bertha",female,17,0,0,SC 1748,12,,C +391,1,1,"Carter, Mr. William Ernest",male,36,1,2,113760,120,B96 B98,S +392,1,3,"Jansson, Mr. Carl Olof",male,21,0,0,350034,7.7958,,S +393,0,3,"Gustafsson, Mr. Johan Birger",male,28,2,0,3101277,7.925,,S +394,1,1,"Newell, Miss. Marjorie",female,23,1,0,35273,113.275,D36,C +395,1,3,"Sandstrom, Mrs. Hjalmar (Agnes Charlotta Bengtsson)",female,24,0,2,PP 9549,16.7,G6,S +396,0,3,"Johansson, Mr. Erik",male,22,0,0,350052,7.7958,,S +397,0,3,"Olsson, Miss. Elina",female,31,0,0,350407,7.8542,,S +398,0,2,"McKane, Mr. Peter David",male,46,0,0,28403,26,,S +399,0,2,"Pain, Dr. Alfred",male,23,0,0,244278,10.5,,S +400,1,2,"Trout, Mrs. William H (Jessie L)",female,28,0,0,240929,12.65,,S +401,1,3,"Niskanen, Mr. Juha",male,39,0,0,STON/O 2. 3101289,7.925,,S +402,0,3,"Adams, Mr. John",male,26,0,0,341826,8.05,,S +403,0,3,"Jussila, Miss. Mari Aina",female,21,1,0,4137,9.825,,S +404,0,3,"Hakkarainen, Mr. Pekka Pietari",male,28,1,0,STON/O2. 3101279,15.85,,S +405,0,3,"Oreskovic, Miss. Marija",female,20,0,0,315096,8.6625,,S +406,0,2,"Gale, Mr. Shadrach",male,34,1,0,28664,21,,S +407,0,3,"Widegren, Mr. Carl/Charles Peter",male,51,0,0,347064,7.75,,S +408,1,2,"Richards, Master. William Rowe",male,3,1,1,29106,18.75,,S +409,0,3,"Birkeland, Mr. Hans Martin Monsen",male,21,0,0,312992,7.775,,S +410,0,3,"Lefebre, Miss. Ida",female,,3,1,4133,25.4667,,S +411,0,3,"Sdycoff, Mr. Todor",male,,0,0,349222,7.8958,,S +412,0,3,"Hart, Mr. Henry",male,,0,0,394140,6.8583,,Q +413,1,1,"Minahan, Miss. Daisy E",female,33,1,0,19928,90,C78,Q +414,0,2,"Cunningham, Mr. Alfred Fleming",male,,0,0,239853,0,,S +415,1,3,"Sundman, Mr. Johan Julian",male,44,0,0,STON/O 2. 3101269,7.925,,S +416,0,3,"Meek, Mrs. Thomas (Annie Louise Rowley)",female,,0,0,343095,8.05,,S +417,1,2,"Drew, Mrs. James Vivian (Lulu Thorne Christian)",female,34,1,1,28220,32.5,,S +418,1,2,"Silven, Miss. Lyyli Karoliina",female,18,0,2,250652,13,,S +419,0,2,"Matthews, Mr. William John",male,30,0,0,28228,13,,S +420,0,3,"Van Impe, Miss. Catharina",female,10,0,2,345773,24.15,,S +421,0,3,"Gheorgheff, Mr. Stanio",male,,0,0,349254,7.8958,,C +422,0,3,"Charters, Mr. David",male,21,0,0,A/5. 13032,7.7333,,Q +423,0,3,"Zimmerman, Mr. Leo",male,29,0,0,315082,7.875,,S +424,0,3,"Danbom, Mrs. Ernst Gilbert (Anna Sigrid Maria Brogren)",female,28,1,1,347080,14.4,,S +425,0,3,"Rosblom, Mr. Viktor Richard",male,18,1,1,370129,20.2125,,S +426,0,3,"Wiseman, Mr. Phillippe",male,,0,0,A/4. 34244,7.25,,S +427,1,2,"Clarke, Mrs. Charles V (Ada Maria Winfield)",female,28,1,0,2003,26,,S +428,1,2,"Phillips, Miss. Kate Florence (""Mrs Kate Louise Phillips Marshall"")",female,19,0,0,250655,26,,S +429,0,3,"Flynn, Mr. James",male,,0,0,364851,7.75,,Q +430,1,3,"Pickard, Mr. Berk (Berk Trembisky)",male,32,0,0,SOTON/O.Q. 392078,8.05,E10,S +431,1,1,"Bjornstrom-Steffansson, Mr. Mauritz Hakan",male,28,0,0,110564,26.55,C52,S +432,1,3,"Thorneycroft, Mrs. Percival (Florence Kate White)",female,,1,0,376564,16.1,,S +433,1,2,"Louch, Mrs. Charles Alexander (Alice Adelaide Slow)",female,42,1,0,SC/AH 3085,26,,S +434,0,3,"Kallio, Mr. Nikolai Erland",male,17,0,0,STON/O 2. 3101274,7.125,,S +435,0,1,"Silvey, Mr. William Baird",male,50,1,0,13507,55.9,E44,S +436,1,1,"Carter, Miss. Lucile Polk",female,14,1,2,113760,120,B96 B98,S +437,0,3,"Ford, Miss. Doolina Margaret ""Daisy""",female,21,2,2,W./C. 6608,34.375,,S +438,1,2,"Richards, Mrs. Sidney (Emily Hocking)",female,24,2,3,29106,18.75,,S +439,0,1,"Fortune, Mr. Mark",male,64,1,4,19950,263,C23 C25 C27,S +440,0,2,"Kvillner, Mr. Johan Henrik Johannesson",male,31,0,0,C.A. 18723,10.5,,S +441,1,2,"Hart, Mrs. Benjamin (Esther Ada Bloomfield)",female,45,1,1,F.C.C. 13529,26.25,,S +442,0,3,"Hampe, Mr. Leon",male,20,0,0,345769,9.5,,S +443,0,3,"Petterson, Mr. Johan Emil",male,25,1,0,347076,7.775,,S +444,1,2,"Reynaldo, Ms. Encarnacion",female,28,0,0,230434,13,,S +445,1,3,"Johannesen-Bratthammer, Mr. Bernt",male,,0,0,65306,8.1125,,S +446,1,1,"Dodge, Master. Washington",male,4,0,2,33638,81.8583,A34,S +447,1,2,"Mellinger, Miss. Madeleine Violet",female,13,0,1,250644,19.5,,S +448,1,1,"Seward, Mr. Frederic Kimber",male,34,0,0,113794,26.55,,S +449,1,3,"Baclini, Miss. Marie Catherine",female,5,2,1,2666,19.2583,,C +450,1,1,"Peuchen, Major. Arthur Godfrey",male,52,0,0,113786,30.5,C104,S +451,0,2,"West, Mr. Edwy Arthur",male,36,1,2,C.A. 34651,27.75,,S +452,0,3,"Hagland, Mr. Ingvald Olai Olsen",male,,1,0,65303,19.9667,,S +453,0,1,"Foreman, Mr. Benjamin Laventall",male,30,0,0,113051,27.75,C111,C +454,1,1,"Goldenberg, Mr. Samuel L",male,49,1,0,17453,89.1042,C92,C +455,0,3,"Peduzzi, Mr. Joseph",male,,0,0,A/5 2817,8.05,,S +456,1,3,"Jalsevac, Mr. Ivan",male,29,0,0,349240,7.8958,,C +457,0,1,"Millet, Mr. Francis Davis",male,65,0,0,13509,26.55,E38,S +458,1,1,"Kenyon, Mrs. Frederick R (Marion)",female,,1,0,17464,51.8625,D21,S +459,1,2,"Toomey, Miss. Ellen",female,50,0,0,F.C.C. 13531,10.5,,S +460,0,3,"O'Connor, Mr. Maurice",male,,0,0,371060,7.75,,Q +461,1,1,"Anderson, Mr. Harry",male,48,0,0,19952,26.55,E12,S +462,0,3,"Morley, Mr. William",male,34,0,0,364506,8.05,,S +463,0,1,"Gee, Mr. Arthur H",male,47,0,0,111320,38.5,E63,S +464,0,2,"Milling, Mr. Jacob Christian",male,48,0,0,234360,13,,S +465,0,3,"Maisner, Mr. Simon",male,,0,0,A/S 2816,8.05,,S +466,0,3,"Goncalves, Mr. Manuel Estanslas",male,38,0,0,SOTON/O.Q. 3101306,7.05,,S +467,0,2,"Campbell, Mr. William",male,,0,0,239853,0,,S +468,0,1,"Smart, Mr. John Montgomery",male,56,0,0,113792,26.55,,S +469,0,3,"Scanlan, Mr. James",male,,0,0,36209,7.725,,Q +470,1,3,"Baclini, Miss. Helene Barbara",female,0.75,2,1,2666,19.2583,,C +471,0,3,"Keefe, Mr. Arthur",male,,0,0,323592,7.25,,S +472,0,3,"Cacic, Mr. Luka",male,38,0,0,315089,8.6625,,S +473,1,2,"West, Mrs. Edwy Arthur (Ada Mary Worth)",female,33,1,2,C.A. 34651,27.75,,S +474,1,2,"Jerwan, Mrs. Amin S (Marie Marthe Thuillard)",female,23,0,0,SC/AH Basle 541,13.7917,D,C +475,0,3,"Strandberg, Miss. Ida Sofia",female,22,0,0,7553,9.8375,,S +476,0,1,"Clifford, Mr. George Quincy",male,,0,0,110465,52,A14,S +477,0,2,"Renouf, Mr. Peter Henry",male,34,1,0,31027,21,,S +478,0,3,"Braund, Mr. Lewis Richard",male,29,1,0,3460,7.0458,,S +479,0,3,"Karlsson, Mr. Nils August",male,22,0,0,350060,7.5208,,S +480,1,3,"Hirvonen, Miss. Hildur E",female,2,0,1,3101298,12.2875,,S +481,0,3,"Goodwin, Master. Harold Victor",male,9,5,2,CA 2144,46.9,,S +482,0,2,"Frost, Mr. Anthony Wood ""Archie""",male,,0,0,239854,0,,S +483,0,3,"Rouse, Mr. Richard Henry",male,50,0,0,A/5 3594,8.05,,S +484,1,3,"Turkula, Mrs. (Hedwig)",female,63,0,0,4134,9.5875,,S +485,1,1,"Bishop, Mr. Dickinson H",male,25,1,0,11967,91.0792,B49,C +486,0,3,"Lefebre, Miss. Jeannie",female,,3,1,4133,25.4667,,S +487,1,1,"Hoyt, Mrs. Frederick Maxfield (Jane Anne Forby)",female,35,1,0,19943,90,C93,S +488,0,1,"Kent, Mr. Edward Austin",male,58,0,0,11771,29.7,B37,C +489,0,3,"Somerton, Mr. Francis William",male,30,0,0,A.5. 18509,8.05,,S +490,1,3,"Coutts, Master. Eden Leslie ""Neville""",male,9,1,1,C.A. 37671,15.9,,S +491,0,3,"Hagland, Mr. Konrad Mathias Reiersen",male,,1,0,65304,19.9667,,S +492,0,3,"Windelov, Mr. Einar",male,21,0,0,SOTON/OQ 3101317,7.25,,S +493,0,1,"Molson, Mr. Harry Markland",male,55,0,0,113787,30.5,C30,S +494,0,1,"Artagaveytia, Mr. Ramon",male,71,0,0,PC 17609,49.5042,,C +495,0,3,"Stanley, Mr. Edward Roland",male,21,0,0,A/4 45380,8.05,,S +496,0,3,"Yousseff, Mr. Gerious",male,,0,0,2627,14.4583,,C +497,1,1,"Eustis, Miss. Elizabeth Mussey",female,54,1,0,36947,78.2667,D20,C +498,0,3,"Shellard, Mr. Frederick William",male,,0,0,C.A. 6212,15.1,,S +499,0,1,"Allison, Mrs. Hudson J C (Bessie Waldo Daniels)",female,25,1,2,113781,151.55,C22 C26,S +500,0,3,"Svensson, Mr. Olof",male,24,0,0,350035,7.7958,,S +501,0,3,"Calic, Mr. Petar",male,17,0,0,315086,8.6625,,S +502,0,3,"Canavan, Miss. Mary",female,21,0,0,364846,7.75,,Q +503,0,3,"O'Sullivan, Miss. Bridget Mary",female,,0,0,330909,7.6292,,Q +504,0,3,"Laitinen, Miss. Kristina Sofia",female,37,0,0,4135,9.5875,,S +505,1,1,"Maioni, Miss. Roberta",female,16,0,0,110152,86.5,B79,S +506,0,1,"Penasco y Castellana, Mr. Victor de Satode",male,18,1,0,PC 17758,108.9,C65,C +507,1,2,"Quick, Mrs. Frederick Charles (Jane Richards)",female,33,0,2,26360,26,,S +508,1,1,"Bradley, Mr. George (""George Arthur Brayton"")",male,,0,0,111427,26.55,,S +509,0,3,"Olsen, Mr. Henry Margido",male,28,0,0,C 4001,22.525,,S +510,1,3,"Lang, Mr. Fang",male,26,0,0,1601,56.4958,,S +511,1,3,"Daly, Mr. Eugene Patrick",male,29,0,0,382651,7.75,,Q +512,0,3,"Webber, Mr. James",male,,0,0,SOTON/OQ 3101316,8.05,,S +513,1,1,"McGough, Mr. James Robert",male,36,0,0,PC 17473,26.2875,E25,S +514,1,1,"Rothschild, Mrs. Martin (Elizabeth L. Barrett)",female,54,1,0,PC 17603,59.4,,C +515,0,3,"Coleff, Mr. Satio",male,24,0,0,349209,7.4958,,S +516,0,1,"Walker, Mr. William Anderson",male,47,0,0,36967,34.0208,D46,S +517,1,2,"Lemore, Mrs. (Amelia Milley)",female,34,0,0,C.A. 34260,10.5,F33,S +518,0,3,"Ryan, Mr. Patrick",male,,0,0,371110,24.15,,Q +519,1,2,"Angle, Mrs. William A (Florence ""Mary"" Agnes Hughes)",female,36,1,0,226875,26,,S +520,0,3,"Pavlovic, Mr. Stefo",male,32,0,0,349242,7.8958,,S +521,1,1,"Perreault, Miss. Anne",female,30,0,0,12749,93.5,B73,S +522,0,3,"Vovk, Mr. Janko",male,22,0,0,349252,7.8958,,S +523,0,3,"Lahoud, Mr. Sarkis",male,,0,0,2624,7.225,,C +524,1,1,"Hippach, Mrs. Louis Albert (Ida Sophia Fischer)",female,44,0,1,111361,57.9792,B18,C +525,0,3,"Kassem, Mr. Fared",male,,0,0,2700,7.2292,,C +526,0,3,"Farrell, Mr. James",male,40.5,0,0,367232,7.75,,Q +527,1,2,"Ridsdale, Miss. Lucy",female,50,0,0,W./C. 14258,10.5,,S +528,0,1,"Farthing, Mr. John",male,,0,0,PC 17483,221.7792,C95,S +529,0,3,"Salonen, Mr. Johan Werner",male,39,0,0,3101296,7.925,,S +530,0,2,"Hocking, Mr. Richard George",male,23,2,1,29104,11.5,,S +531,1,2,"Quick, Miss. Phyllis May",female,2,1,1,26360,26,,S +532,0,3,"Toufik, Mr. Nakli",male,,0,0,2641,7.2292,,C +533,0,3,"Elias, Mr. Joseph Jr",male,17,1,1,2690,7.2292,,C +534,1,3,"Peter, Mrs. Catherine (Catherine Rizk)",female,,0,2,2668,22.3583,,C +535,0,3,"Cacic, Miss. Marija",female,30,0,0,315084,8.6625,,S +536,1,2,"Hart, Miss. Eva Miriam",female,7,0,2,F.C.C. 13529,26.25,,S +537,0,1,"Butt, Major. Archibald Willingham",male,45,0,0,113050,26.55,B38,S +538,1,1,"LeRoy, Miss. Bertha",female,30,0,0,PC 17761,106.425,,C +539,0,3,"Risien, Mr. Samuel Beard",male,,0,0,364498,14.5,,S +540,1,1,"Frolicher, Miss. Hedwig Margaritha",female,22,0,2,13568,49.5,B39,C +541,1,1,"Crosby, Miss. Harriet R",female,36,0,2,WE/P 5735,71,B22,S +542,0,3,"Andersson, Miss. Ingeborg Constanzia",female,9,4,2,347082,31.275,,S +543,0,3,"Andersson, Miss. Sigrid Elisabeth",female,11,4,2,347082,31.275,,S +544,1,2,"Beane, Mr. Edward",male,32,1,0,2908,26,,S +545,0,1,"Douglas, Mr. Walter Donald",male,50,1,0,PC 17761,106.425,C86,C +546,0,1,"Nicholson, Mr. Arthur Ernest",male,64,0,0,693,26,,S +547,1,2,"Beane, Mrs. Edward (Ethel Clarke)",female,19,1,0,2908,26,,S +548,1,2,"Padro y Manent, Mr. Julian",male,,0,0,SC/PARIS 2146,13.8625,,C +549,0,3,"Goldsmith, Mr. Frank John",male,33,1,1,363291,20.525,,S +550,1,2,"Davies, Master. John Morgan Jr",male,8,1,1,C.A. 33112,36.75,,S +551,1,1,"Thayer, Mr. John Borland Jr",male,17,0,2,17421,110.8833,C70,C +552,0,2,"Sharp, Mr. Percival James R",male,27,0,0,244358,26,,S +553,0,3,"O'Brien, Mr. Timothy",male,,0,0,330979,7.8292,,Q +554,1,3,"Leeni, Mr. Fahim (""Philip Zenni"")",male,22,0,0,2620,7.225,,C +555,1,3,"Ohman, Miss. Velin",female,22,0,0,347085,7.775,,S +556,0,1,"Wright, Mr. George",male,62,0,0,113807,26.55,,S +557,1,1,"Duff Gordon, Lady. (Lucille Christiana Sutherland) (""Mrs Morgan"")",female,48,1,0,11755,39.6,A16,C +558,0,1,"Robbins, Mr. Victor",male,,0,0,PC 17757,227.525,,C +559,1,1,"Taussig, Mrs. Emil (Tillie Mandelbaum)",female,39,1,1,110413,79.65,E67,S +560,1,3,"de Messemaeker, Mrs. Guillaume Joseph (Emma)",female,36,1,0,345572,17.4,,S +561,0,3,"Morrow, Mr. Thomas Rowan",male,,0,0,372622,7.75,,Q +562,0,3,"Sivic, Mr. Husein",male,40,0,0,349251,7.8958,,S +563,0,2,"Norman, Mr. Robert Douglas",male,28,0,0,218629,13.5,,S +564,0,3,"Simmons, Mr. John",male,,0,0,SOTON/OQ 392082,8.05,,S +565,0,3,"Meanwell, Miss. (Marion Ogden)",female,,0,0,SOTON/O.Q. 392087,8.05,,S +566,0,3,"Davies, Mr. Alfred J",male,24,2,0,A/4 48871,24.15,,S +567,0,3,"Stoytcheff, Mr. Ilia",male,19,0,0,349205,7.8958,,S +568,0,3,"Palsson, Mrs. Nils (Alma Cornelia Berglund)",female,29,0,4,349909,21.075,,S +569,0,3,"Doharr, Mr. Tannous",male,,0,0,2686,7.2292,,C +570,1,3,"Jonsson, Mr. Carl",male,32,0,0,350417,7.8542,,S +571,1,2,"Harris, Mr. George",male,62,0,0,S.W./PP 752,10.5,,S +572,1,1,"Appleton, Mrs. Edward Dale (Charlotte Lamson)",female,53,2,0,11769,51.4792,C101,S +573,1,1,"Flynn, Mr. John Irwin (""Irving"")",male,36,0,0,PC 17474,26.3875,E25,S +574,1,3,"Kelly, Miss. Mary",female,,0,0,14312,7.75,,Q +575,0,3,"Rush, Mr. Alfred George John",male,16,0,0,A/4. 20589,8.05,,S +576,0,3,"Patchett, Mr. George",male,19,0,0,358585,14.5,,S +577,1,2,"Garside, Miss. Ethel",female,34,0,0,243880,13,,S +578,1,1,"Silvey, Mrs. William Baird (Alice Munger)",female,39,1,0,13507,55.9,E44,S +579,0,3,"Caram, Mrs. Joseph (Maria Elias)",female,,1,0,2689,14.4583,,C +580,1,3,"Jussila, Mr. Eiriik",male,32,0,0,STON/O 2. 3101286,7.925,,S +581,1,2,"Christy, Miss. Julie Rachel",female,25,1,1,237789,30,,S +582,1,1,"Thayer, Mrs. John Borland (Marian Longstreth Morris)",female,39,1,1,17421,110.8833,C68,C +583,0,2,"Downton, Mr. William James",male,54,0,0,28403,26,,S +584,0,1,"Ross, Mr. John Hugo",male,36,0,0,13049,40.125,A10,C +585,0,3,"Paulner, Mr. Uscher",male,,0,0,3411,8.7125,,C +586,1,1,"Taussig, Miss. Ruth",female,18,0,2,110413,79.65,E68,S +587,0,2,"Jarvis, Mr. John Denzil",male,47,0,0,237565,15,,S +588,1,1,"Frolicher-Stehli, Mr. Maxmillian",male,60,1,1,13567,79.2,B41,C +589,0,3,"Gilinski, Mr. Eliezer",male,22,0,0,14973,8.05,,S +590,0,3,"Murdlin, Mr. Joseph",male,,0,0,A./5. 3235,8.05,,S +591,0,3,"Rintamaki, Mr. Matti",male,35,0,0,STON/O 2. 3101273,7.125,,S +592,1,1,"Stephenson, Mrs. Walter Bertram (Martha Eustis)",female,52,1,0,36947,78.2667,D20,C +593,0,3,"Elsbury, Mr. William James",male,47,0,0,A/5 3902,7.25,,S +594,0,3,"Bourke, Miss. Mary",female,,0,2,364848,7.75,,Q +595,0,2,"Chapman, Mr. John Henry",male,37,1,0,SC/AH 29037,26,,S +596,0,3,"Van Impe, Mr. Jean Baptiste",male,36,1,1,345773,24.15,,S +597,1,2,"Leitch, Miss. Jessie Wills",female,,0,0,248727,33,,S +598,0,3,"Johnson, Mr. Alfred",male,49,0,0,LINE,0,,S +599,0,3,"Boulos, Mr. Hanna",male,,0,0,2664,7.225,,C +600,1,1,"Duff Gordon, Sir. Cosmo Edmund (""Mr Morgan"")",male,49,1,0,PC 17485,56.9292,A20,C +601,1,2,"Jacobsohn, Mrs. Sidney Samuel (Amy Frances Christy)",female,24,2,1,243847,27,,S +602,0,3,"Slabenoff, Mr. Petco",male,,0,0,349214,7.8958,,S +603,0,1,"Harrington, Mr. Charles H",male,,0,0,113796,42.4,,S +604,0,3,"Torber, Mr. Ernst William",male,44,0,0,364511,8.05,,S +605,1,1,"Homer, Mr. Harry (""Mr E Haven"")",male,35,0,0,111426,26.55,,C +606,0,3,"Lindell, Mr. Edvard Bengtsson",male,36,1,0,349910,15.55,,S +607,0,3,"Karaic, Mr. Milan",male,30,0,0,349246,7.8958,,S +608,1,1,"Daniel, Mr. Robert Williams",male,27,0,0,113804,30.5,,S +609,1,2,"Laroche, Mrs. Joseph (Juliette Marie Louise Lafargue)",female,22,1,2,SC/Paris 2123,41.5792,,C +610,1,1,"Shutes, Miss. Elizabeth W",female,40,0,0,PC 17582,153.4625,C125,S +611,0,3,"Andersson, Mrs. Anders Johan (Alfrida Konstantia Brogren)",female,39,1,5,347082,31.275,,S +612,0,3,"Jardin, Mr. Jose Neto",male,,0,0,SOTON/O.Q. 3101305,7.05,,S +613,1,3,"Murphy, Miss. Margaret Jane",female,,1,0,367230,15.5,,Q +614,0,3,"Horgan, Mr. John",male,,0,0,370377,7.75,,Q +615,0,3,"Brocklebank, Mr. William Alfred",male,35,0,0,364512,8.05,,S +616,1,2,"Herman, Miss. Alice",female,24,1,2,220845,65,,S +617,0,3,"Danbom, Mr. Ernst Gilbert",male,34,1,1,347080,14.4,,S +618,0,3,"Lobb, Mrs. William Arthur (Cordelia K Stanlick)",female,26,1,0,A/5. 3336,16.1,,S +619,1,2,"Becker, Miss. Marion Louise",female,4,2,1,230136,39,F4,S +620,0,2,"Gavey, Mr. Lawrence",male,26,0,0,31028,10.5,,S +621,0,3,"Yasbeck, Mr. Antoni",male,27,1,0,2659,14.4542,,C +622,1,1,"Kimball, Mr. Edwin Nelson Jr",male,42,1,0,11753,52.5542,D19,S +623,1,3,"Nakid, Mr. Sahid",male,20,1,1,2653,15.7417,,C +624,0,3,"Hansen, Mr. Henry Damsgaard",male,21,0,0,350029,7.8542,,S +625,0,3,"Bowen, Mr. David John ""Dai""",male,21,0,0,54636,16.1,,S +626,0,1,"Sutton, Mr. Frederick",male,61,0,0,36963,32.3208,D50,S +627,0,2,"Kirkland, Rev. Charles Leonard",male,57,0,0,219533,12.35,,Q +628,1,1,"Longley, Miss. Gretchen Fiske",female,21,0,0,13502,77.9583,D9,S +629,0,3,"Bostandyeff, Mr. Guentcho",male,26,0,0,349224,7.8958,,S +630,0,3,"O'Connell, Mr. Patrick D",male,,0,0,334912,7.7333,,Q +631,1,1,"Barkworth, Mr. Algernon Henry Wilson",male,80,0,0,27042,30,A23,S +632,0,3,"Lundahl, Mr. Johan Svensson",male,51,0,0,347743,7.0542,,S +633,1,1,"Stahelin-Maeglin, Dr. Max",male,32,0,0,13214,30.5,B50,C +634,0,1,"Parr, Mr. William Henry Marsh",male,,0,0,112052,0,,S +635,0,3,"Skoog, Miss. Mabel",female,9,3,2,347088,27.9,,S +636,1,2,"Davis, Miss. Mary",female,28,0,0,237668,13,,S +637,0,3,"Leinonen, Mr. Antti Gustaf",male,32,0,0,STON/O 2. 3101292,7.925,,S +638,0,2,"Collyer, Mr. Harvey",male,31,1,1,C.A. 31921,26.25,,S +639,0,3,"Panula, Mrs. Juha (Maria Emilia Ojala)",female,41,0,5,3101295,39.6875,,S +640,0,3,"Thorneycroft, Mr. Percival",male,,1,0,376564,16.1,,S +641,0,3,"Jensen, Mr. Hans Peder",male,20,0,0,350050,7.8542,,S +642,1,1,"Sagesser, Mlle. Emma",female,24,0,0,PC 17477,69.3,B35,C +643,0,3,"Skoog, Miss. Margit Elizabeth",female,2,3,2,347088,27.9,,S +644,1,3,"Foo, Mr. Choong",male,,0,0,1601,56.4958,,S +645,1,3,"Baclini, Miss. Eugenie",female,0.75,2,1,2666,19.2583,,C +646,1,1,"Harper, Mr. Henry Sleeper",male,48,1,0,PC 17572,76.7292,D33,C +647,0,3,"Cor, Mr. Liudevit",male,19,0,0,349231,7.8958,,S +648,1,1,"Simonius-Blumer, Col. Oberst Alfons",male,56,0,0,13213,35.5,A26,C +649,0,3,"Willey, Mr. Edward",male,,0,0,S.O./P.P. 751,7.55,,S +650,1,3,"Stanley, Miss. Amy Zillah Elsie",female,23,0,0,CA. 2314,7.55,,S +651,0,3,"Mitkoff, Mr. Mito",male,,0,0,349221,7.8958,,S +652,1,2,"Doling, Miss. Elsie",female,18,0,1,231919,23,,S +653,0,3,"Kalvik, Mr. Johannes Halvorsen",male,21,0,0,8475,8.4333,,S +654,1,3,"O'Leary, Miss. Hanora ""Norah""",female,,0,0,330919,7.8292,,Q +655,0,3,"Hegarty, Miss. Hanora ""Nora""",female,18,0,0,365226,6.75,,Q +656,0,2,"Hickman, Mr. Leonard Mark",male,24,2,0,S.O.C. 14879,73.5,,S +657,0,3,"Radeff, Mr. Alexander",male,,0,0,349223,7.8958,,S +658,0,3,"Bourke, Mrs. John (Catherine)",female,32,1,1,364849,15.5,,Q +659,0,2,"Eitemiller, Mr. George Floyd",male,23,0,0,29751,13,,S +660,0,1,"Newell, Mr. Arthur Webster",male,58,0,2,35273,113.275,D48,C +661,1,1,"Frauenthal, Dr. Henry William",male,50,2,0,PC 17611,133.65,,S +662,0,3,"Badt, Mr. Mohamed",male,40,0,0,2623,7.225,,C +663,0,1,"Colley, Mr. Edward Pomeroy",male,47,0,0,5727,25.5875,E58,S +664,0,3,"Coleff, Mr. Peju",male,36,0,0,349210,7.4958,,S +665,1,3,"Lindqvist, Mr. Eino William",male,20,1,0,STON/O 2. 3101285,7.925,,S +666,0,2,"Hickman, Mr. Lewis",male,32,2,0,S.O.C. 14879,73.5,,S +667,0,2,"Butler, Mr. Reginald Fenton",male,25,0,0,234686,13,,S +668,0,3,"Rommetvedt, Mr. Knud Paust",male,,0,0,312993,7.775,,S +669,0,3,"Cook, Mr. Jacob",male,43,0,0,A/5 3536,8.05,,S +670,1,1,"Taylor, Mrs. Elmer Zebley (Juliet Cummins Wright)",female,,1,0,19996,52,C126,S +671,1,2,"Brown, Mrs. Thomas William Solomon (Elizabeth Catherine Ford)",female,40,1,1,29750,39,,S +672,0,1,"Davidson, Mr. Thornton",male,31,1,0,F.C. 12750,52,B71,S +673,0,2,"Mitchell, Mr. Henry Michael",male,70,0,0,C.A. 24580,10.5,,S +674,1,2,"Wilhelms, Mr. Charles",male,31,0,0,244270,13,,S +675,0,2,"Watson, Mr. Ennis Hastings",male,,0,0,239856,0,,S +676,0,3,"Edvardsson, Mr. Gustaf Hjalmar",male,18,0,0,349912,7.775,,S +677,0,3,"Sawyer, Mr. Frederick Charles",male,24.5,0,0,342826,8.05,,S +678,1,3,"Turja, Miss. Anna Sofia",female,18,0,0,4138,9.8417,,S +679,0,3,"Goodwin, Mrs. Frederick (Augusta Tyler)",female,43,1,6,CA 2144,46.9,,S +680,1,1,"Cardeza, Mr. Thomas Drake Martinez",male,36,0,1,PC 17755,512.3292,B51 B53 B55,C +681,0,3,"Peters, Miss. Katie",female,,0,0,330935,8.1375,,Q +682,1,1,"Hassab, Mr. Hammad",male,27,0,0,PC 17572,76.7292,D49,C +683,0,3,"Olsvigen, Mr. Thor Anderson",male,20,0,0,6563,9.225,,S +684,0,3,"Goodwin, Mr. Charles Edward",male,14,5,2,CA 2144,46.9,,S +685,0,2,"Brown, Mr. Thomas William Solomon",male,60,1,1,29750,39,,S +686,0,2,"Laroche, Mr. Joseph Philippe Lemercier",male,25,1,2,SC/Paris 2123,41.5792,,C +687,0,3,"Panula, Mr. Jaako Arnold",male,14,4,1,3101295,39.6875,,S +688,0,3,"Dakic, Mr. Branko",male,19,0,0,349228,10.1708,,S +689,0,3,"Fischer, Mr. Eberhard Thelander",male,18,0,0,350036,7.7958,,S +690,1,1,"Madill, Miss. Georgette Alexandra",female,15,0,1,24160,211.3375,B5,S +691,1,1,"Dick, Mr. Albert Adrian",male,31,1,0,17474,57,B20,S +692,1,3,"Karun, Miss. Manca",female,4,0,1,349256,13.4167,,C +693,1,3,"Lam, Mr. Ali",male,,0,0,1601,56.4958,,S +694,0,3,"Saad, Mr. Khalil",male,25,0,0,2672,7.225,,C +695,0,1,"Weir, Col. John",male,60,0,0,113800,26.55,,S +696,0,2,"Chapman, Mr. Charles Henry",male,52,0,0,248731,13.5,,S +697,0,3,"Kelly, Mr. James",male,44,0,0,363592,8.05,,S +698,1,3,"Mullens, Miss. Katherine ""Katie""",female,,0,0,35852,7.7333,,Q +699,0,1,"Thayer, Mr. John Borland",male,49,1,1,17421,110.8833,C68,C +700,0,3,"Humblen, Mr. Adolf Mathias Nicolai Olsen",male,42,0,0,348121,7.65,F G63,S +701,1,1,"Astor, Mrs. John Jacob (Madeleine Talmadge Force)",female,18,1,0,PC 17757,227.525,C62 C64,C +702,1,1,"Silverthorne, Mr. Spencer Victor",male,35,0,0,PC 17475,26.2875,E24,S +703,0,3,"Barbara, Miss. Saiide",female,18,0,1,2691,14.4542,,C +704,0,3,"Gallagher, Mr. Martin",male,25,0,0,36864,7.7417,,Q +705,0,3,"Hansen, Mr. Henrik Juul",male,26,1,0,350025,7.8542,,S +706,0,2,"Morley, Mr. Henry Samuel (""Mr Henry Marshall"")",male,39,0,0,250655,26,,S +707,1,2,"Kelly, Mrs. Florence ""Fannie""",female,45,0,0,223596,13.5,,S +708,1,1,"Calderhead, Mr. Edward Pennington",male,42,0,0,PC 17476,26.2875,E24,S +709,1,1,"Cleaver, Miss. Alice",female,22,0,0,113781,151.55,,S +710,1,3,"Moubarek, Master. Halim Gonios (""William George"")",male,,1,1,2661,15.2458,,C +711,1,1,"Mayne, Mlle. Berthe Antonine (""Mrs de Villiers"")",female,24,0,0,PC 17482,49.5042,C90,C +712,0,1,"Klaber, Mr. Herman",male,,0,0,113028,26.55,C124,S +713,1,1,"Taylor, Mr. Elmer Zebley",male,48,1,0,19996,52,C126,S +714,0,3,"Larsson, Mr. August Viktor",male,29,0,0,7545,9.4833,,S +715,0,2,"Greenberg, Mr. Samuel",male,52,0,0,250647,13,,S +716,0,3,"Soholt, Mr. Peter Andreas Lauritz Andersen",male,19,0,0,348124,7.65,F G73,S +717,1,1,"Endres, Miss. Caroline Louise",female,38,0,0,PC 17757,227.525,C45,C +718,1,2,"Troutt, Miss. Edwina Celia ""Winnie""",female,27,0,0,34218,10.5,E101,S +719,0,3,"McEvoy, Mr. Michael",male,,0,0,36568,15.5,,Q +720,0,3,"Johnson, Mr. Malkolm Joackim",male,33,0,0,347062,7.775,,S +721,1,2,"Harper, Miss. Annie Jessie ""Nina""",female,6,0,1,248727,33,,S +722,0,3,"Jensen, Mr. Svend Lauritz",male,17,1,0,350048,7.0542,,S +723,0,2,"Gillespie, Mr. William Henry",male,34,0,0,12233,13,,S +724,0,2,"Hodges, Mr. Henry Price",male,50,0,0,250643,13,,S +725,1,1,"Chambers, Mr. Norman Campbell",male,27,1,0,113806,53.1,E8,S +726,0,3,"Oreskovic, Mr. Luka",male,20,0,0,315094,8.6625,,S +727,1,2,"Renouf, Mrs. Peter Henry (Lillian Jefferys)",female,30,3,0,31027,21,,S +728,1,3,"Mannion, Miss. Margareth",female,,0,0,36866,7.7375,,Q +729,0,2,"Bryhl, Mr. Kurt Arnold Gottfrid",male,25,1,0,236853,26,,S +730,0,3,"Ilmakangas, Miss. Pieta Sofia",female,25,1,0,STON/O2. 3101271,7.925,,S +731,1,1,"Allen, Miss. Elisabeth Walton",female,29,0,0,24160,211.3375,B5,S +732,0,3,"Hassan, Mr. Houssein G N",male,11,0,0,2699,18.7875,,C +733,0,2,"Knight, Mr. Robert J",male,,0,0,239855,0,,S +734,0,2,"Berriman, Mr. William John",male,23,0,0,28425,13,,S +735,0,2,"Troupiansky, Mr. Moses Aaron",male,23,0,0,233639,13,,S +736,0,3,"Williams, Mr. Leslie",male,28.5,0,0,54636,16.1,,S +737,0,3,"Ford, Mrs. Edward (Margaret Ann Watson)",female,48,1,3,W./C. 6608,34.375,,S +738,1,1,"Lesurer, Mr. Gustave J",male,35,0,0,PC 17755,512.3292,B101,C +739,0,3,"Ivanoff, Mr. Kanio",male,,0,0,349201,7.8958,,S +740,0,3,"Nankoff, Mr. Minko",male,,0,0,349218,7.8958,,S +741,1,1,"Hawksford, Mr. Walter James",male,,0,0,16988,30,D45,S +742,0,1,"Cavendish, Mr. Tyrell William",male,36,1,0,19877,78.85,C46,S +743,1,1,"Ryerson, Miss. Susan Parker ""Suzette""",female,21,2,2,PC 17608,262.375,B57 B59 B63 B66,C +744,0,3,"McNamee, Mr. Neal",male,24,1,0,376566,16.1,,S +745,1,3,"Stranden, Mr. Juho",male,31,0,0,STON/O 2. 3101288,7.925,,S +746,0,1,"Crosby, Capt. Edward Gifford",male,70,1,1,WE/P 5735,71,B22,S +747,0,3,"Abbott, Mr. Rossmore Edward",male,16,1,1,C.A. 2673,20.25,,S +748,1,2,"Sinkkonen, Miss. Anna",female,30,0,0,250648,13,,S +749,0,1,"Marvin, Mr. Daniel Warner",male,19,1,0,113773,53.1,D30,S +750,0,3,"Connaghton, Mr. Michael",male,31,0,0,335097,7.75,,Q +751,1,2,"Wells, Miss. Joan",female,4,1,1,29103,23,,S +752,1,3,"Moor, Master. Meier",male,6,0,1,392096,12.475,E121,S +753,0,3,"Vande Velde, Mr. Johannes Joseph",male,33,0,0,345780,9.5,,S +754,0,3,"Jonkoff, Mr. Lalio",male,23,0,0,349204,7.8958,,S +755,1,2,"Herman, Mrs. Samuel (Jane Laver)",female,48,1,2,220845,65,,S +756,1,2,"Hamalainen, Master. Viljo",male,0.67,1,1,250649,14.5,,S +757,0,3,"Carlsson, Mr. August Sigfrid",male,28,0,0,350042,7.7958,,S +758,0,2,"Bailey, Mr. Percy Andrew",male,18,0,0,29108,11.5,,S +759,0,3,"Theobald, Mr. Thomas Leonard",male,34,0,0,363294,8.05,,S +760,1,1,"Rothes, the Countess. of (Lucy Noel Martha Dyer-Edwards)",female,33,0,0,110152,86.5,B77,S +761,0,3,"Garfirth, Mr. John",male,,0,0,358585,14.5,,S +762,0,3,"Nirva, Mr. Iisakki Antino Aijo",male,41,0,0,SOTON/O2 3101272,7.125,,S +763,1,3,"Barah, Mr. Hanna Assi",male,20,0,0,2663,7.2292,,C +764,1,1,"Carter, Mrs. William Ernest (Lucile Polk)",female,36,1,2,113760,120,B96 B98,S +765,0,3,"Eklund, Mr. Hans Linus",male,16,0,0,347074,7.775,,S +766,1,1,"Hogeboom, Mrs. John C (Anna Andrews)",female,51,1,0,13502,77.9583,D11,S +767,0,1,"Brewe, Dr. Arthur Jackson",male,,0,0,112379,39.6,,C +768,0,3,"Mangan, Miss. Mary",female,30.5,0,0,364850,7.75,,Q +769,0,3,"Moran, Mr. Daniel J",male,,1,0,371110,24.15,,Q +770,0,3,"Gronnestad, Mr. Daniel Danielsen",male,32,0,0,8471,8.3625,,S +771,0,3,"Lievens, Mr. Rene Aime",male,24,0,0,345781,9.5,,S +772,0,3,"Jensen, Mr. Niels Peder",male,48,0,0,350047,7.8542,,S +773,0,2,"Mack, Mrs. (Mary)",female,57,0,0,S.O./P.P. 3,10.5,E77,S +774,0,3,"Elias, Mr. Dibo",male,,0,0,2674,7.225,,C +775,1,2,"Hocking, Mrs. Elizabeth (Eliza Needs)",female,54,1,3,29105,23,,S +776,0,3,"Myhrman, Mr. Pehr Fabian Oliver Malkolm",male,18,0,0,347078,7.75,,S +777,0,3,"Tobin, Mr. Roger",male,,0,0,383121,7.75,F38,Q +778,1,3,"Emanuel, Miss. Virginia Ethel",female,5,0,0,364516,12.475,,S +779,0,3,"Kilgannon, Mr. Thomas J",male,,0,0,36865,7.7375,,Q +780,1,1,"Robert, Mrs. Edward Scott (Elisabeth Walton McMillan)",female,43,0,1,24160,211.3375,B3,S +781,1,3,"Ayoub, Miss. Banoura",female,13,0,0,2687,7.2292,,C +782,1,1,"Dick, Mrs. Albert Adrian (Vera Gillespie)",female,17,1,0,17474,57,B20,S +783,0,1,"Long, Mr. Milton Clyde",male,29,0,0,113501,30,D6,S +784,0,3,"Johnston, Mr. Andrew G",male,,1,2,W./C. 6607,23.45,,S +785,0,3,"Ali, Mr. William",male,25,0,0,SOTON/O.Q. 3101312,7.05,,S +786,0,3,"Harmer, Mr. Abraham (David Lishin)",male,25,0,0,374887,7.25,,S +787,1,3,"Sjoblom, Miss. Anna Sofia",female,18,0,0,3101265,7.4958,,S +788,0,3,"Rice, Master. George Hugh",male,8,4,1,382652,29.125,,Q +789,1,3,"Dean, Master. Bertram Vere",male,1,1,2,C.A. 2315,20.575,,S +790,0,1,"Guggenheim, Mr. Benjamin",male,46,0,0,PC 17593,79.2,B82 B84,C +791,0,3,"Keane, Mr. Andrew ""Andy""",male,,0,0,12460,7.75,,Q +792,0,2,"Gaskell, Mr. Alfred",male,16,0,0,239865,26,,S +793,0,3,"Sage, Miss. Stella Anna",female,,8,2,CA. 2343,69.55,,S +794,0,1,"Hoyt, Mr. William Fisher",male,,0,0,PC 17600,30.6958,,C +795,0,3,"Dantcheff, Mr. Ristiu",male,25,0,0,349203,7.8958,,S +796,0,2,"Otter, Mr. Richard",male,39,0,0,28213,13,,S +797,1,1,"Leader, Dr. Alice (Farnham)",female,49,0,0,17465,25.9292,D17,S +798,1,3,"Osman, Mrs. Mara",female,31,0,0,349244,8.6833,,S +799,0,3,"Ibrahim Shawah, Mr. Yousseff",male,30,0,0,2685,7.2292,,C +800,0,3,"Van Impe, Mrs. Jean Baptiste (Rosalie Paula Govaert)",female,30,1,1,345773,24.15,,S +801,0,2,"Ponesell, Mr. Martin",male,34,0,0,250647,13,,S +802,1,2,"Collyer, Mrs. Harvey (Charlotte Annie Tate)",female,31,1,1,C.A. 31921,26.25,,S +803,1,1,"Carter, Master. William Thornton II",male,11,1,2,113760,120,B96 B98,S +804,1,3,"Thomas, Master. Assad Alexander",male,0.42,0,1,2625,8.5167,,C +805,1,3,"Hedman, Mr. Oskar Arvid",male,27,0,0,347089,6.975,,S +806,0,3,"Johansson, Mr. Karl Johan",male,31,0,0,347063,7.775,,S +807,0,1,"Andrews, Mr. Thomas Jr",male,39,0,0,112050,0,A36,S +808,0,3,"Pettersson, Miss. Ellen Natalia",female,18,0,0,347087,7.775,,S +809,0,2,"Meyer, Mr. August",male,39,0,0,248723,13,,S +810,1,1,"Chambers, Mrs. Norman Campbell (Bertha Griggs)",female,33,1,0,113806,53.1,E8,S +811,0,3,"Alexander, Mr. William",male,26,0,0,3474,7.8875,,S +812,0,3,"Lester, Mr. James",male,39,0,0,A/4 48871,24.15,,S +813,0,2,"Slemen, Mr. Richard James",male,35,0,0,28206,10.5,,S +814,0,3,"Andersson, Miss. Ebba Iris Alfrida",female,6,4,2,347082,31.275,,S +815,0,3,"Tomlin, Mr. Ernest Portage",male,30.5,0,0,364499,8.05,,S +816,0,1,"Fry, Mr. Richard",male,,0,0,112058,0,B102,S +817,0,3,"Heininen, Miss. Wendla Maria",female,23,0,0,STON/O2. 3101290,7.925,,S +818,0,2,"Mallet, Mr. Albert",male,31,1,1,S.C./PARIS 2079,37.0042,,C +819,0,3,"Holm, Mr. John Fredrik Alexander",male,43,0,0,C 7075,6.45,,S +820,0,3,"Skoog, Master. Karl Thorsten",male,10,3,2,347088,27.9,,S +821,1,1,"Hays, Mrs. Charles Melville (Clara Jennings Gregg)",female,52,1,1,12749,93.5,B69,S +822,1,3,"Lulic, Mr. Nikola",male,27,0,0,315098,8.6625,,S +823,0,1,"Reuchlin, Jonkheer. John George",male,38,0,0,19972,0,,S +824,1,3,"Moor, Mrs. (Beila)",female,27,0,1,392096,12.475,E121,S +825,0,3,"Panula, Master. Urho Abraham",male,2,4,1,3101295,39.6875,,S +826,0,3,"Flynn, Mr. John",male,,0,0,368323,6.95,,Q +827,0,3,"Lam, Mr. Len",male,,0,0,1601,56.4958,,S +828,1,2,"Mallet, Master. Andre",male,1,0,2,S.C./PARIS 2079,37.0042,,C +829,1,3,"McCormack, Mr. Thomas Joseph",male,,0,0,367228,7.75,,Q +830,1,1,"Stone, Mrs. George Nelson (Martha Evelyn)",female,62,0,0,113572,80,B28, +831,1,3,"Yasbeck, Mrs. Antoni (Selini Alexander)",female,15,1,0,2659,14.4542,,C +832,1,2,"Richards, Master. George Sibley",male,0.83,1,1,29106,18.75,,S +833,0,3,"Saad, Mr. Amin",male,,0,0,2671,7.2292,,C +834,0,3,"Augustsson, Mr. Albert",male,23,0,0,347468,7.8542,,S +835,0,3,"Allum, Mr. Owen George",male,18,0,0,2223,8.3,,S +836,1,1,"Compton, Miss. Sara Rebecca",female,39,1,1,PC 17756,83.1583,E49,C +837,0,3,"Pasic, Mr. Jakob",male,21,0,0,315097,8.6625,,S +838,0,3,"Sirota, Mr. Maurice",male,,0,0,392092,8.05,,S +839,1,3,"Chip, Mr. Chang",male,32,0,0,1601,56.4958,,S +840,1,1,"Marechal, Mr. Pierre",male,,0,0,11774,29.7,C47,C +841,0,3,"Alhomaki, Mr. Ilmari Rudolf",male,20,0,0,SOTON/O2 3101287,7.925,,S +842,0,2,"Mudd, Mr. Thomas Charles",male,16,0,0,S.O./P.P. 3,10.5,,S +843,1,1,"Serepeca, Miss. Augusta",female,30,0,0,113798,31,,C +844,0,3,"Lemberopolous, Mr. Peter L",male,34.5,0,0,2683,6.4375,,C +845,0,3,"Culumovic, Mr. Jeso",male,17,0,0,315090,8.6625,,S +846,0,3,"Abbing, Mr. Anthony",male,42,0,0,C.A. 5547,7.55,,S +847,0,3,"Sage, Mr. Douglas Bullen",male,,8,2,CA. 2343,69.55,,S +848,0,3,"Markoff, Mr. Marin",male,35,0,0,349213,7.8958,,C +849,0,2,"Harper, Rev. John",male,28,0,1,248727,33,,S +850,1,1,"Goldenberg, Mrs. Samuel L (Edwiga Grabowska)",female,,1,0,17453,89.1042,C92,C +851,0,3,"Andersson, Master. Sigvard Harald Elias",male,4,4,2,347082,31.275,,S +852,0,3,"Svensson, Mr. Johan",male,74,0,0,347060,7.775,,S +853,0,3,"Boulos, Miss. Nourelain",female,9,1,1,2678,15.2458,,C +854,1,1,"Lines, Miss. Mary Conover",female,16,0,1,PC 17592,39.4,D28,S +855,0,2,"Carter, Mrs. Ernest Courtenay (Lilian Hughes)",female,44,1,0,244252,26,,S +856,1,3,"Aks, Mrs. Sam (Leah Rosen)",female,18,0,1,392091,9.35,,S +857,1,1,"Wick, Mrs. George Dennick (Mary Hitchcock)",female,45,1,1,36928,164.8667,,S +858,1,1,"Daly, Mr. Peter Denis ",male,51,0,0,113055,26.55,E17,S +859,1,3,"Baclini, Mrs. Solomon (Latifa Qurban)",female,24,0,3,2666,19.2583,,C +860,0,3,"Razi, Mr. Raihed",male,,0,0,2629,7.2292,,C +861,0,3,"Hansen, Mr. Claus Peter",male,41,2,0,350026,14.1083,,S +862,0,2,"Giles, Mr. Frederick Edward",male,21,1,0,28134,11.5,,S +863,1,1,"Swift, Mrs. Frederick Joel (Margaret Welles Barron)",female,48,0,0,17466,25.9292,D17,S +864,0,3,"Sage, Miss. Dorothy Edith ""Dolly""",female,,8,2,CA. 2343,69.55,,S +865,0,2,"Gill, Mr. John William",male,24,0,0,233866,13,,S +866,1,2,"Bystrom, Mrs. (Karolina)",female,42,0,0,236852,13,,S +867,1,2,"Duran y More, Miss. Asuncion",female,27,1,0,SC/PARIS 2149,13.8583,,C +868,0,1,"Roebling, Mr. Washington Augustus II",male,31,0,0,PC 17590,50.4958,A24,S +869,0,3,"van Melkebeke, Mr. Philemon",male,,0,0,345777,9.5,,S +870,1,3,"Johnson, Master. Harold Theodor",male,4,1,1,347742,11.1333,,S +871,0,3,"Balkic, Mr. Cerin",male,26,0,0,349248,7.8958,,S +872,1,1,"Beckwith, Mrs. Richard Leonard (Sallie Monypeny)",female,47,1,1,11751,52.5542,D35,S +873,0,1,"Carlsson, Mr. Frans Olof",male,33,0,0,695,5,B51 B53 B55,S +874,0,3,"Vander Cruyssen, Mr. Victor",male,47,0,0,345765,9,,S +875,1,2,"Abelson, Mrs. Samuel (Hannah Wizosky)",female,28,1,0,P/PP 3381,24,,C +876,1,3,"Najib, Miss. Adele Kiamie ""Jane""",female,15,0,0,2667,7.225,,C +877,0,3,"Gustafsson, Mr. Alfred Ossian",male,20,0,0,7534,9.8458,,S +878,0,3,"Petroff, Mr. Nedelio",male,19,0,0,349212,7.8958,,S +879,0,3,"Laleff, Mr. Kristo",male,,0,0,349217,7.8958,,S +880,1,1,"Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)",female,56,0,1,11767,83.1583,C50,C +881,1,2,"Shelley, Mrs. William (Imanita Parrish Hall)",female,25,0,1,230433,26,,S +882,0,3,"Markun, Mr. Johann",male,33,0,0,349257,7.8958,,S +883,0,3,"Dahlberg, Miss. Gerda Ulrika",female,22,0,0,7552,10.5167,,S +884,0,2,"Banfield, Mr. Frederick James",male,28,0,0,C.A./SOTON 34068,10.5,,S +885,0,3,"Sutehall, Mr. Henry Jr",male,25,0,0,SOTON/OQ 392076,7.05,,S +886,0,3,"Rice, Mrs. William (Margaret Norton)",female,39,0,5,382652,29.125,,Q +887,0,2,"Montvila, Rev. Juozas",male,27,0,0,211536,13,,S +888,1,1,"Graham, Miss. Margaret Edith",female,19,0,0,112053,30,B42,S +889,0,3,"Johnston, Miss. Catherine Helen ""Carrie""",female,,1,2,W./C. 6607,23.45,,S +890,1,1,"Behr, Mr. Karl Howell",male,26,0,0,111369,30,C148,C +891,0,3,"Dooley, Mr. Patrick",male,32,0,0,370376,7.75,,Q diff --git a/work-with-data/datasets/datasets-tutorial/train-dataset/train.py b/work-with-data/datasets/datasets-tutorial/train-dataset/train.py new file mode 100644 index 00000000..9fbca891 --- /dev/null +++ b/work-with-data/datasets/datasets-tutorial/train-dataset/train.py @@ -0,0 +1,37 @@ +import azureml.dataprep as dprep +import azureml.core +import pandas as pd +import logging +import os +import datetime +import shutil + +from azureml.core import Workspace, Datastore, Dataset, Experiment, Run +from sklearn.model_selection import train_test_split +from azureml.core.compute import ComputeTarget, AmlCompute +from azureml.core.compute_target import ComputeTargetException +from sklearn.tree import DecisionTreeClassifier + +run = Run.get_context() +workspace = run.experiment.workspace + +dataset_name = 'clean_Titanic_tutorial' + +dataset = Dataset.get(workspace=workspace, name=dataset_name) +df = dataset.to_pandas_dataframe() + +x_col = ['Pclass', 'Sex', 'SibSp', 'Parch'] +y_col = ['Survived'] +x_df = df.loc[:, x_col] +y_df = df.loc[:, y_col] + +x_train, x_test, y_train, y_test = train_test_split(x_df, y_df, test_size=0.2, random_state=223) + +data = {"train": {"X": x_train, "y": y_train}, + + "test": {"X": x_test, "y": y_test}} + +clf = DecisionTreeClassifier().fit(data["train"]["X"], data["train"]["y"]) + +print('Accuracy of Decision Tree classifier on training set: {:.2f}'.format(clf.score(x_train, y_train))) +print('Accuracy of Decision Tree classifier on test set: {:.2f}'.format(clf.score(x_test, y_test)))