Repository: GoogleCloudPlatform/tensorflow-lifetime-value Branch: master Commit: e4c775bf6549 Files: 41 Total size: 334.1 KB Directory structure: gitextract_ajebzp4v/ ├── .gitignore ├── CONTRIBUTING ├── LICENSE ├── README.md ├── clv_automl/ │ ├── __init__.py │ ├── clv_automl.py │ └── to_predict.csv ├── clv_mle/ │ ├── __init__.py │ ├── clv_ml_engine.egg-info/ │ │ ├── PKG-INFO │ │ ├── SOURCES.txt │ │ ├── dependency_links.txt │ │ ├── requires.txt │ │ └── top_level.txt │ ├── config.yaml │ ├── config_tune.json │ ├── setup.py │ ├── to_predict.csv │ ├── to_predict.json │ └── trainer/ │ ├── README.md │ ├── __init__.py │ ├── btyd.py │ ├── context.py │ ├── model.py │ └── task.py ├── linear.py ├── notebooks/ │ ├── Exploration.ipynb │ ├── clv_automl.ipynb │ └── linear_model.ipynb ├── preparation/ │ └── sql/ │ ├── common/ │ │ ├── benchmark.sql │ │ ├── clean.sql │ │ └── features_n_target.sql │ └── dnn/ │ ├── split_eval.sql │ ├── split_test.sql │ └── split_train.sql ├── requirements.txt └── run/ ├── airflow/ │ ├── dags/ │ │ ├── 01_build_train_deploy.py │ │ └── 02_predict_serve.py │ ├── gcs_datastore_transform.js │ ├── requirements.txt │ └── schema_source.json └── mltrain.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *pyc data/* jobs/* trained/* .DS_Store images/ nul .ipynb_checkpoints mykey.json run/airflow/*cfg run/airflow/airflow.db default.profraw ================================================ FILE: CONTRIBUTING ================================================ Want to contribute? Great! First, read this page (including the small print at the end). ## Before you contribute Before we can use your code, you must sign the [Google Individual Contributor License Agreement] (https://cla.developers.google.com/about/google-individual) (CLA), which you can do online. The CLA is necessary mainly because you own the copyright to your changes, even after your contribution becomes part of our codebase, so we need your permission to use and distribute your code. We also need to be sure of various other things—for instance that you'll tell us if you know that your code infringes on other people's patents. You don't have to sign the CLA until after you've submitted your code for review and a member has approved it, but you must do it before we can put your code into our codebase. Before you start working on a larger contribution, you should get in touch with us first through the issue tracker with your idea so that we can help out and possibly guide you. Coordinating up front makes it much easier to avoid frustration later on. ## Code reviews All submissions, including submissions by project members, require review. We use Github pull requests for this purpose. ## The small print Contributions made by corporations are covered by a different agreement than the one above, the [Software Grant and Corporate Contributor License Agreement] (https://cla.developers.google.com/about/google-corporate). ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ This code supports the three-part solution [Predicting Customer Lifetime Value with Cloud ML Engine](https://cloud.google.com/solutions/machine-learning/clv-prediction-with-offline-training-intro) published on cloud.google.com. This code is also used in the updated solution [Predicting Customer Lifetime Value with AutoML Tables ](https://cloud.google.com/solutions/machine-learning/clv-prediction-with-automl-tables) published on cloud.google.com. # Customer Lifetime Value Prediction on GCP This project shows how to use ML models to predict customer lifetime value in the following context: - We apply the models using [this data set](http://archive.ics.uci.edu/ml/datasets/Online+Retail) [1]. - We provide an implementation using a TensorFlow DNN model with batch normalization and dropout. - We provide an implementation, using the [Lifetimes library](https://github.com/CamDavidsonPilon/lifetimes) in Python, of [statistical models](https://rdrr.io/cran/BTYD/) commonly used in industry to perform lifetime value prediction. - We also provide an implementation using [AutoML Tables](https://cloud.google.com/automl-tables). The project also shows how to deploy a production-ready data processing pipeline for lifetime value prediction on Google Cloud Platform, using BigQuery and DataStore with orchestration provided by Cloud Composer. ## Install ### install Miniconda The code works with python 2/3. Using Miniconda2: ``` sudo apt-get install -y git bzip2 wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh bash Miniconda2-latest-Linux-x86_64.sh -b export PATH=~/miniconda2/bin:$PATH ``` ### Create dev environment ``` conda create -y -n clv source activate clv conda install -y -n clv python=2.7 pip pip install -r requirements.txt ``` ### Enable the required APIs in your GCP Project - Cloud Composer API - Machine Learning API (for TensorFlow / Lifetimes models) - Dataflow API - AutoML Tables API (for AutoML Tables models) ### Environment setup Before running the training and Airflow scripts, you need some environment variables: ``` export PROJECT=$(gcloud config get-value project 2> /dev/null) export BUCKET=gs://${PROJECT}_data_final export REGION=us-central1 export DATASET_NAME=ltv export COMPOSER_NAME="clv-final" export COMPOSER_BUCKET_NAME=${PROJECT}_composer_final export COMPOSER_BUCKET=gs://${COMPOSER_BUCKET_NAME} export DF_STAGING=${COMPOSER_BUCKET}/dataflow_staging export DF_ZONE=${REGION}-a export SQL_MP_LOCATION="sql" export LOCAL_FOLDER=$(pwd) ``` ### Data setup Creating the BigQuery workspace: ``` gcloud storage buckets create ${BUCKET} --location ${REGION} --project ${PROJECT} gcloud storage buckets create ${COMPOSER_BUCKET} --location ${REGION} --project ${PROJECT} bq --location=US mk --dataset ${PROJECT}:${DATASET_NAME} ``` Create a datastore database as detailed in the [Datastore documentation](https://cloud.google.com/datastore/docs/quickstart) ### Copy the raw dataset ``` gcloud storage cp gs://solutions-public-assets/ml-clv/db_dump.csv ${BUCKET} gcloud storage cp ${BUCKET}/db_dump.csv ${COMPOSER_BUCKET} ``` ### Copy the dataset to be predicted. Replace with your own. ``` gcloud storage cp clv_mle/to_predict.json ${BUCKET}/predictions/ gcloud storage cp ${BUCKET}/predictions/to_predict.json ${COMPOSER_BUCKET}/predictions/ gcloud storage cp clv_mle/to_predict.csv ${BUCKET}/predictions/ gcloud storage cp ${BUCKET}/predictions/to_predict.csv ${COMPOSER_BUCKET}/predictions/ ``` ### Create a service account Creating a service account is important to make sure that your Cloud Composer instance can perform the required tasks within BigQuery, AutoML Tables, ML Engine, Dataflow, Cloud Storage and Datastore. It is also needed to run training for AutoML locally. The following creates a service account called composer@[YOUR-PROJECT-ID].iam.gserviceaccount.com. and assigns the required roles to the service account. ``` gcloud iam service-accounts create composer --display-name composer --project ${PROJECT} gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/composer.worker gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/bigquery.dataEditor gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/bigquery.jobUser gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/storage.admin gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/ml.developer gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/dataflow.developer gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/compute.viewer gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role roles/storage.objectAdmin gcloud projects add-iam-policy-binding ${PROJECT} \ --member serviceAccount:composer@${PROJECT}.iam.gserviceaccount.com \ --role='roles/automl.editor' ``` Wait until the service account has all the proper roles setup. ### Download API Key for AutoML Tables [Create a service account API key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) and download the json keyfile to run training for AutoML locally. ### Upload Machine Learning Engine packaged file If using the TensorFlow or Lifetimes model, do this once. If you make changes to any of the Python files in clv_mle, you need to repeat. ``` cd ${LOCAL_FOLDER}/clv_mle rm -rf clv_ml_engine.egg-info/ rm -rf dist python setup.py sdist gcloud storage cp dist/* ${COMPOSER_BUCKET}/code/ ``` ## [Optional] launch Jupyter The ```notebooks``` folder contains notebooks for data exploration and modeling with linear models and AutoML Tables. ``` jupyter notebook ``` If you are interested in using Jupyter with Datalab, you can do the following: ``` jupyter nbextension install --py datalab.notebook --sys-prefix jupyter notebook ``` And enter in the first cell of your Notebook ``` %load_ext google.datalab.kernel ``` ## Train and Tune Models ### AutoML Tables You can train the model using the script clv_automl/clv_automl.py. This takes several parameters. See usage for full params and default values. Make sure you have downloaded the json API key. By default this is assumed to be in a file ```mykey.json``` in the same directory as the script. For example: ``` cd ${LOCAL_FOLDER}/clv_automl python clv_automl.py --project_id [YOUR_PROJECT] ``` ### TensorFlow DNN/Lifetimes To run training or hypertuning for the non-automl models you can use the mltrain.sh script. It must be run from the top level directory, as in the examples below. For ML Engine jobs you must supply a bucket on GCS. The job data folder will be gs://bucket/data and the job directory will be gs://bucket/jobs. So your data files must already be in gs://bucket/data. If you use ${COMPOSER_BUCKET}, and the DAG has been run at least once, the data files will be present. For DNN models the data should be named 'train.csv', 'eval.csv' and 'test.csv', for probablistic models the file must be 'btyd.csv'. For example: Train the DNN model on local data: ``` cd ${LOCAL_FOLDER} gcloud storage cp --recursive ${COMPOSER_BUCKET}/data . run/mltrain.sh local data ``` Train the DNN model in a Cloud ML Engine job on data in the ${COMPOSER_BUCKET}: ``` run/mltrain.sh train ${COMPOSER_BUCKET} ``` Run hyperparameter tuning on Cloud ML Engine: ``` run/mltrain.sh tune gs://your-bucket ``` For statistical models: ``` run/mltrain.sh local data --model_type paretonbd_model --threshold_date 2011-08-08 --predict_end 2011-12-12 ``` ## Automation with AirFlow This code is set to run automatically using Cloud Composer, a google-managed version of Airflow. The following steps describe how to go from your own copy of the data to a deployed model with results exported both in Datastore and BigQuery. See [part three of the solution](https://cloud.google.com/solutions/machine-learning/clv-prediction-with-offline-training-deploy) for more details. ### Set up Cloud Composer #### Create a composer instance with the service account This will take a while. This project assumes Airflow 1.9.0, which is the default for Cloud Composer as of March 2019. ``` gcloud composer environments create ${COMPOSER_NAME} \ --location ${REGION} \ --zone ${REGION}-f \ --machine-type n1-standard-1 \ --service-account=composer@${PROJECT}.iam.gserviceaccount.com ``` #### Make SQL files available to the DAG There are various ways of calling BigQuery queries. This solutions leverages BigQuery files directly. For them to be accessible by the DAGs, they need to be in the same folder. The following command line, copies the entire sql folder as a subfolder in the Airflow dags folder. ``` cd ${LOCAL_FOLDER}/preparation gcloud composer environments storage dags import \ --environment ${COMPOSER_NAME} \ --source ${SQL_MP_LOCATION} \ --location ${REGION} \ --project ${PROJECT} ``` #### Other files Some files are important when running the DAG. They need to be placed in the composer bucket: 1 - The BigQuery schema file used to load data into BigQuery ``` cd ${LOCAL_FOLDER} gcloud storage cp ./run/airflow/schema_source.json ${COMPOSER_BUCKET} ``` 2 - A Javascript file used by the Dataflow template for processing. ``` gcloud storage cp ./run/airflow/gcs_datastore_transform.js ${COMPOSER_BUCKET} ``` #### Set Composer environment variables Region where things happen ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ region ${REGION} ``` Staging location for Dataflow ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ df_temp_location ${DF_STAGING} ``` Zone where Dataflow should run ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ df_zone ${DF_ZONE} ``` BigQuery working dataset ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ dataset ${DATASET_NAME} ``` Composer bucket ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ composer_bucket_name ${COMPOSER_BUCKET_NAME} ``` #### (for AutoML Tables) Composer environment variables AutoML Dataset name ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ automl_dataset "clv_solution" ``` AutoML Model name ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ automl_model "clv_model" ``` AutoML training budget ``` gcloud composer environments run ${COMPOSER_NAME} --location ${REGION} variables set \ -- \ automl_training_budget "1" ``` #### (for AutoML Tables) Import AutoML libraries ``` gcloud composer environments storage dags import \ --environment ${COMPOSER_NAME} \ --source clv_automl \ --location ${REGION} \ --project ${PROJECT} gcloud composer environments update ${COMPOSER_NAME} \ --update-pypi-packages-from-file run/airflow/requirements.txt \ --location ${REGION} \ --project ${PROJECT} ``` #### Import DAGs You need to run this for all your dag files. This solution has two DAGs located in the [run/airflow/dags](run/airflow/dags) folder. ``` gcloud composer environments storage dags import \ --environment ${COMPOSER_NAME} \ --source run/airflow/dags/01_build_train_deploy.py \ --location ${REGION} \ --project ${PROJECT} gcloud composer environments storage dags import \ --environment ${COMPOSER_NAME} \ --source run/airflow/dags/02_predict_serve.py \ --location ${REGION} \ --project ${PROJECT} ``` ### Run DAGs You now should have both DAGs and the SQL files in the Cloud Composer's reserved bucket. Because you probably want to run training and prediction tasks independently, you can run the following script as needed. For more automatic runs (like daily for example), refer to the Airflow documentation to setup your DAGs accordingly. Airflow can take various parameters as inputs. The following are used within the .sql files through the syntax {{ dag_run.conf['PARAMETER-NAME'] }} - project: Project ID where the data is located - dataset: Dataset that is used to write and read the data - predict_end: When is the final date of the whole sales dataset - threshold_date: What is the data used to split the data Other variables are important as they depend on your environment and are passed directly to the Operators: - model_type: Name of the model that you want to use. Should be either 'automl' or one of the options from model.py - project: Your project id - dataset: Your dataset id - threshold_date: Date that separates features from target - predict_end: End date of the dataset - model_name: Name of the model saved to AutoML Tables or Machine Learning Engine - model_version: Name of the version of model_name save to Machine Learning Engine (not used for AutoML Tables) - tf_version: Tensorflow version to be used - max_monetary: Monetary cap to discard all customers that spend more than that amount ``` gcloud composer environments run ${COMPOSER_NAME} \ --project ${PROJECT} \ --location ${REGION} \ dags trigger \ -- \ build_train_deploy \ --conf '{"model_type":"automl", "project":"'${PROJECT}'", "dataset":"'${DATASET_NAME}'", "threshold_date":"2011-08-08", "predict_end":"2011-12-12", "model_name":"clv_automl", "model_version":"v1", "tf_version":"1.10", "max_monetary":"15000"}' ``` ``` gcloud composer environments run ${COMPOSER_NAME} \ --project ${PROJECT} \ --location ${REGION} \ dags trigger \ -- \ predict_serve \ --conf '{"model_name":"clv_automl", "model_version":"v1", "dataset":"'${DATASET_NAME}'"}' ``` ### Disclaimer: This is not an official Google product [1]: Dua, D. and Karra Taniskidou, E. (2017). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science. ================================================ FILE: clv_automl/__init__.py ================================================ ================================================ FILE: clv_automl/clv_automl.py ================================================ # Copyright 2019 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from __future__ import print_function from __future__ import absolute_import import argparse from google.cloud.automl_v1beta1 import AutoMlClient, PredictionServiceClient import sys import time # parameter defaults KEY_FILE = 'mykey.json' LOCATION = 'us-central1' BQ_DATASET = 'ltv_edu_auto' BQ_TABLE = 'features_n_target' AUTOML_DATASET = 'clv_solution' TARGET_LABEL = 'target_monetary' AUTOML_MODEL = 'clv_model' BATCH_GCS_INPUT = 'gs://' BATCH_GCS_OUTPUT = 'gs://' def create_automl_model(client, project_id, location, bq_dataset, bq_table, automl_dataset, automl_model, training_budget): """ Create an AutoML Tables dataset based on the data in BigQuery. Create a model to predict CLV based on that dataset. Returns: The name of the created model. """ location_path = client.location_path(project_id, location) dataset_display_name = automl_dataset # create dataset create_dataset_response = client.create_dataset( location_path, {'display_name': dataset_display_name, 'tables_dataset_metadata': {}}) print("Creating AutoML Tables dataset...") dataset_name = create_dataset_response.name print("Done") # import data dataset_bq_input_uri = 'bq://{}.{}.{}'.format(project_id, bq_dataset, bq_table) input_config = { 'bigquery_source': { 'input_uri': dataset_bq_input_uri}} print("Importing data...") import_data_response = client.import_data(dataset_name, input_config) while import_data_response.done() is False: time.sleep(1) print("Done") # get column specs list_table_specs_response = client.list_table_specs(dataset_name) table_specs = [s for s in list_table_specs_response] table_spec_name = table_specs[0].name list_column_specs_response = client.list_column_specs(table_spec_name) column_specs = {s.display_name: s for s in list_column_specs_response} # update dataset to assign a label label_column_name = TARGET_LABEL label_column_spec = column_specs[label_column_name] label_column_id = label_column_spec.name.rsplit('/', 1)[-1] update_dataset_dict = { 'name': dataset_name, 'tables_dataset_metadata': { 'target_column_spec_id': label_column_id } } print("Setting label...") update_dataset_response = client.update_dataset(update_dataset_dict) print("Done") # define the features used to train the model feat_list = list(column_specs.keys()) feat_list.remove('target_monetary') feat_list.remove('customer_id') feat_list.remove('monetary_btyd') feat_list.remove('frequency_btyd') feat_list.remove('frequency_btyd_clipped') feat_list.remove('monetary_btyd_clipped') feat_list.remove('target_monetary_clipped') # create and train the model model_display_name = automl_model model_training_budget = training_budget * 1000 model_dict = { 'display_name': model_display_name, 'dataset_id': dataset_name.rsplit('/', 1)[-1], 'tables_model_metadata': { 'target_column_spec': column_specs['target_monetary'], 'input_feature_column_specs': [ column_specs[x] for x in feat_list], 'train_budget_milli_node_hours': model_training_budget, 'optimization_objective': 'MINIMIZE_MAE' } } print("Creating AutoML Tables model...") create_model_response = client.create_model(location_path, model_dict) while create_model_response.done() is False: time.sleep(10) print("Done") create_model_result = create_model_response.result() model_name = create_model_result.name return model_name def deploy_model(client, model_name): """ Deploy model for predictions. """ print("Deploying AutoML Tables model...") deploy_model_response = client.deploy_model(model_name) api = client.transport._operations_client while deploy_model_response.done is False: deploy_model_response = api.get_operation(deploy_model_response.name) time.sleep(10) print("Done") def get_model_evaluation(client, model_name): """ Get the evaluation stats for the model. """ model_evaluations = [e for e in client.list_model_evaluations(model_name)] model_evaluation = model_evaluations[0] print("Model evaluation:") print(model_evaluation) return model_evaluation def do_batch_prediction(prediction_client, model_name, gcs_input_uri, gcs_output_uri_prefix): # Define input source. batch_prediction_input_source = { 'gcs_source': { 'input_uris': [gcs_input_uri] } } # Define output target. batch_prediction_output_target = { 'gcs_destination': { 'output_uri_prefix': gcs_output_uri_prefix } } # initiate batch predict print('Performing AutoML Tables batch predict...') batch_predict_response = prediction_client.batch_predict( model_name, batch_prediction_input_source, batch_prediction_output_target) # Wait until batch prediction is done. while batch_predict_response.done() is False: time.sleep(1) print('Done') batch_predict_result = batch_predict_response.result() return batch_predict_result def create_parser(): """Initialize command line parser using argparse. Returns: An argparse.ArgumentParser. """ parser = argparse.ArgumentParser() # required args parser.add_argument('--project_id', help='Project id for project containing BQ data', default=KEY_FILE, type=str, required=True) # data and model args parser.add_argument('--training_budget', help='Training budget in hours', default=1, type=int) parser.add_argument('--key_file', help='JSON key file for API access', default=KEY_FILE, type=str) parser.add_argument('--location', help='GCP region to run', default=LOCATION, type=str) parser.add_argument('--automl_dataset', help='Name of AutoML dataset', default=AUTOML_DATASET, type=str) parser.add_argument('--automl_model', help='Name of AutoML model', default=AUTOML_MODEL, type=str) parser.add_argument('--bq_dataset', help='BigQuery dataset to import from', default=BQ_DATASET, type=str) parser.add_argument('--bq_table', help='BigQuery table to import from', default=BQ_TABLE, type=str) parser.add_argument('--batch_gcs_input', help='GCS URI for batch predict CSV', default=BATCH_GCS_INPUT, type=str) parser.add_argument('--batch_gcs_output', help='GCS URI for batch predict output', default=BATCH_GCS_OUTPUT, type=str) return parser def main(argv=None): """Create and train the CLV model on AutoML Tables.""" argv = sys.argv if argv is None else argv args = create_parser().parse_args(args=argv[1:]) # create and configure client keyfile_name = args.key_file client = AutoMlClient.from_service_account_file(keyfile_name) # create and deploy model model_name = create_automl_model(client, args.project_id, args.location, args.bq_dataset, args.bq_table, args.automl_dataset, args.automl_model, args.training_budget) # deploy model deploy_model(client, model_name) # get model evaluations model_evaluation = get_model_evaluation(client, model_name) # make predictions prediction_client = PredictionServiceClient.from_service_account_file( keyfile_name) do_batch_prediction(prediction_client, model_name, args.batch_gcs_input, args.batch_gcs_output) if __name__ == '__main__': main() ================================================ FILE: clv_automl/to_predict.csv ================================================ customer_id,monetary,recency,frequency,avg_basket_value,avg_basket_size,date_range,cnt_orders,cnt_returns,has_returned 0123456789,1000.0,10,2,240,4.0,240.0,13.0,1,1 1234567890,500.0,20,3,250,5.0,250.0,15.0,1,1 ================================================ FILE: clv_mle/__init__.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ================================================ FILE: clv_mle/clv_ml_engine.egg-info/PKG-INFO ================================================ Metadata-Version: 1.0 Name: clv-ml-engine Version: 0.1 Summary: A trainer application package for CLV prediction on ML Engine Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN ================================================ FILE: clv_mle/clv_ml_engine.egg-info/SOURCES.txt ================================================ setup.py clv_ml_engine.egg-info/PKG-INFO clv_ml_engine.egg-info/SOURCES.txt clv_ml_engine.egg-info/dependency_links.txt clv_ml_engine.egg-info/requires.txt clv_ml_engine.egg-info/top_level.txt trainer/__init__.py trainer/btyd.py trainer/context.py trainer/model.py trainer/task.py ================================================ FILE: clv_mle/clv_ml_engine.egg-info/dependency_links.txt ================================================ ================================================ FILE: clv_mle/clv_ml_engine.egg-info/requires.txt ================================================ sh lifetimes==0.9.0.0 numpy==1.14.5 tensorflow==1.10 ================================================ FILE: clv_mle/clv_ml_engine.egg-info/top_level.txt ================================================ trainer ================================================ FILE: clv_mle/config.yaml ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. trainingInput: masterType: standard ================================================ FILE: clv_mle/config_tune.json ================================================ { "trainingInput": { "scaleTier": "CUSTOM", "masterType": "complex_model_m", "hyperparameters": { "goal": "MINIMIZE", "hyperparameterMetricTag": "rmse", "params": [ { "parameterName": "learning_rate", "type": "DOUBLE", "minValue": "0.0001", "maxValue": "0.1", "scaleType": "UNIT_REVERSE_LOG_SCALE" }, { "parameterName": "l1_regularization", "type": "DOUBLE", "minValue": "0.0001", "maxValue": "0.1", "scaleType": "UNIT_REVERSE_LOG_SCALE" }, { "parameterName": "l2_regularization", "type": "DOUBLE", "minValue": "0.0001", "maxValue": "0.1", "scaleType": "UNIT_REVERSE_LOG_SCALE" }, { "parameterName": "dropout", "minValue": 0.01, "maxValue": 0.99, "type": "DOUBLE", "scaleType": "UNIT_REVERSE_LOG_SCALE" }, { "parameterName": "learning_decay_rate", "minValue": 0.6, "maxValue": 0.99, "type": "DOUBLE", "scaleType": "UNIT_LINEAR_SCALE" }, { "parameterName": "hidden_units", "type": "CATEGORICAL", "categoricalValues": ["256 128 64 32", "128 64 32 16", "256 128 64 32 16", "128 64 32 16 16 8"] }, { "parameterName": "batch_size", "type": "DISCRETE", "discreteValues": ["5", "10", "15", "20", "25", "30"], } ], "maxTrials": 1000, "maxParallelTrials": 10 } } } ================================================ FILE: clv_mle/setup.py ================================================ # Copyright 2017 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from setuptools import find_packages from setuptools import setup REQUIRED_PACKAGES = ['sh', 'lifetimes==0.9.0.0', 'numpy==1.14.5', 'tensorflow==1.10'] setup( name='clv_ml_engine', version='0.1', install_requires=REQUIRED_PACKAGES, packages=find_packages(), include_package_data=True, description='A trainer application package for CLV prediction on ML Engine' ) ================================================ FILE: clv_mle/to_predict.csv ================================================ 0123456789,1000.0,10,2,240,4.0,240.0,13.0,1,1 1234567890,500.0,20,3,250,5.0,250.0,15.0,1,1 ================================================ FILE: clv_mle/to_predict.json ================================================ {"customer_id":"abc", "monetary": 1000.0, "recency": 20.0, "frequency": 3.0, "avg_basket_value": 250.0, "avg_basket_size": 5.0, "date_range": 250, "cnt_orders": 15, "cnt_returns": 1, "has_returned": 1} {"customer_id":"cde", "monetary": 500.0, "recency": 20.0, "frequency": 3.0, "avg_basket_value": 250.0, "avg_basket_size": 5.0, "date_range": 250, "cnt_orders": 15, "cnt_returns": 1, "has_returned": 1} ================================================ FILE: clv_mle/trainer/README.md ================================================ ================================================ FILE: clv_mle/trainer/__init__.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ================================================ FILE: clv_mle/trainer/btyd.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Core functions for Probabilistic (BTYD) models.""" from __future__ import print_function from __future__ import absolute_import from datetime import datetime from lifetimes import BetaGeoFitter, ParetoNBDFitter, GammaGammaFitter import math import numpy as np import os import pandas as pd import tensorflow as tf from .model import PARETO, BGNBD PENALIZER_COEF = 0.01 DISCOUNT_RATE = 0.01 TRAINING_DATA_FILE = 'btyd.csv' OUTPUT_FILE = 'predictions.csv' def load_data(datapath): """Loads data from CSV data file. Args: datapath: Location of the training file Returns: summary dataframe containing RFM data for btyd models actuals_df containing additional data columns for calculating error """ # Does not used the summary_data_from_transaction_data from the Lifetimes # library as it wouldn't scale as well. The pre-processing done in BQ instead. tf.logging.info('Loading data...') ft_file = '{0}/{1}'.format(datapath, TRAINING_DATA_FILE) #[START prob_selec] df_ft = pd.read_csv(ft_file) # Extracts relevant dataframes for RFM: # - summary has aggregated values before the threshold date # - actual_df has values of the overall period. summary = df_ft[['customer_id', 'frequency_btyd', 'recency', 'T', 'monetary_btyd']] #[END prob_selec] summary.columns = ['customer_id', 'frequency', 'recency', 'T', 'monetary_value'] summary = summary.set_index('customer_id') # additional columns needed for calculating error actual_df = df_ft[['customer_id', 'frequency_btyd', 'monetary_dnn', 'target_monetary']] actual_df.columns = ['customer_id', 'train_frequency', 'train_monetary', 'act_target_monetary'] tf.logging.info('Data loaded.') return summary, actual_df def bgnbd_model(summary): """Instantiate and fit a BG/NBD model. Args: summary: RFM transaction data Returns: bgnbd model fit to the data """ bgf = BetaGeoFitter(penalizer_coef=PENALIZER_COEF) bgf.fit(summary['frequency'], summary['recency'], summary['T']) return bgf def paretonbd_model(summary): """Instantiate and fit a Pareto/NBD model. Args: summary: RFM transaction data Returns: bgnbd model fit to the data """ #[START run_btyd] paretof = ParetoNBDFitter(penalizer_coef=PENALIZER_COEF) paretof.fit(summary['frequency'], summary['recency'], summary['T']) return paretof #[END run_btyd] def run_btyd(model_type, data_src, threshold_date, predict_end): """Run selected BTYD model on data files located in args.data_src. Args: model_type: model type (PARETO, BGNBD) data_src: path to data threshold_date: end date for training data 'YYYY-mm-dd' predict_end: end date for predictions 'YYYY-mm-dd' """ train_end_date = datetime.strptime(threshold_date, '%Y-%m-%d') predict_end_date = datetime.strptime(predict_end, '%Y-%m-%d') # load training transaction data summary, actual_df = load_data(data_src) # train fitter for selected model tf.logging.info('Fitting model...') if model_type == PARETO: fitter = paretonbd_model(summary) elif model_type == BGNBD: fitter = bgnbd_model(summary) tf.logging.info('Done.') # # use trained fitter to compute actual vs predicted ltv for each user # # compute the number of days in the prediction period time_days = (predict_end_date - train_end_date).days time_months = int(math.ceil(time_days / 30.0)) # fit gamma-gamma model tf.logging.info('Fitting GammaGamma model...') ggf = GammaGammaFitter(penalizer_coef=0) ggf.fit(summary['frequency'], summary['monetary_value']) tf.logging.info('Done.') ltv, rmse = predict_value(summary, actual_df, fitter, ggf, time_days, time_months) # output results to csv output_file = os.path.join(data_src, OUTPUT_FILE) ltv.to_csv(output_file, index=False) # log results tf.logging.info('BTYD RMSE error for %s model: %.2f', model_type, rmse) print('RMSE prediction error: %.2f' % rmse) def predict_value(summary, actual_df, fitter, ggf, time_days, time_months): """Predict lifetime values for customers. Args: summary: RFM transaction data actual_df: dataframe containing data fields for customer id, actual customer values fitter: lifetimes fitter, previously fit to data ggf: lifetimes gamma/gamma fitter, already fit to data time_days: time to predict purchases in days time_months: time to predict value in months Returns: ltv: dataframe with predicted values for each customer, along with actual values and error rmse: root mean squared error summed over all customers """ # setup dataframe to hold results ltv = pd.DataFrame(data=np.zeros([actual_df.shape[0], 6]), columns=['customer_id', 'actual_total', 'predicted_num_purchases', 'predicted_value', 'predicted_total', 'error'], dtype=np.float32) predicted_num_purchases = fitter.predict(time_days, summary['frequency'], summary['recency'], summary['T']) predicted_value = ggf.customer_lifetime_value(fitter, summary['frequency'], summary['recency'], summary['T'], summary['monetary_value'], time=time_months, discount_rate=DISCOUNT_RATE) ltv['customer_id'] = actual_df['customer_id'] ltv['actual_total'] = actual_df['act_target_monetary'] ltv['predicted_num_purchases'] = predicted_num_purchases.values ltv['predicted_value'] = predicted_value.values ltv['predicted_total'] = actual_df['train_monetary'] + ltv['predicted_value'] ltv['error'] = ltv['actual_total'] - ltv['predicted_total'] mse = pd.Series.sum(ltv['error'] * ltv['error']) / ltv.shape[0] rmse = math.sqrt(mse) return ltv, rmse ================================================ FILE: clv_mle/trainer/context.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Feature definition and processing.""" from tensorflow import feature_column as tfc from six import iteritems class CLVFeatures(object): """Encapulates the features for Estimator models.""" # Columns HEADERS = ['customer_id', 'monetary_dnn', 'monetary_btyd', 'frequency_dnn', 'frequency_btyd', 'recency', 'T', 'time_between', 'avg_basket_value', 'avg_basket_size', 'cnt_returns', 'has_returned', 'frequency_btyd_clipped', 'monetary_btyd_clipped', 'target_monetary_clipped', 'target_monetary'] HEADERS_DEFAULT = [[''], [0.0], [0.0], [0], [0], [0], [0], [0.0], [0.0], [0.0], [0], [-1], [0], [0.0], [0.0], [0.0]] NUMERICS = { 'monetary_dnn': [], 'recency': [], 'frequency_dnn': [], 'T': [], 'time_between': [], 'avg_basket_value': [], 'avg_basket_size': [], 'cnt_returns': []} CATEGORICALS_W_LIST = { 'has_returned': [0, 1]} # Columns to cross (name, bucket_size, boundaries) # Note that boundaries is None if we have all the values. This will helps # using between categorical_column_with_identity vs bucketized_column # max(recency)=33383 # max(frequency) = 300 # max(monetary) = 3809291.2 CROSSED = [] KEY = 'customer_id' UNUSED = [KEY, 'monetary_btyd', 'frequency_btyd', 'frequency_btyd_clipped', 'monetary_btyd_clipped', 'target_monetary_clipped'] TARGET_NAME = 'target_monetary' def __init__(self, ignore_crosses=False, is_dnn=None): """Initialize CLVFeatures. Args: ignore_crosses: Whether to apply crosses or not is_dnn: Whether the model is a dnn one or not. """ if not is_dnn: return self.ignore_crosses = ignore_crosses # Initializes features names that will be used. (self.headers, self.numerics_names, self.categoricals_names) = self._keep_used() # Creates the base continuous and categorical features self.continuous, self.categorical = self._make_base_features() # Creates the crossed features for both wide and deep. if not self.ignore_crosses: self.crossed_for_wide, self.crossed_for_deep = self._make_crossed() def _keep_used(self): """Returns only the used headers names. Returns: used_headers names """ headers = [h for h in self.HEADERS if h not in self.UNUSED] numerics_names = { k: v for k, v in iteritems(self.NUMERICS) if (k not in self.UNUSED) and (k != self.TARGET_NAME) } categoricals_names = { k: v for k, v in iteritems(self.CATEGORICALS_W_LIST) if k not in self.UNUSED } return headers, numerics_names, categoricals_names def get_key(self): return self.KEY def get_used_headers(self, with_key=False, with_target=False): """Returns headers that are useful to the model. Possibly includes the key and the target. Args: with_key: include KEY column with_target: include target column Returns: used_headers """ used_headers = [h for h in self.headers if h != self.TARGET_NAME] if with_key: used_headers.insert(0, self.KEY) if with_target: used_headers.append(self.TARGET_NAME) return used_headers def get_defaults(self, headers_names=None, with_key=False): """Returns default values based on indexes taken from the headers to keep. If key and target are to keep, it is decided in get_used_headers. Args: headers_names: column header names with_key: include KEY column Returns: default values """ if headers_names is None: headers_names = self.get_used_headers(with_key) keep_indexes = [self.HEADERS.index(n) for n in headers_names] return [self.HEADERS_DEFAULT[i] for i in keep_indexes] def get_all_names(self): return self.HEADERS def get_all_defaults(self): return self.HEADERS_DEFAULT def get_unused(self): return self.UNUSED def get_target_name(self): return self.TARGET_NAME ##################### # Features creation # ##################### # dense columns = numeric columns + embedding columns # categorical columns = vocabolary list columns + bucketized columns # sparse columns = hashed categorical columns + crossed columns # categorical columns => indicator columns # deep columns = dense columns + indicator columns # wide columns = categorical columns + sparse columns def _make_base_features(self): """Make base features. Returns: base features """ # Continuous columns continuous = {key_name: tfc.numeric_column(key_name) for key_name in self.numerics_names.keys()} # Categorical columns (can contain all categorical_column_with_*) categorical = { key_name: tfc.categorical_column_with_vocabulary_list( key=key_name, vocabulary_list=voc) for key_name, voc in self.categoricals_names.items() } return continuous, categorical def get_base_features(self): # Could create bucket or/and hash here before return return self.continuous, self.categorical def _prepare_for_crossing(self, key_name, num_bck, boundaries): """Prepares features for crossing. Whether they're continuous or categorical matters, and whether we have the whole dictionary or not. Args: key_name: A string representing the name of the feature num_bck: How many buckets to use when we know # of distinct values boundaries: Range used for boundaries when bucketinizing Returns: key name """ key = None if key_name in self.continuous.keys(): if boundaries is not None: # Note that cont[key_name] is a source column key = tfc.bucketized_column(self.continuous[key_name], boundaries) else: # We can count all the values in the dataset. Ex: boolean. # Note that key_name is a string key = tfc.categorical_column_with_identity(key_name, num_bck) elif key_name in self.categorical.keys(): # It is also possible to use the categorical column instead of the # column name. i.e key = cat[key_name] key = key_name else: key = key_name return key def _make_crossed(self): """Makes crossed features for both Wide or Deep network. Returns: Tuple (crossed columns for Wide, its dimension) """ # Crossed columns f_crossed_for_wide = [] f_crossed_for_deep = [] for to_cross in self.CROSSED: keys = [] bck_size = 1 for (key, bck, bnd) in to_cross: keys.append(self._prepare_for_crossing(key, bck, bnd)) bck_size *= bck # We can't go crazy on the dim for crossed_column so use a min # **0.25 is a rule of thumb for bucket size vs dimension t_crossed = tfc.crossed_column(keys, min(bck_size, 10000)) t_dimension = int(bck_size**0.25) f_crossed_for_wide.append(t_crossed) f_crossed_for_deep.append(tfc.embedding_column(t_crossed, t_dimension)) return f_crossed_for_wide, f_crossed_for_deep def get_wide_features(self): """Creates wide features. Sparse (ie. hashed categorical + crossed) + categorical. Returns: A list of wide features """ # Base sparse (ie categorical) feature columns + crossed wide_features = self.categorical.values() if not self.ignore_crosses: wide_features += self.crossed_for_wide return wide_features def get_deep_features(self, with_continuous=True): """Creates deep features: dense(ie numeric + embedding) + indicator. Args: with_continuous: include continuous columns Returns: features for DNN """ # Multi-hot representation of categories. We know all the values so use # indicator_column. If the vocabulary could be bigger in the outside # world, we'd use embedding_column deep_features = [tfc.indicator_column(f) for f in self.categorical.values()] # Creates deep feature lists if with_continuous: deep_features += self.continuous.values() if not self.ignore_crosses: deep_features += self.crossed_for_deep return deep_features ================================================ FILE: clv_mle/trainer/model.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """DNN Estimator model code.""" from __future__ import print_function from __future__ import absolute_import import tensorflow as tf from .context import CLVFeatures # Possible estimators: # Canned: https://www.tensorflow.org/api_docs/python/tf/estimator or custom ones CANNED_MODEL_TYPES = ['DNNRegressor', 'Linear'] MODEL_TYPES = CANNED_MODEL_TYPES[:] + ['dnn_model', 'paretonbd_model', 'bgnbd_model'] CANNED_DEEP, LINEAR, DEEP, PARETO, BGNBD = MODEL_TYPES PROBABILISTIC_MODEL_TYPES = [PARETO, BGNBD] # Either a custom function or a canned estimator name # Used as default it not passed as an argument when calling the task MODEL_TYPE = DEEP # Features clvf = CLVFeatures( ignore_crosses=True, is_dnn=MODEL_TYPE not in PROBABILISTIC_MODEL_TYPES) def parse_csv(csv_row): """Parse CSV data row. tf.data.Dataset.map takes a function as an input so need to call parse_fn using map(lamba x: parse_fn(x)) or do def parse_fn and return the function as we do here. Builds a pair (feature dictionary, label) tensor for each example. Args: csv_row: one example as a csv row coming from the Dataset.map() Returns: features and targets """ columns = tf.decode_csv(csv_row, record_defaults=clvf.get_all_defaults()) features = dict(zip(clvf.get_all_names(), columns)) # Remove the headers that we don't use for column_name in clvf.get_unused(): features.pop(column_name) target = features.pop(clvf.get_target_name()) return features, target def dataset_input_fn(data_folder, prefix=None, mode=None, params=None, count=None): """Creates a dataset reading example from filenames. Args: data_folder: Location of the files finishing with a '/' prefix: Start of the file names mode: tf.estimator.ModeKeys(TRAIN, EVAL) params: hyperparameters Returns: features and targets """ shuffle = True if mode == tf.estimator.ModeKeys.TRAIN else False # Read CSV files into a Dataset filenames = tf.matching_files('{}{}*.csv'.format(data_folder, prefix)) dataset = tf.data.TextLineDataset(filenames) # Parse the record into tensors. dataset = dataset.map(parse_csv) # Shuffle the dataset if shuffle: dataset = dataset.shuffle(buffer_size=params.buffer_size) # Repeat the input indefinitely if count is None dataset = dataset.repeat(count=count) # Generate batches dataset = dataset.batch(params.batch_size) # Create a one-shot iterator iterator = dataset.make_one_shot_iterator() # Get batch X and y features, target = iterator.get_next() return features, target def read_train(data_folder, params): """Returns a shuffled dataset for training.""" return dataset_input_fn( data_folder=data_folder, prefix='train', params=params, mode=tf.estimator.ModeKeys.TRAIN) def read_eval(data_folder, params): """Returns a dataset for evaluation.""" return dataset_input_fn(data_folder=data_folder, prefix='eval', params=params) def read_test(data_folder, params): """Returns a dataset for test.""" return dataset_input_fn(data_folder=data_folder, prefix='test', params=params, count=1) ##################### # Model Definitions # ##################### def dnn_model(features, mode, params): """Creates a DNN regressor model. Args: features: list of feature_columns mode: tf.estimator.ModeKeys(TRAIN, EVAL) params: hyperparameters Returns: output tensor """ # Make features feature_columns = clvf.get_deep_features() # Creates the input layers from the features. h = tf.feature_column.input_layer(features=features, feature_columns=feature_columns) # Loops through the layers. for size in params.hidden_units: h = tf.layers.dense(h, size, activation=None) h = tf.layers.batch_normalization(h, training=( mode == tf.estimator.ModeKeys.TRAIN)) h = tf.nn.relu(h) if (params.dropout is not None) and (mode == tf.estimator.ModeKeys.TRAIN): h = tf.layers.dropout(h, params.dropout) logits = tf.layers.dense(h, 1, activation=None) return logits def model_fn(features, labels, mode, params): """Model function for custom Estimator. Args: features: given by dataset_input_fn() tuple labels: given by dataset_input_fn() tuple mode: given when calling the estimator.train/predict/evaluate function params: hyperparameters Returns: EstimatorSpec that can be used by tf.estimator.Estimator. """ # Build the dnn model and get output logits logits = dnn_model(features, mode, params) # Reshape output layer to 1-dim Tensor to return predictions output = tf.squeeze(logits) # Returns an estimator spec for PREDICT. if mode == tf.estimator.ModeKeys.PREDICT: #[START prediction_output_format] predictions = { 'customer_id': tf.squeeze(features[clvf.get_key()]), 'predicted_monetary': output } export_outputs = { 'predictions': tf.estimator.export.PredictOutput(predictions) } return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions, export_outputs=export_outputs) #[END prediction_output_format] # Calculates loss using mean squared error between the given labels # and the calculated output. loss = tf.losses.mean_squared_error(labels, output) # Create Optimizer and thhe train operation optimizer = get_optimizer(params) # add update ops for batch norm stats update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) with tf.control_dependencies(update_ops): train_op = optimizer.minimize(loss=loss, global_step=tf.train.get_global_step()) # Root mean square error eval metric eval_metric_ops = { 'rmse': tf.metrics.root_mean_squared_error(labels, output) } # Returns an estimator spec for EVAL and TRAIN modes. return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op, eval_metric_ops=eval_metric_ops) def rmse_evaluator(labels, predictions): """Metric for RMSE. Args: labels: Truth provided by the estimator when adding the metric predictions: Predicted values. Provided by the estimator silently Returns: metric_fn that can be used to add the metrics to an existing Estimator """ pred_values = predictions['predictions'] return {'rmse': tf.metrics.root_mean_squared_error(labels, pred_values)} def get_learning_rate(params): """Get learning rate given hyperparams. Args: params: hyperparameters Returns: learning_rate tensor if params.learning_rate_decay, else a constant. """ if params.learning_rate_decay: global_step = tf.train.get_global_step() learning_rate = tf.train.exponential_decay( learning_rate=params.learning_rate, global_step=global_step, decay_steps=params.checkpoint_steps, decay_rate=params.learning_decay_rate, staircase=True ) else: learning_rate = params.learning_rate return learning_rate def get_optimizer(params): """Get optimizer given hyperparams. Args: params: hyperparameters Returns: optimizer object Raises: ValueError: if params.optimizer is not supported. """ if params.optimizer == 'ProximalAdagrad': optimizer = tf.train.ProximalAdagradOptimizer( learning_rate=get_learning_rate(params), l1_regularization_strength=params.l1_regularization, l2_regularization_strength=params.l2_regularization ) elif params.optimizer == 'SGD': optimizer = tf.train.GradientDescentOptimizer(get_learning_rate(params)) elif params.optimizer == 'Adam': optimizer = tf.train.AdamOptimizer(learning_rate=get_learning_rate(params)) elif params.optimizer == 'RMSProp': optimizer = tf.train.RMSPropOptimizer( learning_rate=get_learning_rate(params)) else: raise ValueError('Invalid optimizer: %s' % params.optimizer) return optimizer def get_estimator(estimator_name, config, params, model_dir): """Return one of the TF-provided canned estimators defined by MODEL_TYPE. Args: estimator_name: estimator model type config: run config params: hyperparams model_dir: model directory Returns: Estimator object """ print('-- Running training with estimator {} --'.format(estimator_name)) if estimator_name not in CANNED_MODEL_TYPES: estimator = tf.estimator.Estimator(model_fn=model_fn, config=config, params=params, model_dir=model_dir) else: if estimator_name == CANNED_DEEP: estimator = tf.estimator.DNNRegressor( feature_columns=clvf.get_deep_features(), hidden_units=params.hidden_units, config=config, model_dir=model_dir, optimizer=lambda: get_optimizer(params), batch_norm=True, dropout=params.dropout) else: estimator = tf.estimator.LinearRegressor( feature_columns=clvf.get_wide_features(), config=config, model_dir=model_dir) # Add RMSE for metric for canned estimators estimator = tf.contrib.estimator.add_metrics(estimator, rmse_evaluator) return estimator ================================================ FILE: clv_mle/trainer/task.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Entry point for CMLE jobs for CLV.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import import sys import argparse import json import os import shutil import tensorflow as tf from .btyd import run_btyd from .context import CLVFeatures from .model import get_estimator, read_train, read_eval, read_test from .model import MODEL_TYPE, MODEL_TYPES, PROBABILISTIC_MODEL_TYPES # Training defaults # 100000 is the approximate size of our training set (to nearest 1000). #[START hyperparams] TRAIN_SIZE = 100000 NUM_EPOCHS = 70 BATCH_SIZE = 5 NUM_EVAL = 20 LEARNING_DECAY_RATE = 0.7 HIDDEN_UNITS = '128 64 32 16' LEARNING_RATE = 0.00135 L1_REGULARIZATION = 0.0216647 L2_REGULARIZATION = 0.0673949 DROPOUT = 0.899732 SHUFFLE_BUFFER_SIZE = 10000 #[END hyperparams] # TRAIN_SIZE = 100000 # NUM_EPOCHS = 70 # BATCH_SIZE = 20 # NUM_EVAL = 20 # HIDDEN_UNITS = '128 64 32 16' # LEARNING_RATE = 0.096505 # L1_REGULARIZATION = 0.0026019 # L2_REGULARIZATION = 0.0102146 # DROPOUT = 0.843251 # SHUFFLE_BUFFER_SIZE = 10000 def create_parser(): """Initialize command line parser using arparse. Returns: An argparse.ArgumentParser. """ parser = argparse.ArgumentParser() parser.add_argument('--model_type', help='Model type to train on', choices=MODEL_TYPES, default=MODEL_TYPE) parser.add_argument('--job-dir', type=str, required=True) parser.add_argument('--data-src', type=str, required=True) # The following parameters are required for BTYD. parser.add_argument('--predict_end', type=str, required=False, help='Predict end date YYYY-mm-dd') parser.add_argument('--threshold_date', type=str, required=False, help='Threshold date YYYY-mm-dd') # hyper params parser.add_argument('--hidden_units', help='List of hidden units per fully connected layer.', default=HIDDEN_UNITS, type=str) parser.add_argument('--learning_rate', help='Learning rate for the optimizer', default=LEARNING_RATE, type=float) parser.add_argument('--learning_rate_decay', type=str, help='Use learning rate decay [True|False]', default='True') parser.add_argument('--learning_decay_rate', help='Learning decay rate', type=float, default=LEARNING_DECAY_RATE) parser.add_argument('--train_size', help='(Approximate) size of training set', default=TRAIN_SIZE, type=int) parser.add_argument('--batch_size', help='Number of input records used per batch', default=BATCH_SIZE, type=int) parser.add_argument('--buffer_size', help='Size of the buffer for training shuffle.', default=SHUFFLE_BUFFER_SIZE, type=float) parser.add_argument('--train_set_size', help='Number of samples on the train dataset.', type=int) parser.add_argument('--l1_regularization', help='L1 Regularization (for ProximalAdagrad)', type=float, default=L1_REGULARIZATION) parser.add_argument('--l2_regularization', help='L2 Regularization (for ProximalAdagrad)', type=float, default=L2_REGULARIZATION) parser.add_argument('--dropout', help='Dropout probability, 0.0 = No dropout layer', type=float, default=DROPOUT) parser.add_argument('--hypertune', action='store_true', help='Perform hyperparam tuning', default=False) parser.add_argument('--optimizer', help='Optimizer: [Adam, ProximalAdagrad, SGD, RMSProp]', type=str, default='ProximalAdagrad') parser.add_argument('--num_epochs', help='Number of epochs', default=NUM_EPOCHS, type=int) parser.add_argument('--ignore_crosses', action='store_true', default=False, help='Whether to ignore crosses (linear model only).') parser.add_argument('--verbose-logging', action='store_true', default=False, help='Turn on debug logging') parser.add_argument('--labels', type=str, default='', help='Labels for job') parser.add_argument('--resume', action='store_true', default=False, help='Resume training on saved model.') return parser def csv_serving_input_fn(): """Defines how the model gets exported and the required prediction inputs. Required to have a saved_model.pdtxt file that can be used for prediction. Returns: ServingInputReceiver for exporting model. """ #[START csv_serving_fn] clvf = CLVFeatures(ignore_crosses=True, is_dnn=MODEL_TYPE not in PROBABILISTIC_MODEL_TYPES) used_headers = clvf.get_used_headers(with_key=True, with_target=False) default_values = clvf.get_defaults(used_headers) rows_string_tensor = tf.placeholder(dtype=tf.string, shape=[None], name='csv_rows') receiver_tensor = {'csv_rows': rows_string_tensor} row_columns = tf.expand_dims(rows_string_tensor, -1) columns = tf.decode_csv(row_columns, record_defaults=default_values) features = dict(zip(used_headers, columns)) return tf.estimator.export.ServingInputReceiver(features, receiver_tensor) #[END csv_serving_fn] def main(argv=None): """Run the CLV model.""" argv = sys.argv if argv is None else argv args = create_parser().parse_args(args=argv[1:]) # Set logging mode tf.logging.set_verbosity(tf.logging.INFO) # execute non-estimator models if args.model_type in PROBABILISTIC_MODEL_TYPES: run_btyd(args.model_type, args.data_src, args.threshold_date, args.predict_end) return if args.hypertune: # if tuning, join the trial number to the output path config = json.loads(os.environ.get('TF_CONFIG', '{}')) trial = config.get('task', {}).get('trial', '') model_dir = os.path.join(args.job_dir, trial) else: model_dir = args.job_dir print('Running training with model {}'.format(args.model_type)) # data path data_folder = '{}/'.format(args.data_src) # Calculate train steps and checkpoint steps based on approximate # training set size, batch size, and requested number of training # epochs. train_steps = (args.train_size/args.batch_size) * args.num_epochs checkpoint_steps = int((args.train_size/args.batch_size) * ( args.num_epochs/NUM_EVAL)) # create RunConfig config = tf.estimator.RunConfig( save_checkpoints_steps=checkpoint_steps ) hidden_units = [int(n) for n in args.hidden_units.split()] # Hyperparameters params = tf.contrib.training.HParams( num_epochs=args.num_epochs, train_steps=train_steps, batch_size=args.batch_size, hidden_units=hidden_units, learning_rate=args.learning_rate, ignore_crosses=args.ignore_crosses, buffer_size=args.buffer_size, learning_rate_decay=( args.learning_rate_decay == 'True'), learning_decay_rate=args.learning_decay_rate, l1_regularization=args.l1_regularization, l2_regularization=args.l2_regularization, optimizer=args.optimizer, dropout=( None if args.dropout == 0.0 else args.dropout), checkpoint_steps=checkpoint_steps) print(params) print('') print('Dataset Size:', args.train_size) print('Batch Size:', args.batch_size) print('Steps per Epoch:', args.train_size/args.batch_size) print('Total Train Steps:', train_steps) print('Required Evaluation Steps:', NUM_EVAL) print('Perform evaluation step after each', args.num_epochs/NUM_EVAL, 'epochs') print('Save Checkpoint After', checkpoint_steps, 'steps') print('**********************************************') # Creates the relevant estimator (canned or custom) estimator = None # get model estimator #[START choose_model] estimator = get_estimator(estimator_name=args.model_type, config=config, params=params, model_dir=model_dir) #[END choose_model] # Creates the training and eval specs by reading the relevant datasets # Note that TrainSpec needs max_steps otherwise it runs forever. train_spec = tf.estimator.TrainSpec( input_fn=lambda: read_train(data_folder, params), max_steps=train_steps) eval_spec = tf.estimator.EvalSpec( input_fn=lambda: read_eval(data_folder, params), exporters=[ tf.estimator.LatestExporter( name='estimate', serving_input_receiver_fn=csv_serving_input_fn, exports_to_keep=1, as_text=True ) ], steps=1000, throttle_secs=1, start_delay_secs=1 ) if not args.resume: print('Removing previous trained model...') shutil.rmtree(model_dir, ignore_errors=True) else: print('Resuming training...') # Runs the training and evaluation using the chosen estimator. # Saves model data into export/estimate/1234567890/... tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec) # Evaluate the test set for final metrics estimator.evaluate(lambda: read_test(data_folder, params), name="Test Set") if __name__ == '__main__': main() ================================================ FILE: linear.py ================================================ from tensorflow.python.lib.io import file_io import pandas from pandas.compat import StringIO import json import math import numpy as np from sklearn.linear_model import LinearRegression c_names =['customer_id', 'monetary_dnn', 'monetary_btyd', 'frequency_dnn', 'frequency_btyd', 'recency', 'T', 'time_between', 'avg_basket_value', 'avg_basket_size', 'cnt_returns', 'has_returned', 'frequency_btyd_clipped', 'monetary_btyd_clipped', 'target_monetary_clipped', 'target_monetary'] train_df = file_io.FileIO( 'data/train.csv', mode='r').read() train_df = pandas.read_csv( StringIO(train_df), header = None, names = c_names, delimiter=',', na_filter=True) test_df = file_io.FileIO( 'data/eval.csv', mode='r').read() test_df = pandas.read_csv( StringIO(test_df), header = None, names = c_names, delimiter=',', na_filter=True) reg = LinearRegression().fit( train_df.values[:, [1,3,5,6,7,8,9,10,11]], train_df.values[:, -1]) error = 0 i = 0 for p in reg.predict(test_df.values[:, [1,3,5,6,7,8,9,10,11]]): error = error + math.pow(p - test_df.values[i, -1], 2) i = i +1 print "RMSE = ", math.sqrt(error/test_df.values.shape[0]) ================================================ FILE: notebooks/Exploration.ipynb ================================================ { "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "Exploration.ipynb", "version": "0.3.2", "provenance": [] }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "cells": [ { "metadata": { "id": "0jifCBTzSnwO", "colab_type": "code", "outputId": "a17521de-b813-4125-b238-5b656578db25", "colab": { "base_uri": "https://localhost:8080/", "height": 17 } }, "cell_type": "code", "source": [ "# %%javascript\n", "# IPython.OutputArea.prototype._should_scroll = function(lines) {\n", "# return false;\n", "# }" ], "execution_count": 0, "outputs": [ { "output_type": "display_data", "data": { "application/javascript": [ "IPython.OutputArea.prototype._should_scroll = function(lines) {\n", " return false;\n", "}" ], "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "GqvIibXtSnwT", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from mpl_toolkits.mplot3d import Axes3D\n", "import seaborn as sns\n", "from google.cloud import bigquery\n", "import calendar\n", "import time\n", "import os" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "l2EbQxk8SnwV", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "PROJECT_ID = '[YOUR-PROJECT-ID]'\n", "DATASET = '[YOUR-DATASET]'\n", "TABLE = 'data_source'\n", "\n", "from google.colab import auth\n", "auth.authenticate_user()" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "IRZwAmVtWAW4", "colab_type": "text" }, "cell_type": "markdown", "source": [ "### Look at source data" ] }, { "metadata": { "id": "sO2ttiq6V_6M", "colab_type": "code", "outputId": "692906c7-e8c2-4303-a253-bda403fb2022", "colab": { "base_uri": "https://localhost:8080/", "height": 416 } }, "cell_type": "code", "source": [ "qry_original = \"\"\"\n", "SELECT\n", " DATETIME_DIFF(\n", " PARSE_DATETIME(\"%m/%d/%y %H:%M\", InvoiceDate), mm.mind, DAY) AS day_number,\n", " *\n", "FROM\n", " `{p}.{d}.{t}`,\n", " (\n", " SELECT\n", " MIN(PARSE_DATETIME(\"%m/%d/%y %H:%M\", InvoiceDate)) mind,\n", " MAX(PARSE_DATETIME(\"%m/%d/%y %H:%M\", InvoiceDate)) maxd\n", " FROM\n", " `{p}.{d}.{t}`) mm {w}\n", "\"\"\"\n", "\n", "where_sample = \"\"\"\n", "WHERE\n", " -- 1 in 50 so we get about 500000 records out of 10000\n", " MOD(ABS(FARM_FINGERPRINT( \n", " CONCAT(\n", " CAST(InvoiceNo AS STRING),\n", " CAST(Quantity AS STRING),\n", " CAST(InvoiceDate AS STRING),\n", " CAST(UnitPrice AS STRING),\n", " CAST(CustomerID AS STRING)\n", " ) )), 50) = 1\n", "\"\"\"\n", "\n", "df_original = pd.io.gbq.read_gbq(qry_original.format(p=PROJECT_ID, d=DATASET, t=TABLE, w=where_sample), \n", " project_id=PROJECT_ID, \n", " dialect='standard')\n", "\n", "print(df_original.head(2))\n", "df_original.describe()" ], "execution_count": 88, "outputs": [ { "output_type": "stream", "text": [ " day_number InvoiceNo StockCode Description Quantity InvoiceDate \\\n", "0 90 545332 M Manual 1 03/01/11 03:52 \n", "1 337 574252 M Manual 1 11/03/11 01:24 \n", "\n", " UnitPrice CustomerID Country mind maxd \n", "0 183.75 12352 Norway 2010-12-01 01:04:00 2011-12-09 12:50:00 \n", "1 0.00 12437 France 2010-12-01 01:04:00 2011-12-09 12:50:00 \n" ], "name": "stdout" }, { "output_type": "execute_result", "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
day_numberQuantityUnitPrice
count7974.0000007974.0000007974.000000
mean221.63029812.2440433.088569
std112.50638543.8053427.036266
min0.000000-144.0000000.000000
25%130.0000002.0000001.250000
50%240.0000005.0000001.950000
75%323.00000012.0000003.750000
max373.0000001500.000000295.000000
\n", "
" ], "text/plain": [ " day_number Quantity UnitPrice\n", "count 7974.000000 7974.000000 7974.000000\n", "mean 221.630298 12.244043 3.088569\n", "std 112.506385 43.805342 7.036266\n", "min 0.000000 -144.000000 0.000000\n", "25% 130.000000 2.000000 1.250000\n", "50% 240.000000 5.000000 1.950000\n", "75% 323.000000 12.000000 3.750000\n", "max 373.000000 1500.000000 295.000000" ] }, "metadata": { "tags": [] }, "execution_count": 88 } ] }, { "metadata": { "id": "nPxHxgGoaUH5", "colab_type": "code", "outputId": "ef6a3b05-e26f-4f21-bf81-1b23c0934716", "colab": { "base_uri": "https://localhost:8080/", "height": 609 } }, "cell_type": "code", "source": [ "fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(20, 10))\n", "\n", "# distplot not as easy to clip\n", "# sns.distplot( df_original['Quantity'], ax=axs[0], label='quantity')\n", "# sns.distplot(df_original['UnitPrice'], ax=axs[1], label='price')\n", "\n", "sns.kdeplot(df_original['Quantity'], ax=axs[0, 0], label='quantity', clip=(-50, 100))\n", "sns.kdeplot(df_original['UnitPrice'], ax=axs[0, 1], label='price', clip=(-50, 5000))\n", "sns.kdeplot(df_original['day_number'], ax=axs[1, 0], label='days')" ], "execution_count": 89, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "" ] }, "metadata": { "tags": [] }, "execution_count": 89 }, { "output_type": "display_data", "data": { "image/png": "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\nxx9/nNTUVJqbm0/5mFOpr28fyHs6LW6365w+/3ChPEQoDxHKQ4TyEKE8RCgPEecyDypOnb7zoQ/W\nQ39bZ045GxjlbWCUtzOnnA2M8jYwnzRvA+1/DWge0Pz581m7di0Au3btIisrK9oRKSgowOPxUFVV\nRTAY5K233mL+/Pkf+xgRERER6Z/6YCIiIhJLA5rONmfOHKZOncqyZcswDIOHHnqIl156CZfLxdVX\nX83DDz/M3XffDcDSpUspKiqiqKioz2NERERE5PSpDyYiIiKxNKDpbINF09nOPeUhQnmIUB4ilIcI\n5SFCeYjQdLaRRdPZhiblbGCUt4FR3s6ccjYwytvADKvpbCIiIiIiIiIiMrKoiCQiIiIiIiIiIv1S\nEUlERERERERERPqlIpKIiIiIiIiIiPRLRSQREREREREREemXikgiIiIiIiIiItIvwzRNM9ZBiIiI\niIiIiIjI0KaRSCIiIiIiIiIi0i8VkUREREREREREpF8qIomIiIiIiIiISL9URBIRERERERERkX6p\niCQiIiIiIiIiIv1SEUlERERERERERPpli3UAsdDQ0MB1113H448/zsUXX8zevXt5+OGHAZg4cSL/\n9m//FtsAz7FgMMgPfvADKioqCIVC3HvvvVx44YUjLg89HnnkEXbs2IFhGDzwwAPMmDEj1iENmp/9\n7Gd88MEHBINBvvGNbzB9+nTuvfdeQqEQbreb//iP/8DhcMQ6zEHh9/v51Kc+xR133MG8efNGZB5e\nffVVfvOb32Cz2bjrrruYOHHiiMtDR0cH9913H62trXR1dXHnnXfidrtHTNu4f/9+7rjjDr785S+z\nfPlyjh49etJ94NVXX+UPf/gDFouFW265hZtvvjnWocswMpKPu2diy5YtfOc732H8+PEATJgwga99\n7Wsjrl0+XWq/BubEvN1///3s2rWL1NRUAG6//XYWLVqkvB3ndPvPyllvJ+Zt/fr12tf64fP5uP/+\n+2lsbCQQCHDHHXcwadKk2O9v5gj0/e9/3/zc5z5nbt682TRN01y+fLm5Y8cO0zRN83vf+565YcOG\nWIZ3zq1Zs8Z86KGHTNM0zf3795uf//znTdMceXkwTdPcsmWL+U//9E+maZpmaWmpecstt8Q4osGz\nadMm82tf+5ppmqbZ1NRkLly40Lz//vvN119/3TRN0/zFL35hPvfcc7EMcVA9+uij5o033mi++OKL\n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\nv9/PD37wA44ePYrf7+db3/oW06ZNO+lxYDDzNmKLSCIiIiIiIiIicvpG1HQ2EREREREREREZGBWR\nRERERERERESkXyoiiYiIiIiIiIhIv1REEhERERERERGRfqmIJCIiIiIiIiIi/VIRSURERERERERE\n+qUikoiIiIiIiIiI9EtFJBERERERERER6ZeKSCIiIiIiIiIi0i8VkUREREREREREpF8qIomIiIiI\niIiISL9URBIRERERERERkX6piCQiIiIiIiIiIv1SEUlERERERERERPqlIpKIiIiIiIiIiPRLRSQR\nEREREREREemXikgiIiIiIiIiItIvFZFERERERERERKRfKiKJiIiIiIiIiEi/VEQSEREREREREZF+\nqYgkIiIiIiIiIiL9UhFJRERERERERET6pSKSiIiIiIiIiIj0S0UkERERERERERHpl4pIIiIiIiIi\nIiLSLxWRRERERERERESkXyoiiYiIiIiIiIhIv1REEhERERERERGRfqmIJCIiIiIiIiIi/VIRSURE\nRERERERE+qUikoiIiMh5YP/+/SxevJhnn322z7b33nuPm266iVtvvZVf/epXMYhOREREzgcqIomI\niIgMc16vlx/+8IfMmzfvpNt/9KMf8dhjj/H888/z7rvvUlpaOsgRioiIyPlARSQRERGRYc7hcPD0\n00+TlZXVZ1tlZSUpKSnk5uZisVhYuHAhmzZtikGUIiIiMtypiCQiIiIyzNlsNuLi4k66rb6+nvT0\n9Ojv6enp1NfXD1ZoIiIich6xxTqAj1Nf3x7rEIattLQEmpu9sQ5jxFHeY0N5jw3lffCdrzl3u12x\nDkFOYJomhmHEOgwREREZYoZ0EUkGzmazxjqEEUl5jw3lPTaU98GnnMtAZGVl0dDQEP29trb2pNPe\njmcYhk7mDUFut0ufyxCjz2Ro0ucy9OgzGXoGehJP09lEREREzmMFBQV4PB6qqqoIBoO89dZbzJ8/\nP9ZhiYiIyDCkkUgiIiIiw1xJSQk//elPqa6uxmazsXbtWq688koKCgq4+uqrefjhh7n77rsBWLp0\nKUVFRTGOWERERIYjFZFEREREhrlp06bxzDPPnHL73LlzWbVq1SBGJCIiIucjTWcTEREREREREZF+\nqYgkIiIiIiIiIiL9UhFJRERERERERET6pSKSiIiIiIiIiIj0S0UkERER6cPr9XLTTZ+OdRgiIiIi\nMoTo6mwiIjJshcJh9hxuZvPuWvaUN5OZEseYHBeFOS4mjU4jPTku1iGKiIiIiJw3VEQSEZFhpysY\n5pV3DvH2ziO0e7sASIq3U1rdyoGqVgBsVgv/9OkpXDgpK5ahDisdHR5+8IN76ezsZMaMWQC88cZf\nWbNmFVarhcLCYu677wd8/etf4uGHf0x+fgF1dbXcf//dPPLIz/nhD/8/LBYLoVCIf/3XH5KTkxvj\ndyQiIiIiZ5OKSCIiMqw0tPj49cslHK5pJynezpVz8rlkag7Fecl0doWprPNQWt3KK+8e4v++XMKt\nV43nmrmjYh32GVu9vpR/7K3rdZvVahAKmQN+zrmTsrjlynGn3L527V8ZO7aYu+66mzfffIN169bi\n8/n4xS8ew+VyceedX6esrJRrr13Km2++wYoVX+WddzayePESNmxYx9y5F/PlL3+Nffv20tDQoCKS\niIiIyHlGRSQRERk2dpY18PR/7abDH2T+9ByWXzMRp90a3e50WBlXkMK4ghQmj0njl3/awQtvHqCx\n1c+tV43DYhgxjH7oO3z4ILNmXQDA7NmRn8nJyfzLv9wNQHn5IVpbW1i8eAnf+963WbHiq7z33tvc\nd9+DtLe38cAD36e9vZ0rrriKadNmxOx9iIiIiMi5oSKSiIgMC2vfr2DV+lJsVgtfvm4Sl8/IxfiY\notCYHBc/WHEBv/zTTv57ayWmaXLb1RMGMeJP5pYrx/UZNeR2u6ivbz9nr2maYLFEchoOm3R1dfHo\noz/j97//TzIyMrn33v8JQEpKKllZWezZs4tw2MTtzsLtzuL3v3+e99/fzBNPPM7119/Addd96pzF\nKiIiIiKDT1dnExGRIW/Dh9WsWl9KmsvJD754AQtm5n1sAalHZko8DyyfQ25GAm9+UMWho22DEO3w\nNXr0GPbu3QPAtm1b8Xq9WK1WMjIyqa2tYe/ePQSDQQCWLFnKo4/+lCuuuAqAdevWcvBgKQsWLOLr\nX7+Dffv2xOx9iIiIiMi5oSKSiIgMaZt31/DM2n24Euzcs2wWY3JcZ/T4hDg7X7xmIibw7Bv7CJsD\nX1PofHfttdeza9dHfOc7/0xlZTmpqanMnXsxX/vaCn73u6e57bYv8n/+z6MEg0Hmz19AVVUVixZF\nikijRo3hf/2vn3HXXd/kd797is9+9qYYvxsREREROds0nU1ERIas7Qca+M1/7SHOaePuW2eRm5E4\noOeZNCaNS6Zks3l3LW/vOMLCWflnOdLzg8vl4rHHnoz+fvvt3+hzn2XLlgORkUrz51+OyxUp6k2c\nOImnn/7j4AQqIiIiIjGhkUgiIjIklVW38uuXS7DZDL5780xGZ5/ZCKQT3XzFOOIcVtZsKKPd23mW\nohyZVq58kieeeJxvfOPOWIciIiIiIoNIRSQRERlyWj0BfvXnjwiFw9z5uemMK0j5xM+Z5nLy2cuK\n6PAHefHvB89ClCPX7bd/g6ee+j1ud1asQxERERGRQaQikoiIDCnBUJhfv1xCi6eTmxeNY/rYjLP2\n3FdeUEC+O5G3dxyhovbcXeVMREREROR8pCKSiIgMKS+8eYADVa1cNDmLJReNOqvPbbNauHlRMSaw\nflv1WX1uEREREZHznYpIIiIyZLyz8yjrt1VT4E7kK9dNxjCMs/4a04oyyEiOY8vuWnyB4Fl/fhER\nERGR85WKSCIiMiRU1Xt45o19JDhtfOvG6Tgd1nPyOhaLwYKZuQS6QmzZXXtOXkNERERE5HykIpKI\niMRcZ1eIJ1/ZRVcwzO3XTyYrLeGcvt5lM/KwGAYbtldjmuY5fS0RERERkfOFikgiIhJzq9aXUt3Q\nwZVz8pk9wX3OXy/N5WTmuAwqaj0crtEC2yIiIiIip0NFJBERiakP9tXz1oeRdZBuuWLcoL3uotn5\nAPx9uxbYFhERERE5HSoiiYhIzDS1+fn9X/fgsFn4xmem4bCfm3WQTmZqYXr3Att1WmBbREREROQ0\nqIgkIiIxETZNfvOX3XT4gyxbPJ78zMRBfX2LxWDBrDwCXSE2a4FtEREREZF+qYgkIiIx8ebWKvZW\ntDBrXCYLZ+bFJIbLpudiMQw2bj8Sk9cXERERERlOVEQSEZFBd6ShgzV/L8OVYOfL103CMIyYxJHm\ncjK1KJ3y2nYaWn0xiUFEREREZLhQEUlERAZVMBTm6b/spisY5kvXTiI50RHTeGaNywBgR2ljTOMQ\nERERERnqVEQSEZFB9Zf3DlNe08786TnMmeCOdTjMHJcJwI7ShhhHIiIiIiIytKmIJCIig+ZwTRt/\nea+cjGQnX7hqQqzDASA9OY7RWUnsrWjWVdpERERERD6GikgiIjIogqEwv31tL2HT5CtLJ5MQZ4t1\nSFEzxmUSDJnsPtwU61BERERERIas0yoiPfLII9x6660sW7aMnTt39tr23nvvcdNNN3Hrrbfyq1/9\n6rQe8/bbbzNx4sSzEL6IiAwXr28up6rew4KZuUwpTI91OL3M6p7Stl1T2kRERERETqnf08Dvv/8+\n5eXlrFq1irKyMh544AFWrVoV3f6jH/2IlStXkp2dzfLly1myZAlNTU2nfEwgEOCpp57C7Y79Ohgi\nIjI4qus9/Ne7h0lNcnDLFeNjHU4fhbkuUhId7CxrJGyaWGJ0tTgRERERkaGs35FImzZtYvHixQAU\nFxfT2tqKx+MBoLKykpSUFHJzc7FYLCxcuJBNmzZ97GOeeOIJbrvtNhyO2F6NR0REBkc4bPK7v+4l\nFDZZsWTSkJrG1sNiGMwozqDd28WhI22xDkdEREREZEjqt4jU0NBAWlpa9Pf09HTq6+sBqK+vJz09\nvc+2Uz3m0KFD7N27l+uuu+5svgcRERnC1m2t5OCRNi6eks2s8ZmxDueUZmpKm4iIiIjIxzrj08Gm\naZ7xi/Q85ic/+QkPPvjgaT8uLS0Bm816xq8nEW63K9YhjEjKe2wo77HRX96PNnTw0tuHSE508O1b\nZ5OS5BykyM7cwuR4nnx1F7sON/PNm4bu/qR9XURERERipd8iUlZWFg0Nx87K1tXVRdczOnFbbW0t\nWVlZ2O32Po9xOBwcPHiQe+65J3rb8uXLefbZZ0/52s3N3jN/RwJEvmTU17fHOowRR3mPDeU9NvrL\nu2maPPr8h3R2hfjKdZPo9HVS7+scxAjP3KTRaXx0sJE9pXVkpsTHOpw+ztd9XYUxERERkeGh3+ls\n8+fPZ+3atQDs2rWLrKwskpKSACgoKMDj8VBVVUUwGOStt95i/vz5J31Mfn4+69atY/Xq1axevZqs\nrKyPLSCJiMjw9vcdR9hb0cKscZlcNDkr1uGcllnjMgDYUdoY40hERERERIaefkcizZkzh6lTp7Js\n2TIMw+Chhx7ipZdewuVycfXVV/Pwww9z9913A7B06VKKioooKirq8xgRERk5mtr8rF5fSrzTxheX\nTMQYJlc7mz42UkTaU97MVRcUxDgaEREREZGh5bTWROqZgtZj0qRJ0X/PnTuXVatW9fuYE61fv/50\nXlpERIYZ0zT549p9+DtDfPm6SaS5hu46SCfKTI0nI9nJ/soWwqaJZZgUv0REREREBkO/09lERETO\nxJY9tewsa2RKYRqXz8iNdThnbMKoVDy+Lo42dMQ6FBERERGRIUVFJBEROWu8/i5eeLMUu83Cimsn\nDZtpbMebMCoVgP2VLTGOROTMPPLII9x6660sW7aMnTt39tr23HPPceutt/KFL3yBH//4xzGKUERE\nRIY7FZFEROSseXHjQdo6OrlhfiFZqUPv6manY+LoNAD2qYgkw8j7779PeXk5q1at4sc//nGvQpHH\n42HlypU899xzPP/885SVlbF9+/YYRisiIiLDlYpIIiJyVhw80saGbdXkZiSw5KLRsQ5nwLLT4klO\ndLCvsgXTNGMdjshp2bRpE4sXLwaguLiY1tZWPB4PAHa7HbvdjtfrJRgM4vP5SElJiWW4IiIiMkyp\niCQiIp9YKBzmj2v3YgIrlkzEZh2+hxfDMJgwKpVWTyd1Lb5YhyNyWhoaGkhLS4v+np6eTn19PQBO\np5M777yTxYsXc8UVVzBz5kyKiopiFaqIiIgMY6d1dTYREZGPs35bNRW1Hi6dlhOdDjacTRyVyta9\ndeyvaCE7LSHW4YicseNH0Xk8Hp588kn+9re/kZSUxJe+9CX27t3b62q7J+N2u851mDIA+lyGHn0m\nQ5M+l6FHn8n5QUUkERH5RFo8Af688SCJcTZuuWJcrMM5KyZ2L669r7KFy2fmxTgakf5lZWXR0NAQ\n/b2urg632w1AWVkZo0aNIj09HYALL7yQkpKSfotI9fXt5y5gGRC326XPZYjRZzI06XMZevSZDD0D\nLeoN3/kGIiIyJKzZUIa/M8SNC4tJTnTEOpyzIs+dSGKcTVdok2Fj/vz5rF27FoBdu3aRlZVFUlIS\nAPn5+ZSVleH3+wEoKSmhsLAwVqGKiIjIMKaRSCIiMmClVa28V1LD6KwkFp5HI3Ys3esifXiggcZW\nPxkpcbEOSeRjzZkzh6lTp7Js2TIMw+Chhx7ipZdewuVycfXVV3P77bezYsUKrFYrs2fP5sILL4x1\nyCIiIjIMqYgkIiIDEgqbPPff+wG47eoJWCxGjCM6u3qKSPurWpiXkhPrcET6dc899/T6/fjpasuW\nLWPZsmWDHZKIiIicZzSdTUREBuS/t5RTXtvOJVOzmdC9htD5pOc97avQlDYREREREVARSUREBqDD\n38UfX9+D027l5kXnx2LaJxqdnUScw6p1kUREREREuqmIJCIiZ+zltw/R7u3k0/MLSXM5Yx3OOWG1\nWBhXkEJNk5fWjs5YhyMiIiIiEnMqIomIyBk52tjBW9uqyc1M5OoLR8U6nHNqfH4KAAerW2MciYiI\niIhI7KmIJCIiZ+RPb5URNk2+8qmp2G3n92FkbF53EeloW4wjERERERGJvfO79y8iImfV7sNNbC9t\nYOKoVC6Zdv5fsaww1wXAYRWRRERERERURBIRkdMTDpusWl+KASy7ajyGYcQ6pHMuMc5OdnoCh462\nEzbNWIesj+X9AAAgAElEQVQjIiIiIhJTKiKJiMhpefejo1TWebh0Wg5jclyxDmfQjM114Q0EqWv2\nxToUEREREZGYUhFJRET65e8M8tLGgzhsFm5cWBzrcAZVUW4yAIeOaEqbiIiIiIxsKiKJiEi/3ni/\nktaOTq69eDRpLmeswxlURXmRIpIW1xYRERGRkU5FJBER+Vht3k7+9n4FrgQ71148OtbhDLrRWUlY\nLQaHVEQSERERkRFORSQREflYr28qx98Z4tOXFhLnsMU6nEFnt1kZlZVERW07wVA41uGIiIiIiMSM\nikgiInJKDa0+1m+rIjMljoWz8mMdTswU5SUTDJlU1nliHYqIiIiISMyoiCQiIqf0yjuHCIZMPnt5\nEXbbyD1kjO1ZXFtT2kRERERkBBu53whERORjVdd7eK+khnx3IpdMyYl1ODGlK7SJiIiIiKiIJCIi\np/DSxoOYJnx+QTEWixHrcGIqJyOBOIdVV2gTERERkRFNRSQREemjrLqVDw80MK4ghZnjMmIdTsxZ\nDIOi3GRqGr14/cFYhyMiIiIiEhMqIomISC+mabJmQxkANy0sxjBG9iikHkW5yZhAeY1GI4mIiIjI\nyKQikoiI9LLrUBP7KluYUZzBhFGpsQ5nyOhZF0lT2kRERERkpFIRSUREosKmyZq/l2EAn19YHOtw\nhpSxeT1XaGuPcSQiIiIiIrGhIpKIiET9Y08dFbUeLp6azaispFiHM6SkuZykJDk4pJFIIiIiIjJC\nqYgkIiIABENh/rzxIFaLwWcvHxvrcIakMdkumtsDtHs7Yx2KiIiIiMigUxFJREQAeHvnUepafCyc\nlUdWanyswxmSRmdHRmdV1HpiHImIiIiIyOBTEUlERAh0hXj1nUM47BY+fWlhrMMZssZkuwCoqNW6\nSCIiIiIy8qiIJCIirNtaSWtHJ9fMHUVKkjPW4QxZo7uLSOUqIomIiIjICKQikojICNfh7+KvmytI\njLNx7UVjYh3OkJaZEkeC00a5prOJiIiIyAikIpKIyAj3+uZyvIEg188rJCHOFutwhjTD+H/t3Xt8\nVPW97//33HOZSciEmQRIAiGAaBQxgi1GQTGgor15S7y1pxd7fGztbeu2yu4pdD+EWoue7lLb3fZn\n+9u1VaM2ddvdVrxBtRAFFLlEQa4hCSGZSUKSyX1m1vkjkC0KBJBkzeX1fDzyICtr1uQ93++QWfOZ\n7/e7LCrIcau5tVu9/WGz4wAAAACjiiISACSxts4+vbKxXlkel+aXTDA7TlwoyPHIkFTXzGgkAAAA\nJBeKSACQxF5Yu1cD4ag+d0mhnA6b2XHiwpHFtWsPsi4SAAAAkgtFJABIUgdbu/XG5kbletNUel6u\n2XHiRkGOW5K0n3WRAAAAkGROavGL5cuXa/PmzbJYLFq8eLFmzJgxtG/dunV69NFHZbPZNHfuXN11\n113HPWbTpk16+OGHZbfb5XQ69eMf/1her3dkHhkA4IT+9PoeRQ1D182dLJuVzxROVm52mpx2q/Zz\nhTYAAAAkmWHfNaxfv161tbWqrKzUsmXLtGzZsqP2P/jgg1q5cqWeeuoprV27Vrt27TruMb/97W/1\n8MMP64knntAFF1ygZ555ZmQeFQDghPYd7NCG7c2alOvRhWf5zI4TV2xWq/L8bjUEuzQQjpodBwAA\nABg1wxaRqqurVVZWJkkqKipSe3u7QqHBIfx1dXXKzMzUuHHjZLVaNW/ePFVXVx/3mJ/+9KfKz8+X\nYRhqampSbi7TJwDADH/8+x5J0g2XFclisZicJv4U5HgUiRo6EOwyOwoAAAAwaoYtIgWDQWVlZQ1t\ne71eBQIBSVIgEDhqOtqRfSc65vXXX9dVV12lYDCoz372s2fsgQAATs77+1pVs7dV50zK0jmTmFJ8\nOo6si1TLlDYAAAAkkZNaE+nDDMM45V/y4WPmzp2rSy+9VCtWrNCvfvUr3Xnnncc9LisrTXY7Vws6\nXT6fx+wISYl2NwftfnIMw9B/PfmOJOlrnz/vE7dbsrb7zOk5+t2LOxTo6Bv1NkjWNgcAAID5hi0i\n+f1+BYPBoe3m5mb5fL5j7mtqapLf75fD4TjmMS+//LIWLFggi8WiK6+8UitXrjzh725r6z7lB4RB\nPp9HgQCfkI822t0ctPvJe3tHQB/sP6RZ0/0ak2L/RO2WzO2ebrfIarFo+76WUW2DRG1zCmMAAADx\nYdjpbKWlpVq1apUkqaamRn6/X2734DD+vLw8hUIh1dfXKxwOa/Xq1SotLT3uMStXrtT7778vSdq8\nebMKCwtH6nEBAD4iHImq6vXdslos+sKl/P39JBx2m8aPTVNdc0jR6KmP0AUAAADi0bAjkUpKSlRc\nXKyKigpZLBYtWbJEVVVV8ng8WrBggZYuXap77rlHkrRo0SIVFhaqsLDwY8dI0rJly/SDH/xANptN\nKSkpevjhh0f20QEAhry++YAaW7p12czxGpedbnacuDcxx6P6QJea2rppTwAAACSFk1oT6d577z1q\ne/r06UPfz549W5WVlcMeI0nnnXeenn766VPNCAD4hLp7w3r+jb1yOW363KWTzY6TEApyPFq77aBq\nD3ZSRAIAAEBSGHY6GwAg/v3trVqFega06NMTlZnuNDtOQjhyhbb9zSGTkwAAAACjgyISACS4lvZe\nvbShTlkelxbOzjc7TsLI9w8WkeooIgEAACBJnNR0NgBA/Kp6fbcGwlFdN3eyXA6b2XESRlqKQ9kZ\nKRSREDOWL1+uzZs3y2KxaPHixZoxY8bQvsbGRv3zP/+zBgYGdM455+jf/u3fTEwKAADiFSORACCB\n7W3sUHVNkwpy3Jpzbq7ZcRJOvt+tjq5+tYf6zI6CJLd+/XrV1taqsrJSy5Yt07Jly47a/9BDD+kr\nX/mKnnvuOdlsNh04cMCkpAAAIJ5RRAKABBU1DD358geSpPL5U2W1WExOlHiOrIvEaCSYrbq6WmVl\nZZKkoqIitbe3KxQafF5Go1G9/fbbmj9/viRpyZIlGj9+vGlZAQBA/KKIBAAJau3WRu0+0KFZ0/06\ne2KW2XESEusiIVYEg0FlZf3P/3Ov16tAICBJam1tVXp6un74wx/q5ptv1iOPPGJWTAAAEOdYEwkA\nElBX74CeXb1bLodNFfOnmB0nYVFEQqwyDOOo75uamvTFL35REyZM0Ne//nWtWbNGl1122Qnvw+fz\njHBKnA76JfbQJ7GJfok99ElioIgEAAnoT6/vUahnQDdcViRvRorZcRLW2DGpSnHatJ8iEkzm9/sV\nDAaHtpubm+Xz+SRJWVlZGj9+vAoKCiRJc+bM0c6dO4ctIgUCnSOWF6fH5/PQLzGGPolN9EvsoU9i\nz+kW9ZjOBgAJpvZgp1ZvalCuN00LZ+ebHSehWS0W5fndOtjSrYFwxOw4SGKlpaVatWqVJKmmpkZ+\nv19u9+BIObvdrvz8fO3bt29of2FhoVlRAQBAHGMkEgAkkKhh6Pcv75BhSLcumCa7jc8KRlq+361d\n9e1qCHZpUm6G2XGQpEpKSlRcXKyKigpZLBYtWbJEVVVV8ng8WrBggRYvXqz7779fhmFo2rRpQ4ts\nAwAAnAqKSACQQFa/06DdDR2adZZPxYVes+MkhaF1kZpCFJFgqnvvvfeo7enTpw99P3HiRD311FOj\nHQkAACQYPqIGgATR3NatZ9fsUnqKXbcumGZ2nKRR4B+cT866SAAAAEh0FJEAIAFEDUO/+et29Q9E\ndevCacp0u8yOlDQm+NJlsXCFNgAAACQ+ikgAkABee7teH9QdUsk0nz51do7ZcZKKy2FTTlaa6ppD\nR11WHQAAAEg0FJEAIM41t3Xrub/vVnqKXbcvnCaLxWJ2pKST73erpy+slvZes6MAAAAAI4YiEgDE\nsXAkql//+T2msZmsIOfw4tpMaQMAAEACo4gEAHHsmdd2afeBDn3qnBymsZlo6AptFJEAAACQwCgi\nAUCcWv9+k155u17jx6brS1edxTQ2E+UfvkIbRSQAAAAkMopIABCHDgS79Nu/bpfLadNdXzhXKU67\n2ZGS2hi3U+5Uh/Y3d5odBQAAABgxFJEAIM709IX12J+2qm8goq8sOlvjstPNjpT0LBaL8v1uBQ71\nqqcvbHYcAAAAYERQRAKAODIQjupnVVvV2NKtsll5mj3db3YkHHZkXaT6AFPaAAAAkJgoIgFAnIhE\no/rlCzV6v7ZNF0wdq/L5U8yOhA85UkTa30QRCQAAAImJIhIAxIGoYej//9t2vfNBQGdPzNKdnyuW\nzcqf8FhSkMPi2gAAAEhsvAMBgBgXNQw9/cpOrd16UIXjPLr7uvPksNvMjoWPGJedJpvVQhEJAAAA\nCYvL+QBADOsfiOj/+8v72ri9WePHpus7N81Uqos/3bHIbrNq/Nh0NQRCikYNWa0WsyMBAAAAZxQj\nkQAgRrWH+vSjJzdp4/ZmTcvL1HdvuUDuVIfZsXAC+X63+sNRNbV1mx0FAAAAOOP4OBsAYtC+gx16\nrGqrWjr6dPG5ufrSVdPlsFP3j3UFfrfWaXBdpHHZ6WbHAQAAAM4oikgAEEP6+iP6r3/s1Usb6hQ1\nDF03d7KumTNRFgtTo+LBkSu01TWHdNHZOSanAQAAAM4sikgARt1AOKrO7n719EfUPxBRX39EfQP/\n89U/EFU0akgWyXq4eGK1SLJYZJFks1qU6rIrxWlTitOurrChnq5epTgHf2a3xeeIna17WvTEqh0K\ntvfKNyZFX7xquoonec2OhVOQzxXaAAAAkMAoIgE446KGocChHtU3h9R8qEeBth41tfWorbNPHV39\n6u4Lj+jvd6c6lJnuVEa6U5lupzLTncpMdynT7ZTX45I3I0VZHldMFJsi0aje3hHQSxvqtOdAh6wW\nixZ9eqI+UzpJLgdXYIs37lSHsjwu7W/qNDsKAAAAcMZRRALwifX0hfVB3SFt39+mfY2d2t/cqZ6+\nyMdu5051KCvDpYlpHmWmO5XissvlsMrlsMnlsMk59K9VNqtFhjF4XPTwN1HDkAwpHDHU2x9Wb39E\nPf1hWaxWtbX3qrc/rJ6+sNq7+nUo1KeGYNdxM1ukwaJSRoq8GSnKznAd/nfwy5vhkjvVMSLTyKJR\nQ3sPdmjr7hb9Y2ujWjv6ZJE0c8pYff7SQhUcHs2C+JTvd2vL7hZ1dvfLk+Y0Ow4AAABwxlBEAnBa\nGoJdent7s7bubdHeA51DhR6LpNzsNJ1f5FG+360cb5r8Y1LlG5Mql3NkRtb4fB4FAh8f+TEQjqi9\nq1/tXf3qCPXrUFe/Wjt61drRp9aOXrV09Kr2YKf2HOg45v067VZlfaTA5M1wKTsjRRnpTqWnOORO\ntcthP/bjMgxDvf0RBdt71dTaraa2bu1vCum9fa3q6h0cjeVy2HRFSZ7KZuUpx5t25hoFpjlSRKpr\nDukcpiMCAAAggVBEAnDSmtu69Y+tB/X2jmY1tgxewtxqsahwnEdnT8rS9IIsTR6foRRnbPxpcdht\nGpuZqrGZqce9TTRqqP1IcamzTy3tvUMFptaOPrV0DBaATsRus8phHxw9ZbcNjqDqHYiovz8i4xi3\n92a4dOFZfp1b6NU5k7xKS4mN9sKZcWRx7f1NFJEAAACQWHjnAuCEolFDW/a0aPU7Ddq2p0WGBkfo\nXDjNpwvP8mlG0di4LoJYrRZleVzK8rhUdJzb9A1E1NY5WFBqbR8sMHX2DKirZ0BdvWF19w4oHDEU\njkQViQwuCJ7pdirFYVOKyy6vx6Ucb5pyvGkalz04MourrSWuAhbXBgAAQIKK33d+AEZUZ3e/3tjS\nqDWbGhRs75UkTZmQqctLJqhkqm/EpqbFIpfDplxvmnKZboaT4B+TKqfDShEJAAAACYciEoAhhmFo\nz4EOvfZOgzZsb1Y4EpXTYdXc88drfskEFnwGToLValGez63ag50KR6IxcRVAAAAA4EygiARAfQMR\nvfVek1a/06Daw5cmz/Gmaf4FE1R6Xq7SUhwmJwTiS77frT0HOnQg2EXxFQAAAAmDIhKQxJpau7V6\nU4P+saVR3X1hWSxSyTSf5pdM0NkTs1i3BzhNBYcX165rDlFEAgAAQMKgiAQkmXAkqi27W7R6U4Nq\n9rZKkjLSnbr2wkm6bOZ4eTNSTE4IxL98P4trAwAAIPFQRAKSRGNLl97Y0qh12w6qo6tfkjQtL1OX\nl+TpwrN8rNsCnEF5/nRZJO0/PD0UAAAASAQUkYAEFjjUo43bm7Vhe7P2HRx8M5ueYtcVF+Zp7vnj\nlX94yg2AMyvFaZffm6b9TSEZhsHUUAAAACSEkyoiLV++XJs3b5bFYtHixYs1Y8aMoX3r1q3To48+\nKpvNprlz5+quu+467jGNjY164IEHFA6HZbfb9eMf/1g+n29kHhmQhAbCUe1qaNd7+1q1bU/r0CLZ\nVotFxYVeXXLeOJVMGyuH3WZyUiDxTcxxa/37zQq298o3JtXsOAAAAMAnNmwRaf369aqtrVVlZaV2\n796txYsXq7Kycmj/gw8+qMcff1w5OTm67bbbdOWVV6q1tfWYx/zkJz/RTTfdpEWLFukPf/iDfvvb\n3+q+++4b0QcIJCrDMNTS3qt9Bzu1t7FDexs7tOdAh/rDUUmSzWrRuYVezZruV8k0n9ypXGENGE0T\nczxa/36z9jd1UkQCAABAQhi2iFRdXa2ysjJJUlFRkdrb2xUKheR2u1VXV6fMzEyNGzdOkjRv3jxV\nV1ertbX1mMcsWbJELpdLkpSVlaWampqRelxAXDMMQz19EXX1Dqize0BtnX06FBr8am7r0cHWbjW1\ndat/IHrUcePHpuucSVkqnuTVWQVjlOJkxipgliNXZatt6tSFZ/lNTgMAAAB8csO+wwwGgyouLh7a\n9nq9CgQCcrvdCgQC8nq9R+2rq6tTW1vbMY8pLCyUJEUiET355JNDU9+AeNTXP1jk6e2PqLc/op7+\nsFwHO9UUCKm3P6L+gYgGwlH1h6MaCEc1EB7cHohE1T8w+O/AQGRwe+g2UfUPRNTTF1HUMI77u50O\nq3Kz0pSbnaaJOR5NGpehiTkepaVQNAJiRUHO4Jpj+5u4QhsAAAASwym/4zRO8Mb2ZI6JRCK67777\n9OlPf1pz5sw54XFZWWmys3bLafP5PGZHiGu9/WHtP9ipA8EuNQa71BgMqbmtR20dvWrr7FVPX+SM\n/B6n3SqHwyaXwyqnwyZ3mlPuVIfcaQ550pzKSHfKm5Gi7MwUeTNSlJudLm9GiqxWFur9MJ7v5qDd\nj88naeyYVNU1h85oO9HmAAAAMMuwRSS/369gMDi03dzcPLQY9kf3NTU1ye/3y+FwHPeYBx54QBMn\nTtTdd989bLi2tu6TfyQ4is/nUSDApaVPVjRqqK45pB11h7SvsUO1TZ062Nqtj9ZMLZI8aQ6NzUxV\nptup9BSHUpw2pTrtSnHalO1NV2QgrBSnTU6HTU67VU67TQ679agv5+F/7TbrKV+1yRgIq6WFkQ0f\nxvPdHLT78PLGpuvdXUHt2htUptv1ie8vUducwhgAAEB8GLaIVFpaqpUrV6qiokI1NTXy+/1yuweH\n6Ofl5SkUCqm+vl65ublavXq1VqxYoba2tmMe88ILL8jhcOib3/zmiD8wYDjBQz16d1dQ2/a2amd9\nu3r6wkP7Upw2TZ2Qqfwcj3K9acrJSpU/K1XejBTZbdbj3meivsEDcHom5nr07q6gaptCmnEGikgA\nAACAmYYtIpWUlKi4uFgVFRWyWCxasmSJqqqq5PF4tGDBAi1dulT33HOPJGnRokUqLCxUYWHhx46R\npCeffFJ9fX26/fbbJQ0uur106dKRe3TARxwIdumt95q0aWdQ9YH/Gc3jz0rVrLN8ml6QpckTMuQb\nkyrrKY4QAoCPOrIuUm1Tp2YUZZucBgAAAPhkTmpNpHvvvfeo7enTpw99P3v2bFVWVg57jCQ9/fTT\np5oP+MQ6uvr15ntNqq45qNqDg6OE7DarZhRla+aUsZpRlC1vRorJKQEkoomHr9C2v4kRigAAAIh/\nXMoJCckwDO1qaNdr7zRo4/ZmRaKGrBaLZhRla05xrs6fkq0UJ09/ACMry+OSO9UxVMAGAAAA4hnv\nopFQwpGo3nqvSS9tqFNd8+B0tXHZabps5gR96pwcZaQ7TU4IIJlYLBZNzHGrZl+bunsHlJbiMDsS\nEtjy5cu1efNmWSwWLV68WDNmzPjYbR555BG9++67euKJJ0xICAAA4h1FJCSEvv6IXt98QKs27Fdr\nR5+sFotmneXT/JI8nVUw5pSvgAYAZ0pBrkc1+9q0vymk6ROzzI6DBLV+/XrV1taqsrJSu3fv1uLF\niz+23MCuXbu0YcMGORwUMwEAwOmhiIS41jcQ0Wvv1Otvb+5XqGdATodVZbPydOXsAmVnss4RAPMd\nWReptqmTIhJGTHV1tcrKyiQNXrikvb1doVBo6Iq6kvTQQw/pO9/5jn72s5+ZFRMAAMQ5ikiISwPh\niNa8e0B/qa5VR1e/Ul12fbZ0kq64ME+eNKasAYgdLK6N0RAMBlVcXDy07fV6FQgEhopIVVVVuuii\nizRhwgSzIgIAgARAEQlxJWoYWv9+k/64Zo9aOnrlctp07cWTdOVF+UpnrREAMciXlaoUp021TSGz\noyCJGIYx9P2hQ4dUVVWl3/72t2pqajrp+/D5PCMRDZ8Q/RJ76JPYRL/EHvokMVBEQtz4oO6QKl/b\nqb2NnbLbLFo4O1+L5kxUBiOPAMQwq8WiAr9bOxva1TcQkcthMzsSEpDf71cwGBzabm5uls/nkyS9\n+eabam1t1a233qr+/n7t379fy5cv1+LFi094n4EAo+dijc/noV9iDH0Sm+iX2EOfxJ7TLepRRELM\na2rt1rNrduudDwKSpIvO9uv6eUXyjUk1ORkAnJyCHI8+qG9XfXNIRRMyzY6DBFRaWqqVK1eqoqJC\nNTU18vv9Q1PZrrrqKl111VWSpPr6ej3wwAPDFpAAAACOhSISYlaoZ0AvrN2r1e80KBI1NGVCpsrn\nT+ENGIC4MzF38JOefQc7+RuGEVFSUqLi4mJVVFTIYrFoyZIlqqqqksfj0YIFC8yOBwAAEgRFJMSc\ngXBUr75dr/9et0/dfWH5xqToxsum6MKzfLJYLGbHA4BTVjguQ5K0t7HD5CRIZPfee+9R29OnT//Y\nbfLy8vTEE0+MViQAAJBgKCIhZhiGoQ3bm/Xcmt0KtvcqPcWuivlTdHlJnhx2q9nxAOC05WanKcVp\no4gEAACAuEYRCaYzDEM1e1v1pzf2aG9jp2xWixbMytdnSifJncoV1wDEP6vFokm5Hu3Yf0jdvWGl\npfDyCwAAgPjDWSxMYxiGduw/pD+9sUc769slSbOm+3X9vMnKyUozOR0AnFmF4zO0ff8h1R7s0NmT\nvGbHAQAAAE4ZRSSMunAkqg3vN+uVt+u0t3HwMo8zp4zV5y8tVEHO6V1mEABi3eTD6yLtaaSIBAAA\ngPhEEQmjwjAM1TWHtHFHs97Y3Kj2rn5ZJF0wdayumTNJk8dnmB0RAEbUkcW19x0ungMAAADxhiIS\nRsyhUJ/2N3Vq+/5DentHswKHeiVJqS67Fs7O1xUX5sk3JtXklAAwOrI8LmW6ndrD4toAAACIUxSR\ncEzBQz060NKlrp6wQr0D6uoZUNSQrJbBBWItFslischqHfx+YCCqUM+AQr0D6uzqV32gS+1d/UP3\n53LadNHZfpVM82lGUbZSnDz1ACQXi8WiwtwMvbsrqLbOPmV5XGZHAgAAAE4J7+QhSRoIR7RxR0Dv\n72vT9v1tCrb3fqL7y85w6YKpYzUxx6NJ4zJ09sQxcthtZygtAMSnwvGDRaR9jR3K8vjMjgMAAACc\nEopISS4SjWrd1oN6/h971dbZJ0lKT7GrZJpPk3I9cqc55E5xKC3FLpvVIsOQooYhwxhc5yh6+F+7\n3Sp3ikOeNIfSUx1yOSgYAcBHfXhx7QumUUQCAABAfKGIlMQ27wrqmdW71NjSLYfdqqsuKtCnzslR\nvt8tq9VidjwASDiTxg1egXIf6yIBAAAgDlFESkKGYei/q2v1p9f3yGKR5p4/Tp8tLZQ3I8XsaACQ\n0NJTHMrJStXexk5FDUNWCwV7AAAAxA+KSEkmHInq9y/t0OubG5Wd4dI3rp+hghyP2bEAIGkUjs/Q\nmzVNam7rUa43zew4AAAAwEmzmh0Ao6enL6yf/nGLXt/cqIIct/71i7MoIAHAKCs8vC7S3gNMaQMA\nAEB8oYiUJCLRqH5WtVXb9rTqvMnZuv/WEo1xc3lpABhtRxbX3su6SAAAAIgzTGdLEpWv7dL7tW2a\nOWWs7rruXNms1A8BwAwFOW7ZrBaKSAAAAIg7VBKSwNqtjXplY73Gj03XHZ85hwISAJjIYbcpz+9W\nbVOnBsJRs+MAAAAAJ41qQoLb29ih/3xxh1Jddn3juvOU6mLwGQCYbeqETIUjBqORAAAAEFcoIiWw\nju5+/axqqyKRqP73Z4uVw1WAACAmTM0fI0naWX/I5CQAAADAyaOIlMAqX92pts4+fWHuZM0oyjY7\nDgDgsGl5mZKknfXtJicBAAAATh5FpAT1zo5mVdc0aVKuR4s+PdHsOACAD8l0u+TPStXO+nZFo4bZ\ncQAAAICTQhEpAfUNRPTz5zbLarHof109XVarxexIAICPmJY3Rj19YdUHQmZHAQAAAE4KRaQE9MI/\n9qqptVsLL8pXQY7H7DgAgGOYms+UNgAAAMQXikgJZn9Tp1atr1OON02fKy00Ow4A4Dim5bG4NgAA\nAOILRaQEEjUM/eeLOxQ1DP3T9efL5bSZHQkAcBz+rFRlpDv1Qd0hGQbrIgEAACD2UURKIBu3N2tv\nY4dmT/erZLrf7DgAgBOwWCyalpepQ6F+Bdp7zY4DAAAADIsiUoIIR6Kq+vse2awWXT9vstlxAAAn\nYWr+4SltdUxpAwAAQOyjiJQg/v7uATUf6tFlF0yQPyvN7DgAgJNwZF2kDygiAQAAIA5QREoAPX1h\nvS1SkVsAABplSURBVLB2r1xOmz5z8SSz4wAATlK+360Up40rtAEAACAuUERKAKvW71dn94CuvqhA\nGelOs+MAAE6S1WrRlAmZOtjarY6ufrPjAAAAACdEESnOtYf6tGp9nTLSnVp4Ub7ZcQAAp2hoXaR6\nprQBAAAgtp1UEWn58uUqLy9XRUWFtmzZctS+devW6YYbblB5ebkee+yxYY/53e9+p+LiYnV1dZ2h\nh5Dc/ru6Vn0DEX2udJJSnHaz4wAATtG0vExJ0g7WRQIAAECMG7bqsH79etXW1qqyslK7d+/W4sWL\nVVlZObT/wQcf1OOPP66cnBzddtttuvLKK9Xa2nrMY55//nm1tLTI7+fy82dCW2ef/v7uAY3NTNGl\n5483Ow4A4DRMHp8hp8Oqmr2tZkcBAAAATmjYIlJ1dbXKysokSUVFRWpvb1coFJLb7VZdXZ0yMzM1\nbtw4SdK8efNUXV2t1tbWYx5TVlYmt9utP//5zyP4kJLH396qVTgS1TVzJspuY2YiAMQjh92m6QVZ\n2rK7RS3tvcrOTDE7EgAAAHBMw1YegsGgsrKyhra9Xq8CgYAkKRAIyOv1fmzf8Y5xu91nMntSOxQa\nHIWUneFS6XnjzI4DAPgEzi0cfC3dtrfF5CQAAADA8Z3yIjqGYZzyLzmdYyQpKytNdrvttI5NdP+1\nrlYD4ajKF07XuNzMY97G5/OMcipItLtZaHdz0O5nxtxZBXrylZ3aeaBDNyyYfsLb0uYAAAAwy7BF\nJL/fr2AwOLTd3Nwsn893zH1NTU3y+/1yOBzHPeZUtLV1n/IxyaC9q19/W7dX3gyXZhZmKRDo/Nht\nfD7PMX+OkUW7m4N2NwftfuY4DENjM1O0aUdAB5vaZbMee6BworY5hTEAAID4MOx0ttLSUq1atUqS\nVFNTI7/fPzQtLS8vT6FQSPX19QqHw1q9erVKS0tPeAw+uRffqlV/OKprPs1aSACQCCwWi86dnK2e\nvrD2HOgwOw4AAABwTMOORCopKVFxcbEqKipksVi0ZMkSVVVVyePxaMGCBVq6dKnuueceSdKiRYtU\nWFiowsLCjx0jSb/4xS+0bt06BQIB3XHHHZo5c6buu+++kX2ECaajq1+rNzUoy+PSJTO4IhsAJIpz\nC71as6lB2/a0amreGLPjAAAAAB9jMU53waJRkIhD9j+pZ9fs0t/e3K9bF0zTFRfmHfd2iTrlIdbR\n7uag3c1Bu59ZPX1hffPf31BBjlv/50uzj3mbRG1zprPFpkR8rsW7RP0bEM/ok9hEv8Qe+iT2nO75\nF3Oh4khnd79ee7tBmW6n5p7PFdkAIJGkuuwqmpCpfY2d6uzuNzsOAAAA8DEUkeLIyxvr1DcQ0aJP\nTZSDq9YBQMI5t9ArQ1LNvlazoyAOLV++XOXl5aqoqNCWLVuO2vfmm2/qpptuUkVFhR544AFFo1GT\nUgIAgHhGESlOhHoG9MrGemWkOzV3JmshAUAiOm9ytiSpZg9FJJya9evXq7a2VpWVlVq2bJmWLVt2\n1P7vf//7+ulPf6qnn35aXV1deuONN0xKCgAA4hlFpDjxysY69fZHdNVFBXI5GIUEAIkoP8ctT5pD\n2/a2KoaXLEQMqq6uVllZmSSpqKhI7e3tCoVCQ/urqqqUm5srSfJ6vWprazMlJwAAiG8UkeJAd++A\nXt5YL0+aQ5dfMMHsOACAEWK1WHRuoVftXf3a3xQa/gDgsGAwqKysrKFtr9erQCAwtO12uyVJzc3N\nWrt2rebNmzfqGQEAQPyzmx0Aw3vl7Xr19IV142VFcjkZhQQAiWzmVJ+qa5q0cUezJuZy1TKcnmON\nZGtpadGdd96pJUuWHFVwOh6umheb6JfYQ5/EJvol9tAniYEiUozr6Qvr5Q11cqc6dHkJo5AAINHN\nKMqWy2HTW+816bq5k2WxWMyOhDjg9/sVDAaHtpubm+Xz+Ya2Q6GQ7rjjDn3729/WJZdcclL3yaWY\nYw+XyI499Elsol9iD30Se063qMd0thj32jv16uoNa+HsfKU4qfkBQKJzOWyaOXWsgu292neQky2c\nnNLSUq1atUqSVFNTI7/fPzSFTZIeeughfelLX9LcuXPNiggAABIAVYkY1tsf1qr1dUpz2XXFhXlm\nxwEAjJKLpvv11ntNWv9+kwrHZZgdB3GgpKRExcXFqqiokMVi0ZIlS1RVVSWPx6NLLrlEzz//vGpr\na/Xcc89Jkq699lqVl5ebnBoAAMQbikgxbPWmBoV6BvT5SwqV6qKrACBZnDs5W6kuuzZsb9aNl0+R\nlSltOAn33nvvUdvTp08f+n7btm2jHQcAACQgprPFqL6BiF58a79SXTaVzWIUEgAkE4fdqpKpY9Xa\n0ac9DR1mxwEAAAAkUUSKWX/f1KDO7gGVXZivtBSH2XEAAKNs9tk5kqS33m8yOQkAAAAwiCJSDOof\niOhvb+2Xy2nTgtn5ZscBAJjgnElZSk+xa+P2ZkWjH79cOwAAADDaKCLFoDWbGtTe1a+yC/PkTmUU\nEgAkI7vNqgvP8qu9q18f1B0yOw4AAABAESnWdPcO6M/r9inVZdeVFxWYHQcAYKKLzvZLktZvbzY5\nCQAAAEARKeb85c1adfWGdc2ciYxCAoAkN70gSxnpTm14v0l9AxGz4wAAACDJUUSKIa0dvXplY72y\nPC6VXcgV2QAg2VmtFs07f7y6esOqrjlodhwAAAAkOYpIMeRPb+zRQDiqL1w6WU6Hzew4AIAYcNkF\nE2SzWvTqxnoZBgtsAwAAwDwUkWJEXXNI67YeVJ4vXRefm2t2HABAjMjyuDR7ul8NwS5t2Rk0Ow4A\nAACSGEWkGPHcmt0yJN14+RRZrRaz4wAAYsgVswanOL/wxh6TkwAAACCZUUSKAZt2BrR1T4vOnpil\ncwu9ZscBAMSYovGZmjw+QxveP6jmtm6z4wAAACBJUUQyWW9/WH94+QPZrBbdsmCaLBZGIQEAPq5s\nVp4MQ3r17QazowAAACBJUUQy2fNv7FVrR5+u/vRETRibbnYcAECMmnWWX94Ml/6x9YB6+sJmxwEA\nAEASoohkotqDnXp5Y538Wam6ds5Es+MAAGKY3WbVoosL1dMX0eubD5gdBwAAAEmIIpJJolFD//ni\ndhmGdPuVZ8npsJkdCQAQ466+uFCpLrv+e90+hXoGzI4DAACAJEMRySSvvl2vfQc7Nac4R8WTWEwb\nADC8jHSnPls6SV29Yf3XP/aaHQcAAABJhiKSCfY3derZNbuVnmJX+fypZscBAMSRKy7Mkz8rVavf\nadCBYJfZcQAAAJBEKCKNsp6+sH7x/DaFI1F97dpzlJHuNDsSACCO2G1WlV8+RVHD0DOrd5kdBwAA\nAEmEItIoMgxDv1u1Q01tPbr6UwU6f8pYsyMBAOLQzKljdfbELG3Z3aKte1rMjgMAAIAkQRFpFP19\n8wG99V6TpkzI1BfmTjY7DgAgTlksFpXPnyKLpKdf3alwJGp2JAAAACQBikijZM+BDj358k6lp9h1\n5+eKZbfR9ACA01eQ49G8mePV2NKtp1/daXYcAAAAJAEqGaOgvjmk//vMu4pEo7rjM+fIm5FidiQA\nQAIonz9VE3zpeu2dBlXXHDQ7DgAAABIcRaQR1tTWrRWV76qrN6yvLDpbM4pYBwkAcGa4nDbd/YXz\nlOqy6T//tl11zSGzIwEAACCBUUQaQa0dvVrx1Lvq6OrXrQumqfS8cWZHAgAkmBxvmr56zTnqD0f1\n2J+2qrt3wOxIAAAASFAUkUbIgWCXHn5qk1o6evWFuZN1xYV5ZkcCACSokmk+Xf3pAjW39ejnz29T\nb3/Y7EgAAABIQBSRRsC7O4N68Hcb1dzWo2svnqRr50w0OxIAIMFdN3eyZk4Zq/f2tenHT72rzu5+\nsyMBAAAgwVBEOoMMw9Cf1+7Vyj9uUSRq6OufOUfXzZ0si8VidjQAQIKzWa36py+cq9Jzc7W3sUPL\nf/+Ogu09ZscCAABAArGbHSBRNAS79OTLH+j92jZ5M1z6xnUzNDHXY3YsAEASsdus+so1ZyvD7dTf\n3tyvZU+8rf/9mWJNn5hldjQAAAAkAIpIn1B3b1gvrN2rV9+uVyRqaEZRtr6y6GxlpDvNjgYASEIW\ni0U3XjZFmWlOVb62Sw8/tUlzinN00/ypyuS1CQAAAJ8ARaTT1NrRq7+/e0Br3m1QZ/eAfGNSdPMV\n03T+lGymrwEATLfwogJNzR+j363aoeqaJm3e1aLPXVKoS2aMU6qLl38AAACcupM6i1y+fLk2b94s\ni8WixYsXa8aMGUP71q1bp0cffVQ2m01z587VXXfdddxjGhsbdd999ykSicjn8+nHP/6xnM74+VS0\npy+s92vbtHZro97dFZRhSKkuu75waaGu+lSBHHab2REBABhSOC5D/+eLs7R6U4OqXt+tp17dqarX\n9+hT5+Ro3szxKhyXYXZEAAAAxJFhi0jr169XbW2tKisrtXv3bi1evFiVlZVD+x988EE9/vjjysnJ\n0W233aYrr7xSra2txzzmpz/9qW655RZdffXVevTRR/Xcc8/plltuGdEHeLoMw1BbZ58agl3a29ih\nmr2t2t3QoahhSJIm5no0/4IJuuicHLkcFI8AALHJarXoigvzNGu6X6+/26DXNx8Y+srOSNH0gjGa\nVjBG0/LHyJeZKquV0bQAAAA4tmGLSNXV1SorK5MkFRUVqb29XaFQSG63W3V1dcrMzNS4ceMkSfPm\nzVN1dbVaW1uPecxbb72lH/zgB5Kkyy+/XL/5zW9MKSJFo4a2729TR3e/+gei6h+IqLsvrPZQv9q7\n+nUo1KfGlm719IWHjrFIKhyfoeJJXs2cOpZPbwEAcSUz3anPlBbqmjmTtG1vq/6x5cDg6NptB7V2\n20FJkt1mkW9MqnKy0uTNcCk9xaH0VIcy0hyaOXWsUpxMgwMAAEhmw54NBoNBFRcXD217vV4FAgG5\n3W4FAgF5vd6j9tXV1amtre2Yx/T09AxNX8vOzlYgEDiTj+Wkvb+/TY88/e5x99usFvmzUlU8KUsT\nfG7l+dw6q2CM3KmOUUwJAMCZZ7VaNKMoWzOKshU1DDUEurR9f5v2HOhQU2u3mtp61NjS/bHjKq6Y\nqoWz801IDAAAgFhxyh8pGoenc33SY07mfrKy0mQfgXWGLh6Tpv6oFIkacjlscjltSnXZ5c1IUZbH\nJU+aMyGG8/t8HrMjJCXa3Ry0uzlo99F3pts8x5+hkuJxQ9uGYaijq1+tHb0K9Qwo1N2vvv6IZp2T\ny4cpAAAASW7YIpLf71cwGBzabm5uls/nO+a+pqYm+f1+ORyOYx6Tlpam3t5epaSkDN32RNraPv5J\n6JlywWTvMX/e39Ovlp7+Efu9o8Xn8ygQ6DQ7RtKh3c1Bu5uDdh99o9nmbodVbodLynBJknpCveoJ\n9Y7I76IYCQAAEB+sw92gtLRUq1atkiTV1NTI7/fL7XZLkvLy8hQKhVRfX69wOKzVq1ertLT0uMdc\nfPHFQz9/6aWXdOmll47U4wIAAAAAAMAZNOxIpJKSEhUXF6uiokIWi0VLlixRVVWVPB6PFixYoKVL\nl+qee+6RJC1atEiFhYUqLCz82DGS9I1vfEPf/e53VVlZqfHjx+vzn//8yD46AAAAAAAAnBEW43QW\nORolTJM4fUwzMQftbg7a3Ry0++hL1DZnOltsSsTnWrxL1L8B8Yw+iU30S+yhT2LP6Z5/DTudDQAA\nAAAAAKCIBAAAAAAAgGFRRAIAAEgAy5cvV3l5uSoqKrRly5aj9q1bt0433HCDysvL9dhjj5mUEAAA\nxDuKSAAAAHFu/fr1qq2tVWVlpZYtW6Zly5Ydtf/BBx/UypUr9dRTT2nt2rXatWuXSUkBAEA8o4gE\nAAAQ56qrq1VWViZJKioqUnt7u0KhkCSprq5OmZmZGjdunKxWq+bNm6fq6moz4wIAgDhFEQkAACDO\nBYNBZWVlDW17vV4FAgFJUiAQkNfrPeY+AACAU2E3O8CJcMnfT4b2Mwftbg7a3Ry0++ijzXEyDMP4\nxPfBcy020S+xhz6JTfRL7KFPEgMjkQAAAOKc3+9XMBgc2m5ubpbP5zvmvqamJvn9/lHPCAAA4h9F\nJAAAgDhXWlqqVatWSZJqamrk9/vldrslSXl5eQqFQqqvr1c4HNbq1atVWlpqZlwAABCnLMaZGO8M\nAAAAU61YsUIbN26UxWLRkiVL9N5778nj8WjBggXasGGDVqxYIUlauHChvvrVr5qcFgAAxCOKSAAA\nAAAAABgW09kAAAAAAAAwLIpIAAAAAAAAGBZFpAS0fPlylZeXq6KiQlu2bDE7TsL54IMPVFZWpt//\n/veSpMbGRt1+++265ZZb9K1vfUv9/f2SpBdeeEHXX3+9brzxRj377LNmRk4IDz/8sMrLy3X99dfr\npZdeot1HWE9Pj771rW/ptttu04033qjVq1fT5qOot7dXZWVlqqqqot0x4k503rBu3TrdcMMNKi8v\n12OPPWZSwuRzoj558803ddNNN6miokIPPPCAotGoSSmTy8mcXz/yyCO6/fbbRzlZcjtRvzQ2Nurm\nm2/WDTfcoO9///smJUw+J+qTP/zhDyovL9fNN9+sZcuWmZQwOX30PeyHnfJrvYGE8tZbbxlf//rX\nDcMwjF27dhk33XSTyYkSS1dXl3HbbbcZ3/ve94wnnnjCMAzDuP/++42//vWvhmEYxiOPPGL84Q9/\nMLq6uoyFCxcaHR0dRk9Pj3HNNdcYbW1tZkaPa9XV1cbXvvY1wzAMo7W11Zg3bx7tPsL+8pe/GL/6\n1a8MwzCM+vp6Y+HChbT5KHr00UeN6667zvjjH/9Iu2NEDXfecPXVVxsHDhwwIpGIcfPNNxs7d+40\nI2ZSGa5PFixYYDQ2NhqGYRjf+MY3jDVr1ox6xmRzMufXO3fuNMrLy43bbrtttOMlreH65Zvf/Kbx\n0ksvGYZhGEuXLjUaGhpGPWOyOVGfdHZ2GpdffrkxMDBgGIZhfPnLXzY2bdpkSs5kc6z3sB92qq/1\njERKMNXV1SorK5MkFRUVqb29XaFQyORUicPpdOrXv/61/H7/0M/eeustXXHFFZKkyy+/XNXV1dq8\nebPOO+88eTwepaSkqKSkRO+8845ZsePe7Nmz9e///u+SpIyMDPX09NDuI2zRokW64447JA1+kpeT\nk0Obj5Ldu3dr165duuyyyyTxNwYj60TnDXV1dcrMzNS4ceNktVo1b948VVdXmxk3KQx3LldVVaXc\n3FxJktfrVVtbmyk5k8nJnF8/9NBD+s53vmNGvKR1on6JRqN6++23NX/+fEnSkiVLNH78eNOyJosT\n9YnD4ZDD4VB3d7fC4bB6enqUmZlpZtykcaz3sEeczms9RaQEEwwGlZWVNbTt9XoVCARMTJRY7Ha7\nUlJSjvpZT0+PnE6nJCk7O1uBQEDBYFBer3foNvTDJ2Oz2ZSWliZJeu655zR37lzafZRUVFTo3nvv\n1eLFi2nzUfKjH/1I999//9A27Y6RdKLzhkAgwPPMBMOdy7ndbklSc3Oz1q5dq3nz5o16xmQzXJ9U\nVVXpoosu0oQJE8yIl7RO1C+tra1KT0/XD3/4Q91888165JFHzIqZVE7UJy6XS3fddZfKysp0+eWX\n6/zzz1dhYaFZUZPKsd7DHnE6r/UUkRKcYRhmR0gqx2tv+uHMeOWVV/Tcc899bF477T5ynn76af3i\nF7/Qv/zLvxzVnrT5yHj++ec1c+ZM5efnH3M/7Y6RxnMp9hyrT1paWnTnnXdqyZIlR71hw+j4cJ8c\nOnRIVVVV+vKXv2xiIkj62HlKU1OTvvjFL+r3v/+93nvvPa1Zs8a8cEnqw30SCoX0y1/+Ui+++KJe\nffVVbd68Wdu3bzcxHU4XRaQE4/f7FQwGh7abm5vl8/lMTJT40tLS1NvbK0lqamqS3+8/Zj8ca/gg\nTt4bb7yh//iP/9Cvf/1reTwe2n2Ebdu2TY2NjZKks88+W5FIROnp6bT5CFuzZo1effVV3XTTTXr2\n2Wf185//nOc6RtSJzhs+uu/I8w8ja7hzuVAopDvuuEPf/va3dckll5gRMemcqE/efPNNtba26tZb\nb9Xdd9+tmpoaLV++3KyoSeVE/ZKVlaXx48eroKBANptNc+bM0c6dO82KmjRO1Ce7d+9Wfn6+vF6v\nnE6nZs2apW3btpkVFYedzms9RaQEU1paqlWrVkmSampq5Pf7h4Y9Y2RcfPHFQ23+0ksv6dJLL9X5\n55+vrVu3qqOjQ11dXXrnnXc0a9Ysk5PGr87OTj388MP65S9/qTFjxkii3Ufaxo0b9Zvf/EbS4NDk\n7u5u2nwU/OQnP9Ef//hHPfPMM7rxxhv1T//0T7Q7RtSJzhvy8vIUCoVUX1+vcDis1atXq7S01My4\nSWG4c7mHHnpIX/rSlzR37lyzIiadE/XJVVddpb/+9a965pln9LOf/UzFxcVavHixmXGTxon6xW63\nKz8/X/v27Rvaz9SpkXeiPpkwYYJ279499MHYtm3bNGnSJLOi4rDTea23GIxbTjgrVqzQxo0bZbFY\ntGTJEk2fPt3sSAlj27Zt+tGPfqSGhgbZ7Xbl5ORoxYoVuv/++9XX16fx48frhz/8oRwOh1588UU9\n/vjjslgsuu222/TZz37W7Phxq7KyUitXrjzqxf+hhx7S9773Pdp9hPT29upf//Vf1djYqN7eXt19\n990699xz9d3vfpc2HyUrV67UhAkTdMkll9DuGFEfPW9477335PF4tGDBAm3YsEErVqyQJC1cuFBf\n/epXTU6bHI7XJ5dccolmz56tCy64YOi21157rcrLy01MmxxO9P/kiPr6ej3wwAN64oknTEyaXE7U\nL7W1tbr//vtlGIamTZumpUuXymplDMVIO1GfPP3006qqqpLNZtMFF1yg++67z+y4SeFY72Hnz5+v\nvLy803qtp4gEAAAAAACAYVGKBQAAAAAAwLAoIgEAAAAAAGBYFJEAAAAAAAAwLIpIAAAAAAAAGBZF\nJAAAAAAAAAyLIhIAAAAAAACGRREJAAAAAAAAw6KIBAAAAAAAgGH9P+YfQiLiqdoTAAAAAElFTkSu\nQmCC\n", "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "NShPt-5udbOg", "colab_type": "code", "outputId": "f42df0c4-ed4d-4fbc-dfec-fbc5e7b75777", "colab": { "base_uri": "https://localhost:8080/", "height": 402 } }, "cell_type": "code", "source": [ "figv, axsv = plt.subplots(nrows=2, ncols=2, figsize=(20, 5))\n", "\n", "axq = sns.violinplot(x=df_original[\"Quantity\"], ax=axsv[0, 0])\n", "axq.set_xlim(-100, 100)\n", "\n", "axp = sns.violinplot(x=df_original[\"UnitPrice\"], ax=axsv[0, 1])\n", "axp.set_xlim(-10, 40)\n", "\n", "axd = sns.violinplot(x=df_original[\"day_number\"], ax=axsv[1, 0])" ], "execution_count": 93, "outputs": [ { "output_type": "stream", "text": [ "/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:588: FutureWarning: remove_na is deprecated and is a private function. Do not use.\n", " kde_data = remove_na(group_data)\n", "/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:816: FutureWarning: remove_na is deprecated and is a private function. Do not use.\n", " violin_data = remove_na(group_data)\n" ], "name": "stderr" }, { "output_type": "display_data", "data": { "image/png": "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\nlZVkZGQwb9481q9fz7JlywC4+uqr+eY3v+lxtYPDJ/Vk9uzZXHTRRUydOrXvvl/4wheYP3++h9UO\nHsf6WelVU1PDQw89xAsvvOBhpYPHsXqyd+9eHnzwQVzXZeLEiTz66KOYpsYmnAnH6suLL75IUVER\nlmUxdepUHnjgAa/LHfCO9jvuVVddxciRI0/otV6Bj4iIiIiIiIjIAKPYVERERERERERkgFHgIyIi\nIiIiIiIywCjwEREREREREREZYBT4iIiIiIiIiIgMMAp8REREREREREQGGAU+IgPQfffdR1FRkddl\nHNNVV13F3r17vS5DRERERERkQFLgIyIiIiIiIiIywNheFyAiJ89xHB5++GF27NhBYWEhXV1dADzz\nzDMUFxcDMGzYMH72s5/xi1/8Atu2ufPOOwH49a9/TWtrKw888MBRH7uoqIg1a9bgOA67d++msLCQ\n5cuXU1JSws9//nP++Mc/AvDggw8yffp0Lr30Ur7zne8wa9YsNmzYQE5ODl/84hf561//Sm1tLc88\n8wznnnsuAH/+858pKyujqamJf/3Xf+Xiiy9m//79/PSnPyUYDNLV1cWPfvQjLrvsMh588EH8fj+7\nd+9m2bJlFBQUnO4vq4iIiIiISL+lET4iA8CaNWv44IMPePnll3nyySfZsWMHsViMlJQU/ud//ocX\nX3yRjo4OVq1axY033sgrr7yC67oAvPrqq9xwww3HfPzNmzezZMkSioqK2L59O9u2bTvm/Xfv3s3N\nN99MUVERu3fvZt++ffzud7/jC1/4Ai+//HLf/bKzs/nv//5vHn74YZ544gkAHn30UW677Taef/55\nfvnLX/LII48QjUYB6Orq4oUXXlDYIyIiIiIichwa4SMyAOzcuZOpU6diGAYpKSlMmTIFy7IwTZNb\nbrkF27b54IMPaGlpYeTIkYwZM4aSkhKGDx9OSkoK48aNO+bjT5kyheTkZACGDx9OW1sbpvnJeXFO\nTg5jx44FoKCggGnTpgHxUUb79+/vu9+sWbMAmDp1KlVVVQCsW7eOzs5Onn32WQBs26apqanvfiIi\nIiIiInJ8CnxEBgDXdTEMo++64zgcPHiQV155hZdffpnU1FTuuuuuvuMLFizgr3/9K2PGjDnu6B4A\ny7KO+XwAkUjkE+9/+PXekUVA32O4rtsXIPn9fpYvX05ubu7H6vD7/cetVURERERERDSlS2RAmDBh\nAqWlpbiuSyAQoLS0lOTkZAoLC0lNTaW2tpYtW7YQDocBuOKKKygrK+Ptt9/mmmuuOaHnTE9P5+DB\ng7iuSzAYpLS09DM/xtq1awHYtGkTZ599NgDTp0/nf//3fwFobm7mscceO6H6REREREREBjON8BEZ\nAGbPns0rr7zCjTfeyIgRI7jwwgvx+XwEAgFuvvlmzj77bO68806effZZLr74YsaOHcvnPvc5AoEA\nKSkpJ/Sc5557Lueccw5f+tKXGD169AlNt2ptbeU73/kO+/fvZ/HixQA8/PDD/OQnP+Fvf/sb4XCY\n7373uydUn4iIiIiIyGBmuIfPrxCRQSEcDnPLLbewdOlSJkyY4HU5IiIiIiIicopphI/IIPPuu++y\nbNky5s+f3xf2vPHGGzz//PNHvf8LL7xwJssTERERERGRU0AjfEREREREREREBhgt2iwiIiIiIiIi\nMsAo8BERERERERERGWAU+IiIiIiIiIiIDDAKfEREREREREREBhgFPiIiIiIiIiIiA4wCHxERERER\nERGRAeb/B9Cc9AVFoVkKAAAAAElFTkSuQmCC\n", "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "QWzOIIq_Gpui", "colab_type": "text" }, "cell_type": "markdown", "source": [ "Day 250 seems to represent a decent threshold data (more than 50% of the orders happened before that day but less than 75%\n", "\n", "Would use this query:" ] }, { "metadata": { "id": "Ur3i5h1qJx4Z", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "threshold_qry = \"\"\"\n", "SELECT\n", " DATETIME_ADD(mind, INTERVAL 250 DAY)\n", "FROM\n", " (\n", " SELECT\n", " MIN(PARSE_DATETIME(\"%m/%d/%y %H:%M\", InvoiceDate)) mind,\n", " MAX(PARSE_DATETIME(\"%m/%d/%y %H:%M\", InvoiceDate)) maxd\n", " FROM\n", " `{}.{}.{}`) mm\n", "\"\"\"" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "wuzX4obrSnwb", "colab_type": "text" }, "cell_type": "markdown", "source": [ "### RFMT" ] }, { "metadata": { "id": "qwKjxIeNSnwc", "colab_type": "code", "outputId": "2e0538c8-9f00-4c69-eb6d-a1500441738b", "colab": { "base_uri": "https://localhost:8080/", "height": 348 } }, "cell_type": "code", "source": [ "# Frequency of Repeat transaction\n", "repeat_transactions_query = \"\"\"\n", "SELECT\n", " customer_id,\n", " COUNT(DISTINCT order_date) -1 AS num_transactions\n", "FROM\n", " `{}.{}.data_cleaned`\n", "GROUP BY\n", " customer_id\"\"\"\n", "repeat_transactions_query = repeat_transactions_query.format(PROJECT_ID, DATASET)\n", "\n", "df_repeat_transactions = pd.io.gbq.read_gbq(query=repeat_transactions_query, dialect ='standard', project_id=PROJECT_ID)\n", "print(df_repeat_transactions.head(2))\n", "df_repeat_transactions.describe()" ], "execution_count": 52, "outputs": [ { "output_type": "stream", "text": [ " customer_id num_transactions\n", "0 16915 5\n", "1 15349 1\n" ], "name": "stdout" }, { "output_type": "execute_result", "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num_transactions
count1650.000000
mean7.436364
std9.308011
min1.000000
25%3.000000
50%5.000000
75%9.000000
max141.000000
\n", "
" ], "text/plain": [ " num_transactions\n", "count 1650.000000\n", "mean 7.436364\n", "std 9.308011\n", "min 1.000000\n", "25% 3.000000\n", "50% 5.000000\n", "75% 9.000000\n", "max 141.000000" ] }, "metadata": { "tags": [] }, "execution_count": 52 } ] }, { "metadata": { "id": "v3pFNR9mSnwf", "colab_type": "code", "outputId": "5c19fd34-8d3e-41d4-c1cc-23ba6ce96e57", "colab": { "base_uri": "https://localhost:8080/", "height": 337 } }, "cell_type": "code", "source": [ "fig, axs = plt.subplots(figsize=(10, 5))\n", "sns.kdeplot(df_repeat_transactions['num_transactions'])" ], "execution_count": 60, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "" ] }, "metadata": { "tags": [] }, "execution_count": 60 }, { "output_type": "display_data", "data": { "image/png": "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\nQmCC\n", "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "hs55GnZbSnwh", "colab_type": "code", "outputId": "c64a274b-9ce9-48a5-a30a-7981f3f5d6e5", "colab": { "base_uri": "https://localhost:8080/", "height": 348 } }, "cell_type": "code", "source": [ "recency_query = \"\"\"\n", "SELECT\n", " customer_id,\n", " DATE_DIFF(MAX(order_date), MIN(order_date), DAY) recency\n", "FROM\n", " `{}.{}.data_cleaned`\n", "GROUP BY\n", " customer_id\"\"\"\n", "recency_query = recency_query.format(PROJECT_ID, DATASET)\n", "\n", "df_recency = pd.io.gbq.read_gbq(query=recency_query, dialect ='standard', project_id=PROJECT_ID)\n", "print(df_recency.head(2))\n", "df_recency.describe()" ], "execution_count": 55, "outputs": [ { "output_type": "stream", "text": [ " customer_id recency\n", "0 16676 304\n", "1 12901 262\n" ], "name": "stdout" }, { "output_type": "execute_result", "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
recency
count1650.000000
mean256.879394
std90.692561
min1.000000
25%197.250000
50%272.500000
75%337.750000
max373.000000
\n", "
" ], "text/plain": [ " recency\n", "count 1650.000000\n", "mean 256.879394\n", "std 90.692561\n", "min 1.000000\n", "25% 197.250000\n", "50% 272.500000\n", "75% 337.750000\n", "max 373.000000" ] }, "metadata": { "tags": [] }, "execution_count": 55 } ] }, { "metadata": { "id": "AlykZLJvSnwk", "colab_type": "code", "outputId": "21a069d0-e3e3-4cef-e866-808905d3559e", "colab": { "base_uri": "https://localhost:8080/", "height": 337 } }, "cell_type": "code", "source": [ "fig, axs = plt.subplots(figsize=(10, 5))\n", "sns.kdeplot(df_recency['recency'])" ], "execution_count": 59, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "" ] }, "metadata": { "tags": [] }, "execution_count": 59 }, { "output_type": "display_data", "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlwAAAEvCAYAAACQQh9CAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzs3Xl81PW97/HXLNkne2ayQ0JYDYRN\nVEzZBETRulWFemhve2s3tef0lNZabs+Bnov0VM/x1u61Rz1trRWP4latWBEVIYIssoQ1IQlJyDLZ\nMyHrzO/+EUhdgATI5DfJvJ+PBw+YzHwznx9fh7z9/r6LxTAMAxERERHxG6vZBYiIiIiMdApcIiIi\nIn6mwCUiIiLiZwpcIiIiIn6mwCUiIiLiZwpcIiIiIn5mN7uA83G7W80uIWjEx0fS2HjK7DLkI9Qn\ngUd9EljUH4En2PvE6Yw+53Ma4RIA7Hab2SXIJ6hPAo/6JLCoPwKP+uTcFLhERERE/EyBS0RERMTP\nFLhERERE/EyBS0RERMTPFLhERERE/EyBS0RERMTPFLhERERE/EyBS0RERMTPFLhERERE/Cygj/YR\nEREZiYoqmimqbGZUsoPs1BgiwvTj+Fxee+0V3n9/G3V1bq68cjbvv78Vi8XKnDnz+fznV9Da2sq/\n/dsPaWtrw+FwsGbNOsBg3bof0draitfr5dvf/h5jx45j2bJbuPnm29i6dQtdXV08+uivCA0NY+3a\n1dTUVBEaGsYPf/gjVq36HmvWPEh6ega1tTU88MBKnnjiqUu6DvWwiIjIEGk91cX/bC7mvf1VfV+z\nAGlJUeTlJHLr3DHYbbr59Ek1NdWsXr2WH//43/jVrx4H4Jvf/AoLFizi5Zc3cMUVs7njjuWsX/8n\ndu7cwfHjRVx55dV89rO3UFJynEcf/Q9++tNf4fV6GTUqi7vu+iKrV/+AnTs/oLm5icTERNaseZA3\n39zIe++9y3XXLWXTpjf44hf/N++99y6LFi255GtQ4BIREfEzn2GwdV8Vz24uoq2jh0yXg2tnZVJV\nf4rjJ5spqWrlr9tP0N7l5QvXjsdisZhd8qc8+1YRHxyuPe9rbDYLXq8x4O85a6KLO68Z2+/rJk26\njEOHCqmoKOdb3/o6AKdOtVFdfZKjRw9z993fBGDZsn8A4KWXNtDU1MjGja8B0NnZ0fe9pk6dDoDT\nmUxbm4cjRw5z+eWzAPqCVXNzE9/5zrf44hf/N9u2beH73//hgK/pXAYUuNatW8fevXuxWCysWrWK\nvLy8vue2bdvGI488gs1mY+7cudx7773nbPPAAw9QWFhIXFwcAF/5yleYP3/+JV+EiIhIoDIMgyde\nPcS2A9WEhdpYvnAcC2emY7P+fSSro6uHdX/czdt7KklNjGTx5ZkmVhx47PYQ7PYQZs/O5/77/8/H\nnnv66T9iGL6PfS0kxM4///P3mDw5j0+y2f5+wLZhGNhsVny+j4fE2Ng4XC4Xhw4V4vMZOJ2uS7+G\n/l6wY8cOysrKWL9+PcXFxaxatYr169f3Pb927Voef/xxkpOTWbFiBUuWLKGhoeGcbb7zne+wYMGC\nSy5cRERkOHhzZwXbDlSTnRrNvbdOISEm/FOvCQ+180+35/F//7CTZzYdIzk+krycRBOqPbc7rxnb\n72iU0xmN293ql/efMGESv/71z+no6CAsLIxHH/1PvvnN+5g06TJ27fqASZNyefHF5wkLC+Oyyybz\n7rtvM3lyHiUlx9m+fRvLl6846/edOPEydu/+gGuuWcTWrVsoLj7GF7/4v1myZCmPPPITbrrptkGp\nv98bxQUFBSxatAiAnJwcmpub8Xg8AJSXlxMbG0tqaipWq5V58+ZRUFBw3jYiIiLB4siJRta/VURM\nVCj33ZZ31rB1RmJsON/63BRsViu/eekAlW793PyolJQU7rzz89x771f52te+RGJiImFh4dxxx+c5\ncGAf9933NbZte4958xZw++3LqKws55577uYnP1nLtGkzzvl9Fy1aQnt7O/fd9zWeffbPXH/9jQDk\n58+loqKC+fMXDkr9/Y5w1dXVkZub2/c4ISEBt9uNw+HA7XaTkJDwsefKy8tpbGw8axuAp556iief\nfJLExET+5V/+5WPtRURERorG1k5+/eIBLBa455bJxEeH9dsmJy2Wr9wwid++XMijz+1j7d1XEhpi\n67fdSLZ06Wf7/nzbbXdw2213fOx5h8PBv//7I59q9+CDD3/qa88990rfn++779t9f/6Xf/m3T712\n//695OfPITo6+qLq/qQLnjRvGAOfDPfJNjfffDNxcXFMmjSJxx57jF/84hf867/+6znbxcdHYrcH\n939oQ8npHJz/qGTwqE8Cj/oksARqf3T3ePnJn/fQcqqbr94ymfwZA5+TdeO8aKqa2nn53ePsK2vi\n+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\n", "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "-QzWVNOISnwn", "colab_type": "text" }, "cell_type": "markdown", "source": [ "### Monetary" ] }, { "metadata": { "id": "V8XjN--xSnwn", "colab_type": "code", "outputId": "086e0800-8a7d-408a-a483-ef0dacd9a068", "colab": { "base_uri": "https://localhost:8080/", "height": 348 } }, "cell_type": "code", "source": [ "monetary_query = \"\"\"\n", "SELECT\n", " customer_id,\n", " SUM(order_value) AS monetary\n", "FROM\n", " `{}.{}.data_cleaned`\n", "GROUP BY\n", " customer_id\"\"\"\n", "monetary_query = monetary_query.format(PROJECT_ID, DATASET)\n", "\n", "df_monetary = pd.io.gbq.read_gbq(query=monetary_query, dialect ='standard', project_id=PROJECT_ID)\n", "print(df_monetary.head(2))\n", "df_monetary.describe()" ], "execution_count": 57, "outputs": [ { "output_type": "stream", "text": [ " customer_id monetary\n", "0 16676 1558.72\n", "1 12901 16293.10\n" ], "name": "stdout" }, { "output_type": "execute_result", "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
monetary
count1650.000000
mean3973.304164
std12993.748834
min36.560000
25%935.362500
50%1767.770000
75%3429.125000
max279489.020000
\n", "
" ], "text/plain": [ " monetary\n", "count 1650.000000\n", "mean 3973.304164\n", "std 12993.748834\n", "min 36.560000\n", "25% 935.362500\n", "50% 1767.770000\n", "75% 3429.125000\n", "max 279489.020000" ] }, "metadata": { "tags": [] }, "execution_count": 57 } ] }, { "metadata": { "id": "BpyOyOIfSnwq", "colab_type": "code", "outputId": "b7d45413-777a-495f-b2f8-3eff30a52c3c", "colab": { "base_uri": "https://localhost:8080/", "height": 337 } }, "cell_type": "code", "source": [ "fig, axs = plt.subplots(figsize=(10, 5))\n", "sns.kdeplot(df_monetary['monetary'], clip=(-1000, 15000))" ], "execution_count": 76, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "" ] }, "metadata": { "tags": [] }, "execution_count": 76 }, { "output_type": "display_data", "data": { "image/png": "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\n9u2b0nl8JmYld85mILVajV6vNx+3t7eb7/DtTU1NDbdu3aKwsBA3NzciIiIICgoCYOvWrTQ1NVnM\nS61Wo1arzXdTw8PDCCHMOwC2xMfHh927d6NQKPDz82PevHn09PQwODgIQFtbmzkPe87pZzqdzvxh\nsGPHDvz8/IDf19TPGY5lNdZm61Qq1aRqydvbe9wjPlvOLSsri0WLFpGcnGxui4uLY/bs2ahUKsLD\nw831Zc85TXYet4Ws5OJsBlq/fj3Pnz8H4P3796jVaubMmWPlXv19fX19XLx4kdu3b5t/1ZOSkkJL\nSwvwnw/YgIAAQkJCePfuHb29vRgMBurr6wkLC2P9+vVUVVUB8Pr1a9auXWu1sfxJFRUVFBcXA9DR\n0UFnZycxMTHmGnrx4gUbNmyw+5zGtLW1MXv2bJRKJUIIEhIS6O3tBf5bU+Hh4VRXV2M0Gmlra6O9\nvZ2lS5eOy2osV1u3bt26SdWSs7MzS5Ysoa6ubtx72JqKigqcnZ05duyYue3Tp0+kpaUhhGBkZIT6\n+noCAgLsOieY/DxuC1kpxEzc75O4dOkSdXV1KBQKcnJyWL58ubW79Nc9evSI69ev888//5jbYmJi\nKC0txdXVFZVKRV5eHl5eXlRVVVFcXGx+dBAVFYXJZOLMmTN8/vwZpVLJ+fPn8fX1teKI/oz+/n5O\nnTpFb28vw8PDJCcnExQUREZGBkNDQ8yfP5+8vDycnZ3tOqcxjY2NXLlyhaKiIgAqKyspKirC1dUV\nHx8fzp07h6urKyUlJTx9+hSFQsHx48eJiIjAYDBw+vRpuru7cXd3Jz8/Hzc3NyuPaOo0NjZy4cIF\nWltbcXJywsfHh0uXLpGZmTmpWmpubiY7O5vR0VFCQkLIysqy9lAnxVJOnZ2dzJo1y3wj7e/vT25u\nLvn5+dTW1uLg4MDWrVs5evSo3eQElrPSaDQUFBRMah6f6VnJxZkkSZIkSdI0Ih9rSpIkSZIkTSNy\ncSZJkiRJkjSNyMWZJEmSJEnSNCIXZ5IkSZIkSdOIXJxJkiRJkiRNI3JxJkmSJEmSNI3IxZkkSZIk\nSdI0IhdnkiRJkiRJ08i/pIg3viLLsbkAAAAASUVORK5CYII=\n", "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "N0icT7seXwu1", "colab_type": "text" }, "cell_type": "markdown", "source": [ "We can clip the monetary data at 15000" ] }, { "metadata": { "id": "Zr3JO8KXTQm-", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "" ], "execution_count": 0, "outputs": [] } ] } ================================================ FILE: notebooks/clv_automl.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from google.cloud import automl_v1beta1\n", "import os\n", "import time" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create and authenticate clients " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "keyfile_name = 'mykey.json'\n", "client = automl_v1beta1.AutoMlClient.from_service_account_file(keyfile_name)\n", "prediction_client = automl_v1beta1.PredictionServiceClient.from_service_account_file(keyfile_name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialize some variables" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "project_id = 'ml-clv'\n", "location = 'us-central1'\n", "location_path = client.location_path(project_id, location)\n", "\n", "dataset_display_name = 'clv_solution_test'\n", "model_display_name = 'clv_model_test2'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "location_path" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create AutoML Dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "create_dataset_response = client.create_dataset(\n", " location_path,\n", " {'display_name': dataset_display_name, 'tables_dataset_metadata': {}})\n", "dataset_name = create_dataset_response.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... or alternatively, use an existing Dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dataset_list_response = client.list_datasets(location_path)\n", "dataset_list = [d for d in dataset_list_response]\n", "dataset = [d for d in dataset_list if d.display_name == dataset_display_name][0]\n", "dataset_name = dataset.name" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dataset_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import data from BigQuery" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dataset_bq_input_uri = 'bq://ml-clv.clv_auto.features_n_target'\n", "input_config = {\n", " 'bigquery_source': {\n", " 'input_uri': dataset_bq_input_uri}}\n", "import_data_response = client.import_data(dataset_name, input_config)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "while import_data_response.done() is False:\n", " time.sleep(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Get column specs for Dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list_table_specs_response = client.list_table_specs(dataset_name)\n", "table_specs = [s for s in list_table_specs_response]\n", "table_spec_name = table_specs[0].name\n", "list_column_specs_response = client.list_column_specs(table_spec_name)\n", "column_specs = {s.display_name: s for s in list_column_specs_response}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example of updating column spec..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# update column spec for 'has_returned'\n", "update_column_spec_dict = {\n", " \"name\": column_specs['has_returned'].name,\n", " \"data_type\": {\n", " \"type_code\": \"CATEGORY\"\n", " }\n", "}\n", "update_column_response = client.update_column_spec(update_column_spec_dict)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Assign a training label" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "label_column_name = 'target_monetary'\n", "label_column_spec = column_specs[label_column_name]\n", "label_column_id = label_column_spec.name.rsplit('/', 1)[-1]\n", "print('Label column ID: {}'.format(label_column_id))\n", "update_dataset_dict = {\n", " 'name': dataset_name,\n", " 'tables_dataset_metadata': {\n", " 'target_column_spec_id': label_column_id\n", " }\n", "}\n", "update_dataset_response = client.update_dataset(update_dataset_dict)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Select features for training" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "feat_list = list(column_specs.keys())\n", "feat_list.remove('target_monetary')\n", "feat_list.remove('customer_id')\n", "feat_list.remove('monetary_btyd')\n", "feat_list.remove('frequency_btyd')\n", "feat_list.remove('frequency_btyd_clipped')\n", "feat_list.remove('monetary_btyd_clipped')\n", "feat_list.remove('target_monetary_clipped')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "feat_list" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Train the model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_dict = {\n", " 'display_name': model_display_name,\n", " 'dataset_id': dataset_name.rsplit('/', 1)[-1],\n", " 'tables_model_metadata': {\n", " 'target_column_spec': column_specs['target_monetary'],\n", " 'input_feature_column_specs': [\n", " column_specs[x] for x in feat_list],\n", " 'train_budget_milli_node_hours': 10000,\n", " 'optimization_objective': 'MINIMIZE_MAE'\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "create_model_response = client.create_model(location_path, model_dict)\n", "while create_model_response.done() is False:\n", " time.sleep(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "create_model_result = create_model_response.result()\n", "model_name = create_model_result.name\n", "create_model_result.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... or alternatively get an existing trained Model " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_list_response = client.list_models(location_path)\n", "model_list = [m for m in model_list_response]\n", "model = [m for m in model_list if m.display_name == model_display_name][0]\n", "model_name = model.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Get evalutions for model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_evaluations = [e for e in client.list_model_evaluations(model_name)]\n", "model_evaluations[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deploy the model" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "deploy_model_response = client.deploy_model(model_name)\n", "api = client.transport._operations_client\n", "while deploy_model_response.done is False:\n", " deploy_model_response = api.get_operation(deploy_model_response.name)\n", " time.sleep(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run batch predictions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gcs_input_uri = \"gs://ml-clv_composer_final/predictions/to_predict.csv\"\n", "gcs_output_uri_prefix = \"gs://ml-clv_composer_final/predictions\"\n", "\n", "# Define input source.\n", "batch_prediction_input_source = {\n", " 'gcs_source': {\n", " 'input_uris': [gcs_input_uri]\n", " }\n", "}\n", "\n", "# Define output target.\n", "batch_prediction_output_target = {\n", " 'gcs_destination': {\n", " 'output_uri_prefix': gcs_output_uri_prefix\n", " }\n", "}\n", "\n", "batch_predict_response = prediction_client.batch_predict(\n", " model_name, batch_prediction_input_source, batch_prediction_output_target)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "while batch_predict_response.done() is False:\n", " time.sleep(1)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.13" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: notebooks/linear_model.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from tensorflow.python.lib.io import file_io\n", "import pandas\n", "from pandas.compat import StringIO\n", "import json\n", "import math\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: sklearn in /Users/lramsey/miniconda2/envs/clv/lib/python2.7/site-packages\n", "Requirement already satisfied: scikit-learn in /Users/lramsey/miniconda2/envs/clv/lib/python2.7/site-packages (from sklearn)\n", "Requirement already satisfied: scipy>=0.13.3 in /Users/lramsey/miniconda2/envs/clv/lib/python2.7/site-packages (from scikit-learn->sklearn)\n", "Requirement already satisfied: numpy>=1.8.2 in /Users/lramsey/miniconda2/envs/clv/lib/python2.7/site-packages (from scikit-learn->sklearn)\n", "\u001b[33mYou are using pip version 9.0.1, however version 19.0.1 is available.\n", "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n" ] } ], "source": [ "!pip install sklearn" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "from sklearn.linear_model import LinearRegression" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "c_names =['customer_id', 'monetary_dnn', 'monetary_btyd', 'frequency_dnn',\n", " 'frequency_btyd', 'recency', 'T', 'time_between', 'avg_basket_value',\n", " 'avg_basket_size', 'cnt_returns', 'has_returned',\n", " 'frequency_btyd_clipped', 'monetary_btyd_clipped',\n", " 'target_monetary_clipped', 'target_monetary']" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "train_df = file_io.FileIO(\n", " '../data/train.csv',\n", " mode='r').read()\n", "train_df = pandas.read_csv(\n", " StringIO(train_df),\n", " header = None,\n", " names = c_names,\n", " delimiter=',',\n", " na_filter=True)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "eval_df = file_io.FileIO(\n", " '../data/eval.csv',\n", " mode='r').read()\n", "eval_df = pandas.read_csv(\n", " StringIO(eval_df),\n", " header = None,\n", " names = c_names,\n", " delimiter=',',\n", " na_filter=True)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "test_df = file_io.FileIO(\n", " '../data/test.csv',\n", " mode='r').read()\n", "test_df = pandas.read_csv(\n", " StringIO(test_df),\n", " header = None,\n", " names = c_names,\n", " delimiter=',',\n", " na_filter=True)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "reg = LinearRegression().fit(\n", " train_df.values[:, [1,3,5,6,7,8,9,10,11]],\n", " train_df.values[:, -1])" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1399.0002542993266" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "error = 0\n", "i = 0\n", "for p in reg.predict(eval_df.values[:, [1,3,5,6,7,8,9,10,11]]):\n", " error = error + math.pow(p - eval_df.values[i, -1], 2)\n", " i = i +1\n", "math.sqrt(error/eval_df.values.shape[0])" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "961.9763923040274" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "error = 0\n", "i = 0\n", "for p in reg.predict(test_df.values[:, [1,3,5,6,7,8,9,10,11]]):\n", " error = error + math.pow(p - test_df.values[i, -1], 2)\n", " i = i +1\n", "math.sqrt(error/test_df.values.shape[0])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.13" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: preparation/sql/common/benchmark.sql ================================================ -- Copyright 2018 Google Inc. All Rights Reserved. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. --[START benchmark] SELECT ROUND(SQRT( SUM(POW(predicted_monetary - target_monetary, 2)) / COUNT(1) ), 2) as rmse FROM ( SELECT tf.customer_id, avg_basket_value * ( cnt_orders * (1 + target_days/feature_days) ) AS predicted_monetary, ROUND(tt.target_monetary, 2) AS target_monetary --[END benchmark] FROM ( -- This SELECT takes records that are used for features later SELECT customer_id, AVG(order_value) avg_basket_value, COUNT(DISTINCT order_date) AS cnt_orders FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_cleaned` WHERE order_date <= DATE('{{ dag_run.conf['threshold_date'] }}') GROUP BY customer_id) tf, ( -- This SELECT takes records that are used for target later SELECT customer_id, SUM(order_value) target_monetary FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_cleaned` --WHERE order_date > '2013-01-31' GROUP BY customer_id) tt, ( SELECT DATE_DIFF(DATE('{{ dag_run.conf['threshold_date'] }}'), MIN(order_date), DAY) feature_days, DATE_DIFF(DATE('{{ dag_run.conf['predict_end'] }}'), DATE('{{ dag_run.conf['threshold_date'] }}'), DAY) target_days FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_cleaned` ) AS days WHERE tf.customer_id = tt.customer_id ) ================================================ FILE: preparation/sql/common/clean.sql ================================================ SELECT customer_id, order_date, order_value, order_qty_articles FROM ( SELECT CustomerID AS customer_id, PARSE_DATE("%m/%d/%y", SUBSTR(InvoiceDate, 0, 8)) AS order_date, ROUND(SUM(UnitPrice * Quantity), 2) AS order_value, SUM(Quantity) AS order_qty_articles, ( SELECT MAX(PARSE_DATE("%m/%d/%y", SUBSTR(InvoiceDate, 0, 8))) FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_source` tl WHERE tl.CustomerID = t.CustomerID ) latest_order FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_source` t GROUP BY CustomerID, order_date ) a INNER JOIN ( -- Only customers with more than one positive order values before threshold. SELECT CustomerID FROM ( -- Customers and how many positive order values before threshold. SELECT CustomerID, SUM(positive_value) cnt_positive_value FROM ( -- Customer with whether order was positive or not at each date. SELECT CustomerID, ( CASE WHEN SUM(UnitPrice * Quantity) > 0 THEN 1 ELSE 0 END ) positive_value FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_source` WHERE PARSE_DATE("%m/%d/%y", SUBSTR(InvoiceDate, 0, 8)) < DATE(`{{ dag_run.conf['threshold_date'] }}`) GROUP BY CustomerID, SUBSTR(InvoiceDate, 0, 8) ) GROUP BY CustomerID ) WHERE cnt_positive_value > 1 ) b ON a.customer_id = b. CustomerID --[START common_clean] WHERE -- Bought in the past 3 months DATE_DIFF(DATE(`{{ dag_run.conf['predict_end'] }}`), latest_order, DAY) <= 90 -- Make sure returns are consistent. AND ( (order_qty_articles > 0 and order_Value > 0) OR (order_qty_articles < 0 and order_Value < 0) ) --[END common_clean] ================================================ FILE: preparation/sql/common/features_n_target.sql ================================================ -- Copyright 2018 Google Inc. All Rights Reserved. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- Keep all records before a threshold date for Features -- And all records before a threshold date for Target -- Threshold taken at {{ dag_run.conf['threshold_date'] }} ex: 2013-01-31 -- {{ dag_run.conf['threshold_date'] }} is understood by Airflow SELECT tf.customer_id, -- For training period -- Copying the calculations from Lifetimes where first orders are ignored -- See https://github.com/CamDavidsonPilon/lifetimes/blob/master/lifetimes/utils.py#L246 --[START features_target] tf.monetary_dnn, tf.monetary_btyd, tf.cnt_orders AS frequency_dnn, tf.cnt_orders - 1 AS frequency_btyd, tf.recency, tf.T, ROUND(tf.recency/cnt_orders, 2) AS time_between, ROUND(tf.avg_basket_value, 2) AS avg_basket_value, ROUND(tf.avg_basket_size, 2) AS avg_basket_size, tf.cnt_returns, (CASE WHEN tf.cnt_returns > 0 THEN 1 ELSE 0 END) AS has_returned, -- Used by BTYD mainly, potentially DNN if clipped improve results (CASE WHEN tf.cnt_orders - 1 > 600 THEN 600 ELSE tf.cnt_orders - 1 END) AS frequency_btyd_clipped, (CASE WHEN tf.monetary_btyd > 100000 THEN 100000 ELSE ROUND(tf.monetary_btyd, 2) END) AS monetary_btyd_clipped, (CASE WHEN tt.target_monetary > 100000 THEN 100000 ELSE ROUND(tt.target_monetary, 2) END) AS target_monetary_clipped, -- Target calculated for overall period ROUND(tt.target_monetary, 2) as target_monetary --[END features_target] FROM -- This SELECT uses only data before threshold to make features. ( SELECT customer_id, SUM(order_value) AS monetary_dnn, (CASE WHEN COUNT(DISTINCT order_date) = 1 THEN 0 ELSE SUM(order_value_btyd) / (COUNT(DISTINCT order_date) -1) END) AS monetary_btyd, DATE_DIFF(MAX(order_date), MIN(order_date), DAY) AS recency, DATE_DIFF(DATE('{{ dag_run.conf['threshold_date'] }}'), MIN(order_date), DAY) AS T, COUNT(DISTINCT order_date) AS cnt_orders, AVG(order_qty_articles) avg_basket_size, AVG(order_value) avg_basket_value, SUM(CASE WHEN order_value < 1 THEN 1 ELSE 0 END) AS cnt_returns FROM -- Makes the order value = 0 if it is the first one ( SELECT a.*, (CASE WHEN a.order_date = c.order_date_min THEN 0 ELSE a.order_value END) AS order_value_btyd --[START airflow_params] FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_cleaned` a --[END airflow_params] INNER JOIN ( SELECT customer_id, MIN(order_date) AS order_date_min FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_cleaned` GROUP BY customer_id) c ON c.customer_id = a.customer_id ) WHERE --[START threshold_date] order_date <= DATE('{{ dag_run.conf['threshold_date'] }}') --[END threshold_date] GROUP BY customer_id) tf, -- This SELECT uses all records to calculate the target (could also use data after threshold ) ( SELECT customer_id, SUM(order_value) target_monetary FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.data_cleaned` --WHERE order_date > DATE('{{ dag_run.conf['threshold_date'] }}') GROUP BY customer_id) tt WHERE tf.customer_id = tt.customer_id AND tf.monetary_dnn > 0 AND tf.monetary_dnn <= {{ dag_run.conf['max_monetary'] }} AND tf.monetary_btyd > 0 ================================================ FILE: preparation/sql/dnn/split_eval.sql ================================================ -- Copyright 2018 Google Inc. All Rights Reserved. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. SELECT * FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.features_n_target` WHERE -- TRAIN MOD(ABS(FARM_FINGERPRINT(CAST(customer_id AS STRING))), 100000) > 70000 AND MOD(ABS(FARM_FINGERPRINT(CAST(customer_id AS STRING))), 100000) < 85000 ================================================ FILE: preparation/sql/dnn/split_test.sql ================================================ -- Copyright 2018 Google Inc. All Rights Reserved. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. SELECT * FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.features_n_target` WHERE -- TRAIN MOD(ABS(FARM_FINGERPRINT(CAST(customer_id AS STRING))), 100000) > 85000 AND MOD(ABS(FARM_FINGERPRINT(CAST(customer_id AS STRING))), 100000) < 100000 ================================================ FILE: preparation/sql/dnn/split_train.sql ================================================ -- Copyright 2018 Google Inc. All Rights Reserved. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- Save to table 'dnn_train' --[START split_train] SELECT * FROM `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.features_n_target` WHERE -- TRAIN MOD(ABS(FARM_FINGERPRINT(CAST(customer_id AS STRING))), 100000) <= 70000 --[END split_train] ================================================ FILE: requirements.txt ================================================ google-cloud-automl>=0.1.2 tensorflow==1.13.1 jupyter apache-beam==2.2.0 ipdb lifetimes==0.9.0.0 google-api-python-client==1.7.3 google-cloud-bigquery==0.32.0 seaborn==0.8.1 pandas-gbq==0.5.0 matplotlib==2.2.3 ================================================ FILE: run/airflow/dags/01_build_train_deploy.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime, json, re, logging from airflow import models from airflow.operators.python_operator import PythonOperator, BranchPythonOperator from airflow.hooks.base_hook import BaseHook from airflow.contrib.operators import bigquery_operator from airflow.contrib.operators import bigquery_get_data from airflow.contrib.operators import gcs_to_bq from airflow.contrib.operators import bigquery_to_gcs from airflow.contrib.operators import mlengine_operator from airflow.contrib.operators import mlengine_operator_utils from airflow.contrib.hooks.gcp_mlengine_hook import MLEngineHook from airflow.contrib.hooks.gcs_hook import GoogleCloudStorageHook from airflow.operators import bash_operator from airflow.operators.dummy_operator import DummyOperator from airflow.utils import trigger_rule from google.cloud.automl_v1beta1 import AutoMlClient from clv_automl import clv_automl def _get_project_id(): """Get project ID from default GCP connection.""" extras = BaseHook.get_connection('google_cloud_default').extra_dejson key = 'extra__google_cloud_platform__project' if key in extras: project_id = extras[key] else: raise ('Must configure project_id in google_cloud_default ' 'connection from Airflow Console') return project_id PROJECT = _get_project_id() REGION = models.Variable.get('region') DATASET = models.Variable.get('dataset') COMPOSER_BUCKET_NAME = models.Variable.get('composer_bucket_name') GCS_SQL = 'sql' DB_DUMP_FILENAME = 'db_dump.csv' LOCATION_TRAINING_DATA = '{}/data'.format(COMPOSER_BUCKET_NAME) PREFIX_JOBS_EXPORT = 'jobs/clv-composer' PREFIX_FINAL_MODEL = '{}/final'.format(PREFIX_JOBS_EXPORT) MODEL_PACKAGE_NAME = 'clv_ml_engine-0.1.tar.gz' # Matches name in setup.py AUTOML_DATASET = models.Variable.get('automl_dataset') AUTOML_MODEL = models.Variable.get('automl_model') AUTOML_TRAINING_BUDGET = int(models.Variable.get('automl_training_budget')) #[START dag_build_train_deploy] default_dag_args = { 'start_date': datetime.datetime(2050, 1, 1), 'schedule_interval': None, 'provide_context': True } dag = models.DAG( 'build_train_deploy', default_args = default_dag_args) #[END dag_build_train_deploy] # Loads the database dump from Cloud Storage to BigQuery t1 = gcs_to_bq.GoogleCloudStorageToBigQueryOperator( task_id="db_dump_to_bigquery", bucket=COMPOSER_BUCKET_NAME, source_objects=[DB_DUMP_FILENAME], schema_object="schema_source.json", source_format="CSV", skip_leading_rows=1, destination_project_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'data_source'), create_disposition="CREATE_IF_NEEDED", write_disposition="WRITE_TRUNCATE", dag=dag ) # Clean the data from BigQuery to BigQuery t2 = bigquery_operator.BigQueryOperator( task_id='bq_from_source_to_clean', bql='{}/common/clean.sql'.format(GCS_SQL), use_legacy_sql=False, allow_large_results=True, destination_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'data_cleaned'), create_disposition="CREATE_IF_NEEDED", write_disposition="WRITE_TRUNCATE", dag=dag ) # Creates split between features and targets and also aggregates both sides. # The threshold date is passed as an arg when calling the Airflow job and # dynamically understood within the .sql file. # We should pass query_params but we run into various problems: # - if using BigQueryOperator, we can not pass dag_run.conf['threshold_date'] # - if using hooks, run_query does not accept a .sql file and needs the string # So the way is to add directly {{ dag_run.conf['threshold_date'] }} into the # .sql file which Airflow can ping up when running the operator. t3 = bigquery_operator.BigQueryOperator( task_id='bq_from_clean_to_features', bql='{}/common/features_n_target.sql'.format(GCS_SQL), use_legacy_sql=False, allow_large_results=True, destination_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'features_n_target'), create_disposition="CREATE_IF_NEEDED", write_disposition="WRITE_TRUNCATE", dag=dag ) def get_model_type(**kwargs): model_type = kwargs['dag_run'].conf.get('model_type') if model_type == 'automl': model_train_task = 'train_automl' else: model_train_task = 'train_ml_engine' return model_train_task t4_train_cond = BranchPythonOperator(task_id='train_branch', dag=dag, python_callable=get_model_type) # # Train the model using AutoML # def do_train_automl(**kwargs): """ Create, train and deploy automl model. """ # instantiate automl client automl_client = AutoMlClient() model_name = clv_automl.create_automl_model(automl_client, PROJECT, REGION, DATASET, 'features_n_target', AUTOML_DATASET, AUTOML_MODEL, AUTOML_TRAINING_BUDGET) clv_automl.deploy_model(automl_client, model_name) t4_automl = PythonOperator( task_id='train_automl', dag=dag, python_callable=do_train_automl) t4_ml_engine = DummyOperator(task_id='train_ml_engine', dag=dag) # Split the data into a training set and evaluation set within BigQuery t4a = bigquery_operator.BigQueryOperator( task_id='bq_dnn_train', bql='{}/dnn/split_train.sql'.format(GCS_SQL), use_legacy_sql=False, allow_large_results=True, destination_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'dnn_train'), create_disposition="CREATE_IF_NEEDED", write_disposition="WRITE_TRUNCATE", dag=dag ) t4b = bigquery_operator.BigQueryOperator( task_id='bq_dnn_eval', bql='{}/dnn/split_eval.sql'.format(GCS_SQL), use_legacy_sql=False, allow_large_results=True, destination_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'dnn_eval'), create_disposition="CREATE_IF_NEEDED", write_disposition="WRITE_TRUNCATE", dag=dag ) t4c = bigquery_operator.BigQueryOperator( task_id='bq_dnn_test', bql='{}/dnn/split_test.sql'.format(GCS_SQL), use_legacy_sql=False, allow_large_results=True, destination_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'dnn_test'), create_disposition="CREATE_IF_NEEDED", write_disposition="WRITE_TRUNCATE", dag=dag ) # TODO: Currently all data steps are done whether BTYD or DNN are used. It would # be better to have a condition to call only one task or the other using 'model_type' data_btyd_location = ['gs://{}/{}'.format(LOCATION_TRAINING_DATA, 'btyd.csv')] data_train_locations = ['gs://{}/{}'.format(LOCATION_TRAINING_DATA, 'train.csv')] data_eval_locations = ['gs://{}/{}'.format(LOCATION_TRAINING_DATA, 'eval.csv')] data_test_locations = ['gs://{}/{}'.format(LOCATION_TRAINING_DATA, 'test.csv')] t5a = bigquery_to_gcs.BigQueryToCloudStorageOperator( task_id='bq_dnn_train_to_gcs', source_project_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'dnn_train'), destination_cloud_storage_uris=data_train_locations, print_header=False, dag=dag ) t5b = bigquery_to_gcs.BigQueryToCloudStorageOperator( task_id='bq_dnn_eval_to_gcs', source_project_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'dnn_eval'), destination_cloud_storage_uris=data_eval_locations, print_header=False, dag=dag ) t5c = bigquery_to_gcs.BigQueryToCloudStorageOperator( task_id='bq_dnn_test_to_gcs', source_project_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'dnn_test'), destination_cloud_storage_uris=data_test_locations, print_header=False, dag=dag ) t5d = bigquery_to_gcs.BigQueryToCloudStorageOperator( task_id='bq_btyd_to_gcs', source_project_dataset_table="{}.{}.{}".format(PROJECT, DATASET, 'features_n_target'), destination_cloud_storage_uris=data_btyd_location, print_header=True, dag=dag ) # # Train the model using ML Engine (TensorFlow DNN or Lifetimes BTYD) # def do_train_ml_engine(**kwargs): """ """ job_id = 'clv-{}'.format(datetime.datetime.now().strftime('%Y%m%d%H%M')) mlengine_operator.MLEngineTrainingOperator( task_id='train_ml_engine_job', project_id=PROJECT, job_id=job_id, package_uris=['gs://{}/code/{}'.format(COMPOSER_BUCKET_NAME, MODEL_PACKAGE_NAME)], training_python_module='trainer.task', region=REGION, training_args=['--job-dir', 'gs://{}/{}/{}'.format(COMPOSER_BUCKET_NAME, PREFIX_JOBS_EXPORT, job_id), '--data-src', 'gs://{}'.format(LOCATION_TRAINING_DATA), '--model_type', kwargs['dag_run'].conf.get('model_type')], dag=dag ).execute(kwargs) t6 = PythonOperator( task_id='train_ml_engine_task', dag=dag, python_callable=do_train_ml_engine) # # Copies the latest model to a consistent 'final' bucket # def do_copy_model_to_final(**kwargs): gcs = GoogleCloudStorageHook() # Returns all the objects within the bucket. All sub-buckets are considered # as prefix of the leaves. List does not differentiate files from subbuckets all_jobs_files = gcs.list( bucket=COMPOSER_BUCKET_NAME, prefix='{}/export/estimate'.format(PREFIX_JOBS_EXPORT) ) # Extract the latest model bucket parent of variables/ and saved_model.pbtxt # The max() string contains the latest model folders in 1234567, we need to # extract that using regex # ex: jobs/clv-composer/export/estimate/1234567890/variables/variables.index # returns /1234567890/ latest_model_bucket = re.findall(r'/\d+/', max(all_jobs_files))[0] # List all the files that needs to be copied (only files in the latest bucket # and skip the ones that are not files but sub buckets) for c in [f for f in all_jobs_files if latest_model_bucket in f and f[-1] != '/']: # The model used for training is saved into a 'final' sub bucket of the # export bucket. dest_object = c.split(latest_model_bucket)[1] dest_object = '{}/{}'.format(PREFIX_FINAL_MODEL, dest_object) logging.info("Copying {} to {} ...".format(dest_object, COMPOSER_BUCKET_NAME)) gcs.copy( source_bucket=COMPOSER_BUCKET_NAME, source_object=c, destination_object=dest_object ) # Note that this could be done as well in Tensorflow using tf.gFile aftet the # model is created but for reasons of flexibility, it was decided to do this in the # wider workflow. This way, it is also possible pick other models. t7 = PythonOperator( task_id='copy_model_to_final', python_callable=do_copy_model_to_final, dag=dag) # # Model Creation # def do_check_model(**kwargs): """ Check if a model with the name exists using Hooks instead of operators. Uses xcom_push to pass it to the next step. Could use return too if no key. """ # pushes an XCom without a specific target, just by returning it mle = MLEngineHook() model_name = kwargs['dag_run'].conf.get('model_name') # return bool(mle.get_model(PROJECT, MODEL_DNN_NAME)) project = mle.get_model(PROJECT, model_name) kwargs['ti'].xcom_push(key='is_project', value=bool(project)) def do_create_model(**kwargs): """ Creates a model only if one with the same name did not exist. It leverages the check from the previous task pushed using xcom. """ model_params = { 'name': kwargs['dag_run'].conf.get('model_name'), 'description': 'A custom DNN regressor model', 'regions': [REGION] } ti = kwargs['ti'] is_model = ti.xcom_pull(key='is_project', task_ids='check_model') if not is_model: mle = MLEngineHook() mle.create_model(PROJECT, model_params) # Checks if model exists using Hook instead of GCP operators due to conditional. t8 = PythonOperator( task_id='check_model', dag=dag, python_callable=do_check_model) # Creates model if it does not exist using Hook instead of GCP operators t9 = PythonOperator( task_id='create_model', dag=dag, python_callable=do_create_model) # # Version Creation # def do_list_versions(**kwargs): """ Check if a version with the name exists using Hooks instead of operators. Uses xcom_push to pass it to the next step. Could use return too if no key. """ mle = MLEngineHook() model_name = kwargs['dag_run'].conf.get('model_name') model_versions = mle.list_versions(PROJECT, model_name) kwargs['ti'].xcom_push(key='model_versions', value=model_versions) def do_create_version(**kwargs): """ Creates a new version or overwrite if existing one. It leverages the check from the previous task pushed using xcom. """ version_params = { "name": kwargs['dag_run'].conf.get('model_version'), "description": 'Version 1', "runtimeVersion": kwargs['dag_run'].conf.get('tf_version'), "deploymentUri": 'gs://{}/{}'.format(COMPOSER_BUCKET_NAME, PREFIX_FINAL_MODEL) } ti = kwargs['ti'] mle = MLEngineHook() model_name = kwargs['dag_run'].conf.get('model_name') model_versions = ti.xcom_pull(key='model_versions', task_ids='list_versions') version_path = 'projects/{}/models/{}/versions/{}'.format(PROJECT, model_name, version_params['name']) if version_path in [v['name'] for v in model_versions]: logging.info("Delete previously version of the model to overwrite.") mle.delete_version(PROJECT, model_name, version_params['name']) mle.create_version(PROJECT, model_name, version_params) # Checks if model exists using Hook instead of GCP operators due to conditional. t10 = PythonOperator( task_id='list_versions', dag=dag, python_callable=do_list_versions) # Creates model if it does not exist using Hook instead of GCP operators t11 = PythonOperator( task_id='create_version', dag=dag, python_callable=do_create_version) # Create task graph t1.set_downstream(t2) t2.set_downstream(t3) t3.set_downstream(t4_train_cond) t4_train_cond.set_downstream([t4_ml_engine, t4_automl]) t4_ml_engine.set_downstream([t4a, t4b, t4c]) t4_ml_engine.set_downstream(t5d) t4a.set_downstream(t5a) t4b.set_downstream(t5b) t4c.set_downstream(t5c) t6.set_upstream([t5a, t5b, t5c, t5d]) t6.set_downstream(t7) t7.set_downstream(t8) t9.set_upstream(t8) t9.set_downstream(t10) t10.set_downstream(t11) ================================================ FILE: run/airflow/dags/02_predict_serve.py ================================================ # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime, json, logging from airflow import models from airflow.operators.python_operator import PythonOperator, BranchPythonOperator from airflow.hooks.base_hook import BaseHook from airflow.contrib.operators import mlengine_operator from airflow.contrib.operators import mlengine_operator_utils from airflow.contrib.operators import dataflow_operator from airflow.contrib.operators import gcs_to_bq # TODO Add when Composer on v2.0 and more Hook # from airflow.contrib.operators import gcs_list_operator from airflow.contrib.hooks.gcs_hook import GoogleCloudStorageHook from airflow.utils import trigger_rule from google.cloud.automl_v1beta1 import AutoMlClient, PredictionServiceClient from clv_automl import clv_automl def _get_project_id(): """Get project ID from default GCP connection.""" extras = BaseHook.get_connection('google_cloud_default').extra_dejson key = 'extra__google_cloud_platform__project' if key in extras: project_id = extras[key] else: raise ('Must configure project_id in google_cloud_default ' 'connection from Airflow Console') return project_id PROJECT = _get_project_id() REGION = models.Variable.get('region') DF_ZONE = models.Variable.get('df_zone') DF_TEMP = models.Variable.get('df_temp_location') COMPOSER_BUCKET_NAME = models.Variable.get('composer_bucket_name') #[START dag_predict_serve] default_dag_args = { 'start_date': datetime.datetime(2050, 1, 1), 'schedule_interval': None, 'provide_context': True, 'dataflow_default_options': { 'project': PROJECT, 'zone': DF_ZONE, 'tempLocation': DF_TEMP } } dag = models.DAG( 'predict_serve', default_args = default_dag_args) #[END dag_predict_serve] # # Runs prediction. # def get_model_type(**kwargs): model_type = kwargs['dag_run'].conf.get('model_type') if model_type == 'automl': model_train_task = 'predict_automl' else: model_train_task = 'predict_ml_engine' return model_train_task t0_predict_cond = BranchPythonOperator(task_id='predict_branch', dag=dag, python_callable=get_model_type) def do_predict_mle(**kwargs): """ Runs a batch prediction on new data and saving the results as CSV into output_path. """ job_id = 'clv-{}'.format(datetime.datetime.now().strftime('%Y%m%d%H%M')) gcs_prediction_input = 'gs://{}/predictions/to_predict.csv'.format(COMPOSER_BUCKET_NAME) gcs_prediction_output = 'gs://{}/predictions/output'.format(COMPOSER_BUCKET_NAME) model_name = kwargs['dag_run'].conf.get('model_name') model_version = kwargs['dag_run'].conf.get('model_version') logging.info("Running prediction using {}:{}...".format(model_name, model_version)) mlengine_operator.MLEngineBatchPredictionOperator( task_id='predict_dnn', project_id=PROJECT, job_id=job_id, region=REGION, data_format='TEXT', input_paths=gcs_prediction_input, output_path=gcs_prediction_output, model_name=model_name, version_name=model_version, #uri=gs://WHERE_MODEL_IS_IF_NOT_ML_ENGINE #runtime_version=TF_VERSION, dag=dag ).execute(kwargs) def do_predict_automl(**kwargs): # get automl clients automl_client = AutoMlClient() automl_predict_client = PredictionServiceClient() # get model resource name automl_model = models.Variable.get('automl_model') location_path = automl_client.location_path(PROJECT, REGION) model_list_response = automl_client.list_models(location_path) model_list = [m for m in model_list_response] model = [m for m in model_list if m.display_name == automl_model][0] # run batch prediction gcs_prediction_input = 'gs://{}/predictions/to_predict.csv'.format(COMPOSER_BUCKET_NAME) gcs_prediction_output = 'gs://{}/predictions/output'.format(COMPOSER_BUCKET_NAME) clv_automl.do_batch_prediction(automl_predict_client, model.name, gcs_prediction_input, gcs_prediction_output) t1a = PythonOperator( task_id='predict_ml_engine', dag=dag, python_callable=do_predict_mle) t1b = PythonOperator( task_id='predict_automl', dag=dag, python_callable=do_predict_automl) # # Load the predictions from GCS to Datastore. # def do_load_to_datastore(**kwargs): """ Saves the predictions results into Datastore. Because there is no way to directly load a CSV to Datastore, we use Apache Beam on Dataflow with templates gs://dataflow-templates/latest/GCS_Text_to_Datastore. https://cloud.google.com/dataflow/docs/templates/provided-templates#gcstexttodatastore """ gcs_prediction_output = 'gs://{}/predictions/output'.format(COMPOSER_BUCKET_NAME) template = 'gs://dataflow-templates/latest/GCS_Text_to_Datastore' df_template_params = { 'textReadPattern': '{}/prediction.results*'.format(gcs_prediction_output), 'javascriptTextTransformGcsPath': 'gs://{}/gcs_datastore_transform.js'.format(COMPOSER_BUCKET_NAME), 'javascriptTextTransformFunctionName': 'from_prediction_output_to_datastore_object', 'datastoreWriteProjectId': PROJECT, 'errorWritePath': 'gs://{}/errors/serving_load'.format(COMPOSER_BUCKET_NAME) } dataflow_operator.DataflowTemplateOperator( task_id='gcs_predictions_df_transform', project_id=PROJECT, template=template, parameters=df_template_params, dag=dag ).execute(kwargs) t2 = PythonOperator( task_id='load_to_datastore', dag=dag, python_callable=do_load_to_datastore) # # Loads the database dump from Cloud Storage to BigQuery # def do_list_predictions_files(**kwargs): """ Retrieves all the predictions files that should be loaded to BigQuery. Can not do a GoogleCloudStorageToBigQueryOperator directly due to the possible multiple files. """ # List all relevant files # TODO Add when Composer is on Airflow 2.0 # predictions_files = gcs_list_operator.GoogleCloudStorageListOperator( # task_id='predictions_files', # bucket=COMPOSER_BUCKET_NAME, # prefix='predictions/output/prediction.results-' # ) # TODO Remove when Composer on Airflow 2.0 gcs = GoogleCloudStorageHook() predictions_files = gcs.list( bucket=COMPOSER_BUCKET_NAME, prefix='predictions/output/prediction.results-' ) logging.info("Predictions files are: {}".format(predictions_files)) # Create a variable that can be used in the next task kwargs['ti'].xcom_push(key='predictions_files', value=predictions_files) def do_load_to_bq(**kwargs): """ Loads the prediction files to BigQuery using the list output from do_list_predictions_files. """ dataset = kwargs['dag_run'].conf.get('dataset') # Reads files from the variables saved in the previous task ti = kwargs['ti'] predictions_files = ti.xcom_pull(key='predictions_files', task_ids='list_predictions_files') gcs_to_bq.GoogleCloudStorageToBigQueryOperator( task_id="load_gcs_predictions_to_bigquery", bucket=COMPOSER_BUCKET_NAME, source_objects=predictions_files, schema_fields=[{ 'name':'customer_id', 'type':'STRING' },{ 'name':'predicted_monetary', 'type':'FLOAT' },{ 'name':'predictions', 'type':'FLOAT' }], source_format="NEWLINE_DELIMITED_JSON", skip_leading_rows=1, destination_project_dataset_table="{}.{}.{}".format(PROJECT, dataset, 'predictions'), create_disposition="CREATE_IF_NEEDED", write_disposition="WRITE_TRUNCATE", dag=dag ).execute(kwargs) t3 = PythonOperator( task_id='list_predictions_files', dag=dag, python_callable=do_list_predictions_files) t4 = PythonOperator( task_id='load_to_bq', dag=dag, python_callable=do_load_to_bq) # How to link them t0_predict_cond.set_downstream([t1a, t1b]) t2.set_upstream([t1a, t1b]) t3.set_upstream([t1a, t1b]) t3.set_downstream(t4) ================================================ FILE: run/airflow/gcs_datastore_transform.js ================================================ // Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // To be copied to GCS so it can be uses with Dataflow template function from_prediction_output_to_datastore_object(prediction_row, entity){ /* * prediction_row looks like what is given by the export function in model.py * {"properties": 427.7606201171875, "key": ["'abc'"]} * entity should match https://cloud.google.com/datastore/docs/reference/data/rest/v1/Entity */ //[START row_to_ds] var prediction_object = JSON.parse(prediction_row); to_write = { key: { path: [{ //id: prediction_object.key, kind: 'clv', name: prediction_object.customer_id }] }, properties: { predicted_monetary: {doubleValue: prediction_object.predicted_monetary} } }; return JSON.stringify(to_write); //[END row_to_ds] } ================================================ FILE: run/airflow/requirements.txt ================================================ google-cloud-automl>=0.2.0 ================================================ FILE: run/airflow/schema_source.json ================================================ [ { "mode": "NULLABLE", "name": "InvoiceNo", "type": "STRING" }, { "mode": "NULLABLE", "name": "StockCode", "type": "STRING" }, { "mode": "NULLABLE", "name": "Description", "type": "STRING" }, { "mode": "NULLABLE", "name": "Quantity", "type": "INTEGER" }, { "mode": "NULLABLE", "name": "InvoiceDate", "type": "STRING" }, { "mode": "NULLABLE", "name": "UnitPrice", "type": "FLOAT" }, { "mode": "NULLABLE", "name": "CustomerID", "type": "STRING" }, { "mode": "NULLABLE", "name": "Country", "type": "STRING" } ] ================================================ FILE: run/mltrain.sh ================================================ #!/bin/bash # Copyright 2018 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. usage () { echo "usage: run/mltrain.sh [local | train | tune] [gs://]data_folder_or_bucket [args] Use 'local' to train locally with a local data folder, and 'train' and 'tune' to run on ML Engine. For ML Engine jobs you must supply a bucket on GCS. The job data folder will be gs://bucket/data and the job directory will be gs://bucket/jobs. So your data files must already be in gs://bucket/data. For DNN models the data should be named 'train.csv', 'eval.csv' and 'test.csv, for probablistic models the file must be 'btyd.csv'. For probabilistic models, specify '--model_type paretonbd_model' and include --threshold_date and --predict_end args. Examples: # train locally run/mltrain.sh local data # train on ML Engine run/mltrain.sh train gs://your_bucket # tune hyperparams on ML Engine: run/mltrain.sh tune gs://your_bucket # train using btyd models run/mltrain.sh local data --model_type paretonbd_model --threshold_date 2011-08-08 --predict_end 2011-12-12 " } date TIME=`date +"%Y%m%d_%H%M%S"` if [[ $# == 0 || $# == 1 ]]; then usage exit 1 fi # set job vars TRAIN_JOB="$1" DATA_DIR="$2" BUCKET="$2" JOB_NAME=clv_${TRAIN_JOB}_${TIME} REGION=us-central1 # queue additional args shift; shift if [[ ${TRAIN_JOB} == "local" ]]; then ARGS="--data-src ${DATA_DIR} --verbose-logging $@" mkdir -p jobs/${JOB_NAME} gcloud ml-engine local train \ --job-dir jobs/${JOB_NAME} \ --module-name clv_mle.trainer.task \ --package-path trainer \ -- \ ${ARGS} elif [[ ${TRAIN_JOB} == "train" ]]; then ARGS="--data-src ${BUCKET}/data --verbose-logging $@" gcloud beta ml-engine jobs submit training ${JOB_NAME} \ --job-dir ${BUCKET}/jobs/${JOB_NAME} \ --region $REGION \ --scale-tier=CUSTOM \ --module-name trainer.task \ --package-path clv_mle/trainer \ --config clv_mle/config.yaml \ --runtime-version 1.10 \ -- \ ${ARGS} elif [[ $TRAIN_JOB == "tune" ]]; then ARGS="--data-src ${BUCKET}/data --verbose-logging $@" # set configuration for tuning CONFIG_TUNE="clv_mle/config_tune.json" gcloud beta ml-engine jobs submit training ${JOB_NAME} \ --job-dir ${BUCKET}/jobs/${JOB_NAME} \ --region ${REGION} \ --scale-tier=CUSTOM \ --module-name trainer.task \ --package-path clv_mle/trainer \ --config ${CONFIG_TUNE} \ --runtime-version 1.10 \ -- \ --hypertune \ ${ARGS} else usage fi date