Showing preview only (350K chars total). Download the full file or copy to clipboard to get everything.
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": [
"<IPython.core.display.Javascript object>"
]
},
"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": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>day_number</th>\n",
" <th>Quantity</th>\n",
" <th>UnitPrice</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>count</th>\n",
" <td>7974.000000</td>\n",
" <td>7974.000000</td>\n",
" <td>7974.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>mean</th>\n",
" <td>221.630298</td>\n",
" <td>12.244043</td>\n",
" <td>3.088569</td>\n",
" </tr>\n",
" <tr>\n",
" <th>std</th>\n",
" <td>112.506385</td>\n",
" <td>43.805342</td>\n",
" <td>7.036266</td>\n",
" </tr>\n",
" <tr>\n",
" <th>min</th>\n",
" <td>0.000000</td>\n",
" <td>-144.000000</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25%</th>\n",
" <td>130.000000</td>\n",
" <td>2.000000</td>\n",
" <td>1.250000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>50%</th>\n",
" <td>240.000000</td>\n",
" <td>5.000000</td>\n",
" <td>1.950000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>75%</th>\n",
" <td>323.000000</td>\n",
" <td>12.000000</td>\n",
" <td>3.750000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>max</th>\n",
" <td>373.000000</td>\n",
" <td>1500.000000</td>\n",
" <td>295.000000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"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": [
"<matplotlib.axes._subplots.AxesSubplot at 0x7f687124c550>"
]
},
"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": [
"<matplotlib.figure.Figure at 0x7f687067fd68>"
]
},
"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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\nV7vt7e1uM
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
SYMBOL INDEX (58 symbols across 8 files)
FILE: clv_automl/clv_automl.py
function create_automl_model (line 35) | def create_automl_model(client,
function deploy_model (line 130) | def deploy_model(client, model_name):
function get_model_evaluation (line 143) | def get_model_evaluation(client, model_name):
function do_batch_prediction (line 154) | def do_batch_prediction(prediction_client,
function create_parser (line 186) | def create_parser():
function main (line 241) | def main(argv=None):
FILE: clv_mle/trainer/btyd.py
function load_data (line 37) | def load_data(datapath):
function bgnbd_model (line 75) | def bgnbd_model(summary):
function paretonbd_model (line 88) | def paretonbd_model(summary):
function run_btyd (line 102) | def run_btyd(model_type, data_src, threshold_date, predict_end):
function predict_value (line 159) | def predict_value(summary, actual_df, fitter, ggf, time_days, time_months):
FILE: clv_mle/trainer/context.py
class CLVFeatures (line 20) | class CLVFeatures(object):
method __init__ (line 64) | def __init__(self, ignore_crosses=False, is_dnn=None):
method _keep_used (line 87) | def _keep_used(self):
method get_key (line 105) | def get_key(self):
method get_used_headers (line 108) | def get_used_headers(self, with_key=False, with_target=False):
method get_defaults (line 129) | def get_defaults(self, headers_names=None, with_key=False):
method get_all_names (line 146) | def get_all_names(self):
method get_all_defaults (line 149) | def get_all_defaults(self):
method get_unused (line 152) | def get_unused(self):
method get_target_name (line 155) | def get_target_name(self):
method _make_base_features (line 168) | def _make_base_features(self):
method get_base_features (line 188) | def get_base_features(self):
method _prepare_for_crossing (line 192) | def _prepare_for_crossing(self, key_name, num_bck, boundaries):
method _make_crossed (line 223) | def _make_crossed(self):
method get_wide_features (line 248) | def get_wide_features(self):
method get_deep_features (line 264) | def get_deep_features(self, with_continuous=True):
FILE: clv_mle/trainer/model.py
function parse_csv (line 41) | def parse_csv(csv_row):
function dataset_input_fn (line 66) | def dataset_input_fn(data_folder, prefix=None, mode=None, params=None, c...
function read_train (line 105) | def read_train(data_folder, params):
function read_eval (line 114) | def read_eval(data_folder, params):
function read_test (line 121) | def read_test(data_folder, params):
function dnn_model (line 131) | def dnn_model(features, mode, params):
function model_fn (line 162) | def model_fn(features, labels, mode, params):
function rmse_evaluator (line 221) | def rmse_evaluator(labels, predictions):
function get_learning_rate (line 234) | def get_learning_rate(params):
function get_optimizer (line 258) | def get_optimizer(params):
function get_estimator (line 288) | def get_estimator(estimator_name, config, params, model_dir):
FILE: clv_mle/trainer/task.py
function create_parser (line 63) | def create_parser():
function csv_serving_input_fn (line 160) | def csv_serving_input_fn():
function main (line 187) | def main(argv=None):
FILE: run/airflow/dags/01_build_train_deploy.py
function _get_project_id (line 34) | def _get_project_id():
function get_model_type (line 130) | def get_model_type(**kwargs):
function do_train_automl (line 143) | def do_train_automl(**kwargs):
function do_train_ml_engine (line 249) | def do_train_ml_engine(**kwargs):
function do_copy_model_to_final (line 273) | def do_copy_model_to_final(**kwargs):
function do_check_model (line 320) | def do_check_model(**kwargs):
function do_create_model (line 332) | def do_create_model(**kwargs):
function do_list_versions (line 361) | def do_list_versions(**kwargs):
function do_create_version (line 371) | def do_create_version(**kwargs):
FILE: run/airflow/dags/02_predict_serve.py
function _get_project_id (line 32) | def _get_project_id():
function get_model_type (line 71) | def get_model_type(**kwargs):
function do_predict_mle (line 82) | def do_predict_mle(**kwargs):
function do_predict_automl (line 111) | def do_predict_automl(**kwargs):
function do_load_to_datastore (line 142) | def do_load_to_datastore(**kwargs):
function do_list_predictions_files (line 174) | def do_list_predictions_files(**kwargs):
function do_load_to_bq (line 199) | def do_load_to_bq(**kwargs):
FILE: run/airflow/gcs_datastore_transform.js
function from_prediction_output_to_datastore_object (line 16) | function from_prediction_output_to_datastore_object(prediction_row, enti...
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (356K chars).
[
{
"path": ".gitignore",
"chars": 137,
"preview": "*pyc\ndata/*\njobs/*\ntrained/*\n.DS_Store\nimages/\nnul\n.ipynb_checkpoints\nmykey.json\nrun/airflow/*cfg\nrun/airflow/airflow.db"
},
{
"path": "CONTRIBUTING",
"chars": 1446,
"preview": "Want to contribute? Great! First, read this page (including the small print at the end).\n\n## Before you contribute\nBefor"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 14333,
"preview": "This code supports the three-part solution [Predicting Customer Lifetime Value with Cloud ML Engine](https://cloud.googl"
},
{
"path": "clv_automl/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "clv_automl/clv_automl.py",
"chars": 9081,
"preview": "# Copyright 2019 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_automl/to_predict.csv",
"chars": 209,
"preview": "customer_id,monetary,recency,frequency,avg_basket_value,avg_basket_size,date_range,cnt_orders,cnt_returns,has_returned\n0"
},
{
"path": "clv_mle/__init__.py",
"chars": 602,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_mle/clv_ml_engine.egg-info/PKG-INFO",
"chars": 239,
"preview": "Metadata-Version: 1.0\nName: clv-ml-engine\nVersion: 0.1\nSummary: A trainer application package for CLV prediction on ML E"
},
{
"path": "clv_mle/clv_ml_engine.egg-info/SOURCES.txt",
"chars": 280,
"preview": "setup.py\nclv_ml_engine.egg-info/PKG-INFO\nclv_ml_engine.egg-info/SOURCES.txt\nclv_ml_engine.egg-info/dependency_links.txt\n"
},
{
"path": "clv_mle/clv_ml_engine.egg-info/dependency_links.txt",
"chars": 1,
"preview": "\n"
},
{
"path": "clv_mle/clv_ml_engine.egg-info/requires.txt",
"chars": 53,
"preview": "sh\nlifetimes==0.9.0.0\nnumpy==1.14.5\ntensorflow==1.10\n"
},
{
"path": "clv_mle/clv_ml_engine.egg-info/top_level.txt",
"chars": 8,
"preview": "trainer\n"
},
{
"path": "clv_mle/config.yaml",
"chars": 641,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_mle/config_tune.json",
"chars": 1736,
"preview": "{\n \"trainingInput\": {\n \"scaleTier\": \"CUSTOM\",\n \"masterType\": \"complex_model_m\",\n \"hyperparameters\": {\n "
},
{
"path": "clv_mle/setup.py",
"chars": 985,
"preview": "# Copyright 2017 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_mle/to_predict.csv",
"chars": 90,
"preview": "0123456789,1000.0,10,2,240,4.0,240.0,13.0,1,1\n1234567890,500.0,20,3,250,5.0,250.0,15.0,1,1"
},
{
"path": "clv_mle/to_predict.json",
"chars": 402,
"preview": "{\"customer_id\":\"abc\", \"monetary\": 1000.0, \"recency\": 20.0, \"frequency\": 3.0, \"avg_basket_value\": 250.0, \"avg_basket_size"
},
{
"path": "clv_mle/trainer/README.md",
"chars": 0,
"preview": ""
},
{
"path": "clv_mle/trainer/__init__.py",
"chars": 602,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_mle/trainer/btyd.py",
"chars": 7099,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_mle/trainer/context.py",
"chars": 8864,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_mle/trainer/model.py",
"chars": 10205,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "clv_mle/trainer/task.py",
"chars": 10633,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "linear.py",
"chars": 1338,
"preview": "from tensorflow.python.lib.io import file_io\nimport pandas\nfrom pandas.compat import StringIO\nimport json\nimport math\nim"
},
{
"path": "notebooks/Exploration.ipynb",
"chars": 206402,
"preview": "{\n \"nbformat\": 4,\n \"nbformat_minor\": 0,\n \"metadata\": {\n \"colab\": {\n \"name\": \"Exploration.ipynb\",\n \"versi"
},
{
"path": "notebooks/clv_automl.ipynb",
"chars": 9774,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": "
},
{
"path": "notebooks/linear_model.ipynb",
"chars": 5042,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": [\n "
},
{
"path": "preparation/sql/common/benchmark.sql",
"chars": 2011,
"preview": "-- Copyright 2018 Google Inc. All Rights Reserved.\n--\n-- Licensed under the Apache License, Version 2.0 (the \"License\");"
},
{
"path": "preparation/sql/common/clean.sql",
"chars": 1886,
"preview": "SELECT\n customer_id,\n order_date,\n order_value,\n order_qty_articles\nFROM\n(\n SELECT\n CustomerID AS customer_id,\n "
},
{
"path": "preparation/sql/common/features_n_target.sql",
"chars": 4112,
"preview": "-- Copyright 2018 Google Inc. All Rights Reserved.\n--\n-- Licensed under the Apache License, Version 2.0 (the \"License\");"
},
{
"path": "preparation/sql/dnn/split_eval.sql",
"chars": 877,
"preview": "-- Copyright 2018 Google Inc. All Rights Reserved.\n--\n-- Licensed under the Apache License, Version 2.0 (the \"License\");"
},
{
"path": "preparation/sql/dnn/split_test.sql",
"chars": 878,
"preview": "-- Copyright 2018 Google Inc. All Rights Reserved.\n--\n-- Licensed under the Apache License, Version 2.0 (the \"License\");"
},
{
"path": "preparation/sql/dnn/split_train.sql",
"chars": 870,
"preview": "-- Copyright 2018 Google Inc. All Rights Reserved.\n--\n-- Licensed under the Apache License, Version 2.0 (the \"License\");"
},
{
"path": "requirements.txt",
"chars": 209,
"preview": "google-cloud-automl>=0.1.2\ntensorflow==1.13.1\njupyter\napache-beam==2.2.0\nipdb\nlifetimes==0.9.0.0\ngoogle-api-python-clien"
},
{
"path": "run/airflow/dags/01_build_train_deploy.py",
"chars": 15737,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "run/airflow/dags/02_predict_serve.py",
"chars": 8904,
"preview": "# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
},
{
"path": "run/airflow/gcs_datastore_transform.js",
"chars": 1398,
"preview": "// Copyright 2018 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");"
},
{
"path": "run/airflow/requirements.txt",
"chars": 27,
"preview": "google-cloud-automl>=0.2.0\n"
},
{
"path": "run/airflow/schema_source.json",
"chars": 636,
"preview": "[\n {\n \"mode\": \"NULLABLE\",\n \"name\": \"InvoiceNo\",\n \"type\": \"STRING\"\n },\n {\n \"mode\": \"NULLABLE\",\n \"name\":"
},
{
"path": "run/mltrain.sh",
"chars": 3058,
"preview": "#!/bin/bash\n\n# Copyright 2018 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \""
}
]
About this extraction
This page contains the full source code of the GoogleCloudPlatform/tensorflow-lifetime-value GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (334.1 KB), approximately 160.7k tokens, and a symbol index with 58 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.