master e4c775bf6549 cached
41 files
334.1 KB
160.7k tokens
58 symbols
1 requests
Download .txt
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\n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\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": "iVBORw0KGgoAAAANSUhEUgAABHwAAAE9CAYAAAB5tHVBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzs3Xl4E+XePvB7JnuabulGWyiLLCIg\noCCCsos7/ETBIwqKIqiIHkV2UXBj8UX0qO/RcxTfgwiyH0FREAREWcpeKatAoQW6snTLnnl+f7SU\nFgpCaTNpcn+uq1ebSTJzp0+SmXzzPM9IQggBIiIiIiIiIiIKGLLaAYiIiIiIiIiIqHqx4ENERERE\nREREFGBY8CEiIiIiIiIiCjAs+BARERERERERBRgWfIiIiIiIiIiIAgwLPkREREREREREAUZbnSvz\neLw4e9ZWnausNSIjzXzsQSqYH38wP3YguB8/H3twPnYAiIkJVTsCXSSYj7/8WbC/V/gjtol/Yrv4\nH7aJf6rKMVi19vDRajXVubpahY89eAXz4w/mxw4E9+PnYyfyH3xO+ie2i/9hm/gntov/YZsEDg7p\nIiIiIiIiIiIKMCz4EBEREREREREFGBZ8iIiIiIiIiIgCDAs+REREREREREQBhgUfIiIiIiIiIqIA\nw4IPEREREREREVGAYcGHiIiIiIiIiCjAsOBDRERERERERBRgWPAhIiIiIiIiIgowLPgQkV9zOh34\n5ZefceJEutpRiIiIrosQAps3/46jRw+rHYWIiIKAVu0ARESVEUIgOXkzFi2ah7Nnz8BgMOKVV0aj\nWbPmakcjIiKqkuTkzfjii39CkiTce++DeOihftDpdGrHIiKiAMUePkTkd44fP4Zp097Gv//9Kc6e\ny4c2vAGcLic+mDkNe/akqB2PiIjomhUVFWHevNmApIGkDcFPP32Pt9+eiPT042pHIyKiAMWCDxH5\njYKCAsye/SXefvt1/PnnQWgtiQhpdB9MCbfDVLczvB4FH388A9u3b1U7KhER0TVZuHAuiooKoY9u\nCXOje6CLaIyTJzPwzjsTsWLFciiKonZEIiIKMCz4EJHqPB4PVq9eifHjR+LXX9dC0ofClNQNpnqd\nIestAACtJQHGel2gCAmfffYPbNy4QeXUREREV2f//r34/fdfIRsioI9qBknWwRjfDqZ6XaBIeixZ\nMh/Tpr2F7OwstaMSEVEAYcGHiFS1d+8eTJo8Ht9++zUcLjcMcW1hbngvtCF1LrmtNiQOpqTugKzD\nrFmfY+3a1SokJiIiunoulwv/+c8XACQY42+DJF04/NZaEhDS6F5ow5Jw+PCfmDRpHNatWwMhhHqB\niYgoYHDSZiJSRU5ONhYsmItdu7YDAHQRN0Af0wqy1njF+2lMUTAl9YA9Yz2++eb/YLfb8cADfXwR\nmYiI6JotX74Uubk50FmbQWOyXnK9pDHAlNgJbksinNk7MGfOV9i1aweefnooIiMvvT0REdHVYsGH\niHzK4XBgxYplWLVqBTweDzSmaBjq3AqNMfKq16ExRsBcWvRZsmQ+HA4bHn74b5AkqQaTExERXZv0\n9ONYufIHSLoQGGJaXvG2uvD60Jhj4MjcitTUFLzxxlgMGvQ0OnTo5KO0REQUaFjwISKfEEJgy5aN\nWLToW5w7dxaS1gRjQntow5KqVKiRDWEw1e8Je/o6rFixHA6HEwMGDIIsc6QqERGpz+v14j+zv4Ci\nKDAltoMk//Xp12WdGaZ6XeE+dwT2nN34178+xa5d2zFw4DOwWCw+SE1ERIGEBR8iqnHHj6dh7tzZ\nOHz4ECBpoI+6CfromyDJ1/cWJOtCYErqCXvGevzyyyo4HHY8/fQwFn2IiEh1P/zwA46lHYU2rD60\nlvirvp8kSdBHNoY2JA72U1uwdesWHDx4AM888xxatWpdg4mJiCjQsOBDRDWmoCAfS5cuxG+/rYcQ\nAtrQujDEtik781Z1kHUmmJN6wJbxKzZu3ACn04lhw16EVsu3NyIiUkdeXi7mzJkDSaOHIa5tldYh\n60Nhrt8TrtMHkJ+Xig8/nI5u3Xri0UefgNF45fnuiIiIABZ8iKgGeDwerF27GsuWLYbdbodsCIcx\nrm2lZ96qDpLWAHNSd9gzNmD79mQ4nQ68+OKr0Ov1NbI9IiKiyxFCYM6cr+B0OmGM7/CXJyO4EkmS\nYYi+CVpLPByntmD9+l+QmroHQ4e+gCZNmlVjaiIiCkQc90BE1So19Q9MmjQO8+fPgcPlhSHuFpgb\n3lNjxZ7zJI0OpqSu0ITEY8+eFHz44XTY7fYa3SYREdHFkpM3Y8+eFGhC4qANb1At69QYI2FucDf0\nUc2Rl5eDadPexuLF8+F2u6tl/UREFJhY8CGiapGTk42PP/4AM2dOQ2ZmJnQRjWFu9AD01qaQJN+8\n1UiyFqa6d0IbWhcHD+7HjBlTUFRU5JNtExERFRUVYt682ZBkDYx12lXr2SMlWQNDbGuY6veEpA3B\njz8uxzvvvIGMjPRq2wYREQUWFnyI6Lo4HA4sWTIfr78+Grt374DGHANzw7thjG8HWWvweR5J1sCY\n2Ana8AZISzuC6dPfQX5+vs9zEBFR8FmwYC6Kigqhi24JWR9aI9vQmmNgbnQPdBE34MSJdLz99uv4\n8cflUBSlRrZHRES1Fws+RFQlQgisW7cO48e/hhUrlkOR9TAmdoIpqQc0xkhVs0mSDGN8B+giG+Pk\nyQxMnfoWTp/OUzUTEREFtn37UrFx4wbIhgjorTU7v44k62CMbw9TvS5QJD0WL56PadPeRk5Odo1u\nl4iIahcWfIjomqWlHcWUKZMxc+ZMFBQUQB/dAiGN7ocuLKlau69fD0mSYIi7Ffqo5sjJycLUqW8h\nOztT7VhERBSAXC4XZs/+EoAEY/xtPhvKrLUkIKThvdCG1sPhw4fw5pvjsH79LxBC+GT7RETk31jw\nIaKrlp+fj6+++jfeffcNHDnyJ7Sh9WBudB8MMa0gyf530j9JkmCIbQ19zM04c+Y0pk59GydOcK4D\nIiKqXsuWLUFubg501qbQmKw+3bakNcBU9w4YEzrC7RX4+utZ+PDD93H27Fmf5iAiIv/Dgg8R/SWP\nx4NVq1Zg/PiR+P339ZD0YTAldYep7h2Q9Ra14/0lQ/RNMMTdgoKCfEyb/g7S0o6oHYmIiAJEevox\nrFq1ArIuBIaYVqrl0IXXh7nhvdCE1EFqagreeGMMtm7drFoeIiJSHws+RHRFe/ak4M03x2LBgrlw\nur0wxN1aepr1OLWjXRO9tSmM8bfBVmzD+++/h4MH96sdiYiIajlFUfCf/3wJRVFgqNNO9d6uss4M\nU72uMNRpB7vDic8//wSff/4Jz1hJRBSk/G8MBhH5hezsLMyfPwcpKbsASNBFNC4ZuqXCmbeqiy6i\nESBr4Ti1GTNnTseIEa+iVavWasciIqJaas2alTh27Ci0YfWhtcSrHQdAyXBmfWRjaEPiYD+1BVu3\nbsbBg/vxzDPPcZ9HRBRk2MOHiCqw2+1YtOhbTJw4Bikpu0pPs34PjPHtanWx5zxdWBJMdTvD4/Hi\n449nYPv2rWpHIiKiWigvLxdLly6EpDHAENdW7TiXkPWhMNfvCX3MzcgvKMCHH07H11/PgtPpUDsa\nERH5CAs+RASgpFv6xo0bMGHCa/jpp+8hKpxmPULteNVKa0mAsV4XKELCZ5/9Axs3blA7EhER1SJC\nCHz99VdwuVwwxLaBrDWqHalSkiTDEH0TzA16QTaEY/36XzBp0ngcOfKn2tGIiMgHOKSLiJCWdgRz\n5/4HR48eASQN9NEtoY+6UfW5CGqSNiQOpqTusGf8ilmzPofT6USPHr3UjkVERLVAcvJmpKamQBMS\nB214A7Xj/CWNMRLmBnfDmbsHOTkHMGXKZDzwwP9Dnz4PQ6sN3H09EVGw4zs8URDLzz+HJUsW4Pff\nfwUAaEPrwRDXBrIuROVkvqExRcGU1AP2jPX45pv/g8Nhx/3391E7FhER+bGiokLMmzcbkqyBsU57\nSJKkdqSrIskaGOPaQBuaAOepZPzww3f4Y89uDH12OBIT66odj4iIagALPkRByO1245dfVmHZ8qVw\nOhyQDREwxLWtdWfeqg4aYwTMpUWfxYvnw2634+GHH601B/BERORbCxbMRVFRIQyxrSHrLWrHuWZa\ncyw0De+FI3sn0o+n4a23JqB//wHo2fMeyDJneyAiCiQs+BAFESEEduzYhoWL5iEvNweSRg9DnVuh\ni7gBkhS8B3myIQym+j1hT1+HFSuWweFwYMCAQTzwJSKiCvbtS8XGjRsgGyKhszZTO06VSRodTAkd\n4A5NhDNrG779dg52796JIUOeh9UapXY8IiKqJiz4EAWJtLSjmD9/Dv788yAgSdBFNoUhpgUkTe0/\n81Z1kHUhMCX1hD1jPX75ZRUcDjuefnoYiz5ERAQAcLlcmD37SwASjPHtA+KLEl1oXWhM0XBkbsX+\n/XvxxhtjMHDg07j99jvY05WIKACw4EMU4M6cOY0lSxZg8+bfAQBaS2JJN3RDmMrJ/I+sM8Gc1AO2\njF+xceMGOJ1ODBv2Iie0JCIiLFu2BLm5OdBZm0Fjsqodp9rIWiNMdTvDfe4oHDm78MUX/8Tu3Tsx\naNAzsFhq35A1IiK6gJ9iiAKUw+HAypU/4KeffoDb7QrqeXquhaQ1wJzUHfaMDdi+PRkulxPDh78C\nvV6vdjQiIlJJevoxrFq1ArIuBIaYVmrHqXaSJEEfeQO0IXFwnNqCbdu24NChAxgy5Dm0bNla7XhE\nRFRFtb8vKhFVoCgKfv/9V4wfPxLLly+FR8gwxt8Gc8O7Wey5SpJGB1NSV2hC4vHHH7vx4YfTYbfb\n1Y5FREQqUBQF//nPl1AUBYY67SDJgft9qay3wFS/B/QxNyO/oAAzZ07HnDn/B6fToXY0IiKqgsDd\nYxEFoQMH9mH+/G+Qnn4MkqyBProF9FE3QpJ1akerdSRZC1PdO+E4tRkHD+7HjBlT8OqrY9m9nYgo\nyKxZsxLHjh2FNqw+tJZ4tePUOEmSYYi+CVpLPByntmDdutXYu3cPhg4djhtuaKx2PCIiugbs4UMU\nALKzM/HJJzPx/vvvIj39GLRh9WFudD8MMa1Y7LkOkqyBMbETtOENkJZ2BNOnv4P8/Hy1YxERkY/k\n5eVi6dKFkDQGGOLaqh3HpzTGSJgb3A2dtRlycrIwdepkfPfdYng8HrWjERHRVWIPH6JarLi4CN9/\n/1/88svP8Hq90JiiYYhrC42Jp1StLpIkwxjfAU5Zi5MnD2Pq1LcwevQEREVFqx2NiIhqkBACX3/9\nFVwuF4wJHSBrjWpH8jlJ1sAY1xZaSwKcmclYvnwp/vhjN4YOfQHx8YlqxyMior/AHj5EtZDH48Hq\n1Ssxdtyr+PnnnyBkI4yJd8BUvyeLPTVAkiQY4m6FPqp56becbyE7O0vtWEREVIOSkzchNTUFmpA4\naMMaqB1HVdqQOJgb3gtteAMcO3YUkyZPwC+/rIKiKGpHIyKiK2APH6JaRAiBlJSdWLBgLrKzsyDJ\nOhhi20AX2QSSrFE7XkCTJAmG2NaArMOZ3D8wdepbmDLlPZjNgXNqXiIiKlFUVIh5874u6eFSpz0k\nSVI7kuokjR6mhNvhtiTCmbUdc+fOxq5dOzBkyPOIjOS+kIjIH7GHD1EtkZ5+HDNmTMHHH3+A7Oxs\n6CIbw3zDA6WTMrPY4yuG6JtgiLsFBQX5GDduHNLSjqgdiYiIqtmCBXNRVFQIfXQryHpO1l+eLqwe\nzI3uhSYkHvv2pWLixDFITt6kdiwiIqoEe/gQ+bn8/HNYunQhfv/9VwghoAmJhyGuDTSGcLWjBS29\ntSkkWYuizG14//338Moro9GsWXO1YxERUTXYu3cPNm7cANkYCZ21qdpx/JKsNcFUrwvc547AkbMb\n//rXp9i1awcGDXoaISEskBER+QsWfIj8lMvlwqpVP2LFimVwuZyQDeEwxrYJilPC1ga6iEaArIXj\n1GbMnDkdI0a8ilatWqsdi4iIroPT6cTs2bMASKVDudgZ/nIkSYI+sjG0IXGwn9qCrVs349ChAxgy\n5Hm0aNFK7XhERAQO6SLyO4qiYMuWjRg//jX8978L4fYChjrtYG54D4s9fkYXlgRT3c7weLz4+OMZ\n2L59q9qRiIjoOixfvhR5eTnQWZtBY+K8NFdD1ofCXL8n9DGtcC7/HD74YCrmzp0Np9OpdjQioqDH\nHj5EfuTPPw9i/vxvSuaFkWToo5pDH3UTJI1O7Wh0GVpLAoz1usBx4jd89tk/8Mwzz+GOO7qoHYuI\niK7R8ePHsGrVCsi6EBhiWqodp1aRJBmG6BbQhsTDkbkFv/yyCnv3/oGhQ19Ew4aN1I5HRBS0WPAh\n8gO5uTlYvHg+tm3bAgDQhtaDIbY1J4qsJbQhcTAldYc941fMmvU51q79GfHxiYiPT0RCQgLi4xMR\nExMLjYaTaxMR+SNFUfCf/3wBRVFgSmwPSeYhclVoTFaYG9wNZ+4fyMo6hPfeexN9+jyMBx74f9wH\nEhGpgHszIhXZbDasWLEMP//8E7xeD2SjFca4W6AxR6sdja6RxhQFU1IPODK3Iu3YMaSlHa14vUaD\nuLg6pYWgBCQklPyuUyceBoNRpdRERAQAa9asxPHjadCGN4DWUkftOLWaJGthjLsFWksCnJlb8d13\ni5GSsgtPPz0MCQmJkGXOKEFE5Css+BCpwOv1YsOGdfjvfxehqKgQks4MY1w7aMPqQ5IkteNRFWmM\nEQhpeDeEUCBcRfC6CqA4C6CU/s7MysGpUycvuV9UVHRZIah8MSg0NEyFR0FEFFzy8nKxdOlCSBoD\nDLFt1I4TMLQhdaBpeC8cWTuQlnYEb745FhqNFlarFVZrVIWfqKgLf5tMZh4LERFVExZ8iHwsNTUF\n8+fPxalTJyDJWuhjWkFvbcbu4wFEkmRIhjDIhjAg9MJyIQSEx15WADr/+0x+AU6fTkFqakqF9Vgs\noRcVgUoKQVZrFL8hJSKqBkIIfP31V3C5XDAmdICsZY/L6iRp9DAldoQ7tC48BcehuG3IO1uA3Nyc\ny97HYDBWKABdXBiKjIyCXq/34aMgIqq9+AmTyEdOnjyBBQvmln2o10U0gj6mFWStSeVk5CuSJEHS\nmSHrzEBIxSEDwusqKwB5S4tBxc4C/PnnIfz558EKt9Xr9ahTJ6FsfqCSolAi4uLqQKvl2zoR0dVK\nTt6E1NQUaELioA1roHacgKULqwddWL2yy0J4Idx2KG4bhMdW8tttg+Kxwe22ITM7t9IesedZLKGX\nLQpZrVGIiIjknEFERGDBh6jGFRQUYNmyxfj117VQFAUacywMcW2hMUaqHY38iKTRQ2OKhsYUjfLn\nZBOKF4qrsEKvII+zAOkZGUhPP1ZhHbIsIyYmDgkJCaUFoQvDxEwms08fDxGRvysqKsS8eV9DkjUw\n1mnPYUQ+JEkaSHrLFU9OIRT3hULQRYWhYpcNRekZOH782GXWLyEiIrKsV1BYWAQsFgvi4qIghBYh\nIRaEhISU/rbAbDaz5ywRBSQWfIhqiNvtxpo1K/H999/B4bBD1ofCFNsGGksCDyrpqkmyBhpjBDTG\niArLhVBKDoLPF4KcBfC6CpCTdxrZ2ZkAdlS4fURE5CVDw+Li6sBkMkGr1UGr1fJ5SURBZcGCuSgq\nKoQhtg3PiumHJFkHjSEcMIRXer0QAvC6oHjKF4WKy4pC54psOHv2MI4c+fOvtyVJMJnMsFgqFoLO\n/12y3HJJoSgkJIQ9iYjIr7HgQ1TNhBDYsWMrFi6ch7y8XEgaPQxxt0AX2RiSFPjfHikeO6B4a35D\nsiaoh8NJknzh21FLQtlyIQSE11lhjiDFVYD8ogKc278X+/fvvew6dTodtFoddLoLPxdfPr/MYjFB\nUaRKb3eldVS2vvLXsehERL6wd+8ebNy4AbIxEjprU7XjAOD+81pJkgRoDdBoDcBlek0LoUB4HBBe\nJ4TXVe53yQ/OX1ZccHhdsJ8pQE5uLiCUq85hNJouWygKCQmBXm+AVquFRqOBVqst/bvkt06nq3R5\nyRcxFZezBxIRVUW1FnwOHTqEc+dsV3VbIUR1bvqyrvTh4a8yXMsHj7NnzVf92P3Z+f/JxY+9suXn\nl509G1L22C/+n56/fWXLa/o5ULJ6UfIBuNzP+TyXLhdQFFF6n9KDhNJ1XFh+/ufC9aGhBhQU2CGE\ngNfrxe+//1oy54okQ2dtBkN0C0iawJ9c0Os4B/vJjRCuwsveRq/XIzo6Gnl5eXC5XNe9TUkfClPi\nHZf0fglmkiRB0hpLJh4Nia1wnVDcUJyFUFz5pb8LIYQXEF5AUeAVXngVBQ6HF7DZSg54hbf0Nld/\n8Hs9NBptpcWgkoNdqaTQJaGseHr+AFiW5bL3m/PLyt9WliUAUsn/p+yn4m0qXieVrk8q3e6FH5NJ\nD6fTc8n6ym/j/HtKyXtF5X9feE+58DeASt6rrnz91W2n/PvwheUl/yepwuO/8H8+f7ni/2b06JHV\n2+hEKlixYhkAlA7lUveDNPefNUeSZEg6M6C7tmHNQvFUKAyVFIWcwMXLvE64vC6cPleM02fOQiie\nGnokJfu2ikWh8j8lBaLy12k0GsiyXPoeLpf9ffHPxdedf9+v/DZXuq7ifc/vey/dt8qX7DMjI0NQ\nUOC4zG0v/TxR2ecRfmH01y732auyz31nz4bg7Nnias9w8WfAK12ujW1a059vY2LaXvN9JFGNqXr3\n7l1dqyKq1WR9GEz1OkPWh/71ja+TI3s3PIXpNb6dvyLcdpz/AFkZvV6P559/Hr169cLq1avx+eef\nV8tBKyBB0vnPN5Xa0CQY4wLvtL5CiHIFIKW0SHThb1FaNCp/vVC8lxSNhOIFFBeE1w2huCG8bkBx\nQ5Qug/DBt9t0Xb7//nu1I1AlcnMvXyygS7399kQcT0+HpVm/Gln/teybuf8sEQj7T6F4S/dn53sQ\nuS58aSKU0v3j+b/LLVe8gOKBEJ6SopHihvCWXC7ZR3qAGiwmEVHtUJVjMA7pIqoBiqsQrjOHoI9u\nCVlrUDtOjTvfm+BKoqOj0atXLwBAr169sHjxYpw6dao6tg4hRK38FkBNJd3c7aVFmXLFmPN/X1TQ\nKV/AuXCQevGykqLP+b+FUEqGJ6jQU4iI6K+I0g/gavbw4f6z9hFClBZkyvf+cUF4nOWKPc5yBR8F\nFb8wKV/0Kf37L54DRERVVa0Fn++//z5ov2GKiQnlYw9S5R+/EAK7d+/AggXzkJPzJzwFx6CPagGd\ntQkkqWYm9TPGtQH84BuxoiMrrtgdPS8vD6tXry77hjIvL69ativrQxFywwPVsq5AJBQPFFdR6Xw+\n+Rfm9nEV1kjxRZKksu7lOp0Oer2xdJ4Cfek8PeeHbFV2+fz99KXLLtzvfNf08sOsLh2idfU/5ddR\nfl1Xs96oKAvOnrVddr2SVH5I6fnfVzN0q+IwrZK/KxuqdW3DxCrbbvnfFYdsle+CX/kwL6JA0KjR\nDTh27Chcpw/CEN282td/Lftm7j/VUfKlhPuSIVqVXlbK9djxunC1BRpJksqGJZfsGw1XHIZVfu6e\nivP5aC+6ra7C0K0L8wFdOqSr8uFc0mWvu/KQrpopjgb7Zwl/xDYJHNU6pAsI3i7FwfyiCObHDlT+\n+D0eD9atW41ly5bCZiuGrLdAH9MG2tDEgP3A5HWcg+PkxpJCwmVU9xwEsj4UxiCYg+BqCK+r9Exd\n5+fnKSnwCPel46/1egPi4xNQp0586Vm6Ki/IXKk4ExsbjqIiV+ntLhRnNBpNwD7Hz+N7Xs0PVaVr\nF8zPyaooLi7C66+PRmFhEcyN7vXJEOzL4f6z+pRN0lzuFO6K21ZauLlQzMH5As5VkmVNuTN1hVT4\n+3Jn77JYLDAaTZxs+SoE+37VH7FN/FNVjsFY8KkmwfyiCObHDlz58RcVFWH58qVYu/ZnKIoCjTkG\nhti20JisPk7pOzzLSM0RQkB47JecgUtxFUB4HJfcPjQ0rPQ07AmlP4lISEhEZKT1uosywfy6D+bH\nDrDg46+C+TlZVVu3bsHnn38MjTkOpqRuqheruf+8sspPw26D4i4uueyxQXjs57tYVkqn01VaoLFY\nLl+4CQmxwGg0qv78CGTBvl/1R2wT/1SVYzDO4UNUgywWCx5//En06NELCxfOw+7dO2A79jO04Q1g\niLkZ8jWeNaI2qI0Hkf5GCKXcMKwLxR3hKrjkDCAlQ4yikZBwI+LjKxZ3LBaLSo+AiMj/tW/fAZs3\nt0VKyi548tOgi2ikap5g338KxX2hV84lRZ2Sy5eb2F+SJERERMJqTYTVGoWoqChERkbBai35CQ8P\nh8USisTEKH6IJaKgwoIPkQ/UqROPl19+Dfv378X8+XOQkXEM3sIT0FmbQR/VHJLMl2IwEorn0t46\nzgIo7qJL5tfRaLSIj69T1kvnfGEnLi4eBkPgTwxORFTdJEnCoEHP4MCBUXDm7IbGkgBZa1Q7VkAS\nQkC4iy8p4Jwv7AiP7YpDrCyWUFjr1ENU1IUiTvmfiIhIaDQ1M1ciEVFtxk+ZRD7UvHkLTJo0BRs3\nbsCSJQtQkLcX7nNHYYi5GdrwBuwuHICEEBBe54ViTvlhWG7bJbc3Gk1IaNgI8fEJ5Qo7iYiOjuHB\nLBFRNbNao/DII49h3rzZcGbvhCmxk9qRAo7iKoT91BYo9tOVXm8wGBEVF1NpIcdqjUJkpJVfbBAR\nVRELPkQ+JssyOnfuhvbtb8dPP32Pn1b+AEdmMuSzh2CIbQttSKzaEamKhBDw2nKhOM5U6LVT2beW\nERGRiI9vWGFunfj4BISHR7DwR0TkQz169MKWLb/j6NEj8IQ3gNaSoHakgCCEgPvcEbhydkMoHrRs\neTPq129YVsg531vHZDJzv0dEVENY8CFSidFoRN++/dG1aw8sWbIAmzf/Dnv6WmhD68IQ21rVM4bQ\ntRNCwJm9C+6zh8qWSZKE2Ni4S+bWiY9PgNkcePM3ERHVRrIsY/DgoZg8eQKcWduhaXQfJFmndqxa\nTXHb4cjcCm9xJkwmM5588nl06MDeU0REvsaCD5HKrNYoDB06HD173o3587/B4cOH4Ck6BV1kExii\nW0DS6NWOSH9BCAXOzO1w5x+sii8QAAAgAElEQVRFfHwiHnroEcTHJyIurg50On5oICLyd3XrJuG+\n+3pjxYplcObugTHuFrUj1Vruggw4s7ZDeJ246aaWGDLkeURGBu7ZSYmI/BkLPkR+olGjxhg/fhJ2\n7NiKhQvnIS/vIDz5x6CPbgFdZGNIkqx2RKqEEF44Tm6BpzAD9es3xMiRYxEaGqZ2LCIiukZ9+vTF\ntm3JyMn5E7qw+tCYotSOVKsIrwuO7J3w5B+DVqfD3x57Ct2794Is8/iFiEgtfAcm8iOSJKFduw54\n773/Qf/+A2DQyXBm74Tt6E/wFJ6EEELtiFSOUDywn9gIT2EGmjRphtGjX2exh4ioltLp9Bg8+FkA\nAo7MbRAXnS2RLs9TnA1b2kp48o+hQYNGeGvyFPTseQ+LPUREKuO7MJEf0un0uO++3pg27UN0734X\nhLsI9hO/wZ6xHl7HObXjEQChuGHP2ABv0Sm0aNEKI0eO47w8RES13I033oTOnbtBcZ6D6/QBteP4\nPaF44cjeBXv6OsDrQJ8+D2PChMmIj09UOxoREYFDuoj8WlhYGAYNegY9etyNhQvnYs+eFNjSVkEX\n0RD6mFaQtSa1IwYl4XXBlvErFPtp3HJLezz33AjO1UNEFCAeffRxpKTsQmHeXujC6vEkCpfhdZyF\n49QWKM58xMbWwbBhw9GoUWO1YxERUTns4UNUCyQm1sWrr47FyJFjkZCQCPe5o7AdWQFn3l4IxaN2\nvKCieBywHV8LxX4a3bt3xwsvvMxiDxFRAAkJseDxx58qmaMtcxuHU19ECAXOvH2wHVsNxZmP7t17\n4a23prDYQ0Tkh1jwIapFWrZsjbfemoonnxyCELMJrtw9KD76I9z5x3lA6gOKuxj2479AcZ5Dt249\n8corr0Cj0agdi4iIqln79h3QunVbeG058OSnqR3HbyiuItiPr4Ur9w+Eh4Vh5MixGDToaRgMRrWj\nERFRJTiki6iW0Wg06NatJ267rSNWrFiGn3/+CY5TmyGfOQRjXFtozNFqRwxIiqsQ9vT1UNzFuO++\n3ujX7zFORklEFKAkScKgQc/gwIFRcObshsaSAFkbvEUNIQTc547ClbMLQvGgXbsOePLJZ2CxcLgb\nEZE/46cVolrKbDajf/8BmDJlBtq3vx2K4zRsx9fAfnITFFeR2vECiteZXzKMy12Mvn0fRb9+j0GS\nJLVjERFRDbJao/DII49BeF1wZu9UO45qFI8D9hO/wZm1DUaDDkOHDscLL7zMYg8RUS3AHj5EtVxM\nTCxeeOFl9Op1L779dg7S0o7AU3gSemtT6KNugqTh/DLXw2s/A3vGrxBeJwYMGIReve5TOxIREflI\njx69sGXL7zh69Ag84Q2gtSSoHcmn3AUn4MzaBuF1onnzFhgy5HlYrVFqxyIioqvEHj5EAaJx46Z4\n/fW3MGzYi4iMiIDr9H4UH/kBrrOHIYSidrxayWPLKTnVrOLC008PY7GHiCjIyLKMwYOHQpZlOLO2\nQyhutSP5hPC6YT+VDMfJ36GRFQwYMAivvTaexR4iolqGBR+iACLLMm6//Q5MnfoB+vZ9FDoN4Mza\nDlvaKniKMtWOV6t4ijLhyPgVErx47rkR6Ny5m9qRiIhIBXXrJuH++/tAcdvgzNmjdpwa57HloDht\nJTz5aUiq3wCTJ01Br173cd46IqJaiEO6iAKQXq9H794PoUuXbvjvfxfht9/Ww57xKzQh8TDEtYHG\nEK52RL/mLjgBx6lN0GpkvPjiSLRu3VbtSEREpKLevR/Ctm1bkJ19CLrw+tCYAq+ni1C8cObugfvM\nAUiShAcffAh9+jwMrZYfF4iIaiuW6okCWHh4BAYPHopJk6bgxhtbwFucCdvRlXBkbYficagdzy+5\n84/BcXIj9DodXn11LIs9REQEnU6PwYOHAgAcmVsDbqi013EOtmM/w33mAGJj4zB+/CQ8/PCjLPYQ\nEdVyfBcnCgJJSfUxevQEpKTswsKFc5GVdRieguPQR7WAztoEkqRRO6JfcJ39E86sHTCZzBg5cixu\nuKGJ2pGIiMhPNGvWHF26dMeGDevgOn0Ahuib1I503YRQ4DpzEK7cPYBQ0K1bT/ztb0/AYAjeU9AT\nEQUSFnyIgoQkSWjT5ha0bHkz1q//Bd8tWwxbzm64zx2BIa5t0J155GKu0/vhzEmBxRKKUaMmICmp\nvtqRiIjIz/TvPwC7d+9EQd5e6MLqQdbX3lOTK64iOE4lw2vPRVhYOJ55Zhhuvpm9WomIAgmHdBEF\nGa1Wi7vuugfTpn6Inj3vBjzFsGdsgC1jAxRXodrxfE4IAWfOH3DmpCAy0orx4yex2ENERJUKCbHg\niSeeAoQXjsxtEEKoHemaCSHgPncUtrSV8Npzceutt+Gdd6az2ENEFIDYw4coSFksFjzxxGB07doT\n8+Z9jQMH9qK4OAu6yKYwRLeApNGpHbHGCSHgzN4F99lDiImJxejRryM6OkbtWERE5MfateuANm1u\nwe7dO+HJT4MuopHaka6a4nHAmbkNnqKTMBpNGDhwKDp2vBOSJKkdjYiIagALPkRBrm7dehg9egJ2\n7NiGBQu+wenTB+ApOAZDTGtowxsE7EGgEAqcmdvhzj+KhIS6GDVqPCIiItWORUREfk6SJAwc+DT2\n798LZ85uaCwJkLX+P+eNu/AEnFnbITwO3HjjTRgy5HlERUWrHYuIiGoQh3QRESRJQrt2t+G992bg\noYf6QSspcGQmw3ZsNbz202rHq3ZCeOE4uRnu/KOoX78hxo6dyGIPERFdNas1Cv36PQbhdcGZvVPt\nOFckvG7YTyXDceJ3aODBY48NxKhRE1jsISIKAuzhQ0Rl9Ho9+vR5GHfc0QWLFs3D1q1bYDu2Gtrw\nhjDE3gxZa1I74nUTigf2k5vgLTqFJk2a4e9/Hw2z2ax2LCIiqmW6d++FzZs34ujRw/CENYA21P9O\nfuApzi45jby7GEn1G2Dos8ORmFhX7VhEROQj7OFDRJeIiorG88+/jLFj30C9eknw5KfBduRHuE7v\nhxBeteNVmVDcsGdsgLfoFFq0aIWRI8ex2ENERFUiyzIGDx4KjUYDR/Z2CK9b7UhlhOKBI3sX7Onr\nIHnt6N27Lya+/jaLPUREQYYFHyK6rGbNmmPSpCl48skhMJsMcOakwHZ0JTyFp9SOds2E1wVb+np4\nbTm45Zb2ePnlUTAYDGrHIiKiWqxu3Xq4777eEG4bnLl71I4DAPDaz8CW9jPcZw4iLq4Oxo+fjL59\n+0OrZcd+IqJgw3d+IroiWZbRrVtPtG/fAcuWLcHatathP7EBmpB4GOPaQjaEqR3xLykeB+zp66E4\nz6FjxzvxzDPPQaPRqB2LiIgCQO/eD2H79mRkZR2CLrw+NKYoVXIIocCVtw+u03sBIdCz5z3o1+8x\nfrlBRBTE2MOHiK5KSIgFjz/+FN56axqaN28Bb3EmitNWwpG926+6sV9McRfDfvwXKM5z6NatJ4YM\neZ7FHiIiqjY6nR5PPfUsAJTMlyMUn2dQnAWwHVsDV14qIiMi8dpr4/HEE0+x2ENEFOTYw4eIrkli\nYl2MGjUBO3dux6JFc5GTc/407jdDG97Qr07jrrgKS3r2uItx33290a/fY36Vj4iIAkOzZs3RpUt3\nbNiwDq7T+2GIbuGT7Qoh4D77J1w5KRDCi44d78QTTzwFsznEJ9snIiL/xh4+RHTNJEnCrbe2x2ef\nfYa+fR+FTlbgyNxaehr3PLXjAQC8znzYjq+F4i5G376PsthDREQ1qn//AQgLC4crbx8UZ0GNb09x\nF8Oevh7O7J0wm00YPvwVDB06nMUeIiIqw4IPEVWZXq9H794PYcqUD9ChQycojjOwHVsD+6ktUNx2\n1XJ57WdgP74WwmPHgAGD0Lv3Qyz2EBFRjQoJseCJJ54ChBeOrO0QQtTIdoQQcOenwXZ0Jby2bLRp\ncwveffd9tGt3W41sj4iIai8O6SKi62a1RuG550age/e7MG/e10hPPwZv4QnoolpAb20KSfbdnDke\nWw4cGb8BwoOnnx6Gzp27+WzbREQU3Nq164A2bW7B7t074c4/Cn3EDdW6fsXjgDNrOzyFJ2AwGPD4\n48Nw551d+aUGERFVigUfIqo2TZveiDfffBe//bYeS5YsQFFuCjz5R2CIbQuNJaHGD0g9RZlwnPwd\nEgSGPTcCt93WsUa3R0REVJ4kSRg48Gns378XrpwUaC0JkLWmalm3p/AkHFnbIDwONG16I4YMeR4x\nMbHVsm4iIgpMHNJFRNVKlmV07doDU6d+gLvuuhfw2GA/8RvsGRvgrcE5DdwFJ2A/8Rs0soSXXnqN\nxR4iIlKF1RqFfv0eg/C64Mzaed3rE143HKeSS/Zx8OBvf3sCY8ZMZLGHiIj+Env4EFGNKDmN+5Po\n2rUHvv32a+zblwpbWhZ0kU1hiG4BSaOvtm2584/BcSoZer0ef//7KDRv7puzoxAREVWme/de2L59\nCw4ePAhP4UloQxOrtB5PcQ4cmckQ7mIkJTXA0KHDkZhYt5rTEhFRoGIPHyKqUYmJdfHaa+Px0ksj\nER0VA/eZgyg++iNc545Uy4SWrrOH4Ti1BSaTCaNHT2Cxh4iIVCfLMl566SVoNBo4snZAeN3XdH+h\neOHI3gV7+lpIXjt69+6LiRPfZrGHiIiuCXv4EFGNkyQJbdu2Q8uWN2PVqh/xww/fwZm5De6zh2GM\nuxUac3SV1us6vR/OnBRYLKEYNWoCkpLqV3NyIiKiqqlfvz7uu693yT4v9w8Y69x6Vffz2s/AkbkF\nirMAcXF18Oyzw3HDDY1rOC0REQUiFnyIyGd0Oj0efPAh3HFHFyxa9C22bNkI2/E10IbVhyG2DWTd\n1U1sKYSAK3cPXKf3ITLSilGjJiA+PqGG0xMREV2b3r0fwvbtycjK+hO68PrQmC7/BYcQClx5++A6\nvRcQAj173oN+/R6DwWDwYWIiIgokHNJFRD4XGWnFsGEvYsKEyahfvyE8BcdhO7oCzrx9EIr3ivcV\nQsCZvQuu0/sQExOL8eMnsdhDRER+SafT46mnngUAODK3QYjK93GKswC2Y2vgyktFZEQkXnttPJ54\n4ikWe4iI6Lqw4ENEqmncuCneeOMdDB48FCFmE1y5f8CW9hM8hScrnd9HCKV0KNghJCTUxfjxkxAd\nHaNCciIioqvTrFlzdOnSHYozH67TBypcJ4SA68wh2I6tguI4g44d78Q770xHixatVEpLRESBhEO6\niEhVsiyjS5fuaNfuNixfvhRr1qwqOfVsSB0Y4tpCYwgHAAjhhePkFngKM1C/fkOMHDkWoaFhKqcn\nIiL6a48++jh2796Jgrx90IXWg2wIg+IuhiNzK7zF2QgJseCpp55Fu3a3qR2ViIgCCHv4EJFfMJtD\n8Nhjg/D229PRsuXN8BZnwZa2Eo7snVA8dthPbISnMANNmjTD6NGvs9hDRES1htkcgoEDBwPCC0fW\nNrjz02BLWwlvcTZat26Ld999n8UeIiKqduzhQ0R+JSEhEa++OhYpKTvx7bdzkJt7CO4zfwIQaNGi\nFUaMGMk5DYiIqNa59dbb0KbNrdi9ewe8tlwYDAY8/uQw3HlnV0iSpHY8IiIKQCz4EJHfkSQJbdrc\nihYtbsbPP/+EH374Dq1atcbQocOh0+nUjkdERHTNJEnCwIGDkXbsKOLrxOPpp4chJiZW7VhERBTA\nWPAhIr+l0+nwwAN9cO+9D0Cj0agdh4iI6LpYrVH4YMYnkGXOqkBERDWPexsi8nss9hARUaBgsYeI\niHyFexwiIiIiIiIiogDDgg8RERERERERUYBhwYeIiIiIiIiIKMCw4ENEREREREREFGBY8CEiIiIi\nIiIiCjAs+BARERERERERBRgWfIiIiIiIiIiIAgwLPkREREREREREAYYFHyIiIiIiIiKiAMOCDxER\nERERERFRgJGEEELtEEREREREREREVH3Yw4eIiIiIiIiIKMCw4ENEREREREREFGBY8CEiIiIiIiIi\nCjAs+BARERERERERBRgWfIiIiIiIiIiIAgwLPkREREREREREAUZb1Ttu3boVf//73zFlyhR0794d\nAHDgwAFMnjwZANCsWTO89dZbAIAvv/wSK1euhCRJGDFiBLp27Xr9yf3AZ599hk2bNgEAFEVBXl4e\nVq1ahR49eqBOnTrQaDQAgBkzZiAuLk7NqNVu6dKl+Mc//oGkpCQAQKdOnfDCCy9c9jkQaDweD15/\n/XWkp6fD6/VizJgxaNeuHQYNGgSbzQaz2QwAGDt2LFq2bKly2poxZcoUpKSkQJIkTJgwATfffLPa\nkWrc+++/jx07dsDj8eC5557D2rVrsXfvXkRERAAAhgwZgm7duqkbsgYkJyfj73//O5o0aQIAaNq0\nKZ599lmMGTMGXq8XMTEx+J//+R/o9XqVk1a/RYsWYfny5WWXU1NT0bJly4B/nR86dAjDhw/H4MGD\nMXDgQGRmZlba3suXL8fs2bMhyzIeffRR9O/fX+3oQeVajsXId4Jx/+ivrva9jHzr4uOpVq1asV1U\nZLfbMW7cOJw+fRpOpxPDhw/HjTfeyDbxAw6HAw8++CCGDx+Ojh07Vq1NRBUcP35cPP/882L48OFi\n7dq1ZcsHDhwoUlJShBBCjBw5Uqxfv16kp6eLvn37CqfTKU6fPi3uuece4fF4qrJZv7Z06VLxxRdf\nCCGE6N69uygqKlI5Uc1asmSJmDZt2iXLK3sOBKLFixeLSZMmCSGEOHTokHjkkUeEECWP/+DBgyom\n843k5GQxbNgwIYQQhw8fFo8++qjKiWre5s2bxbPPPiuEEOLMmTOia9euYuzYsRXeAwPVli1bxEsv\nvVRh2bhx48SPP/4ohBDigw8+EHPnzlUjmk8lJyeLyZMnB/zrvLi4WAwcOFBMnDhRzJkzRwhReXsX\nFxeLu+++WxQUFAi73S4eeOABcfbsWTWjB5VrORYj3wnG/aO/utr3MvKtyo6n2C7qWrFihfj3v/8t\nhBDixIkT4u6772ab+ImZM2eKhx9+WCxZsqTKbVKlIV0xMTH49NNPERoaWrbM5XLh5MmTZd9idO/e\nHZs3b0ZycjI6d+4MvV4Pq9WKxMREHD58uCqb9VsejwfffvstBg4cqHYUVV3uORCI+vTpg/HjxwMA\nrFYrzp07p3Ii39q8eTPuuusuAMANN9yA/Px8FBUVqZyqZrVv3x7/+Mc/AABhYWGw2+3wer0qp1JP\ncnIyevbsCSCwX+vl/e///i+GDx+udowap9fr8cUXXyA2NrZsWWXtnZKSglatWiE0NBRGoxG33HIL\ndu7cqVbsoHMtx2LkO8G4f/RXV/teRr5V2fEU20Vd999/P4YOHQoAyMzMRFxcHNvEDxw5cgSHDx8u\nGz1Q1TapUsHHZDKVDVc67+zZswgLCyu7HBUVhdzcXOTl5cFqtZYtt1qtyM3Nrcpm/dbPP/+MO++8\nE0ajsWzZpEmTMGDAAMyYMQNCCBXT1ZytW7diyJAheOqpp7Bv377LPgcCkU6ng8FgAADMnj0bDz74\nYNl1H3/8MZ544gm8+eabcDgcakWsUXl5eYiMjCy7HIiv64tpNJqyITyLFy9Gly5doNFo8M033+DJ\nJ5/Eq6++ijNnzqicsuYcPnwYzz//PAYMGICNGzfCbreXdSMN5Nf6eX/88Qfi4+MRExMDILBf51qt\ntsL+DECl7R0M+3d/di3HYuQ7wbh/9FdX+15GvlXZ8RTbxT889thjGDVqFCZMmMA28QPTp0/HuHHj\nyi5XtU3+cg6fRYsWYdGiRRWWvfTSS+jcufMV73e5IkdtLX5c6f+wZMmSCmPkX375ZXTu3Bnh4eF4\n8cUXsWrVKtx7772+jlxtKnvsDzzwAF566SV069YNu3btwtixY/Hll19WuE1tbeuLXant586di717\n9+Lzzz8HADz55JNo1qwZkpKSMGnSJMydOxdDhgxRI7ZPBUpbX401a9Zg8eLF+Oqrr5CamoqIiAg0\nb94c//73v/Hpp5/izTffVDtitWvQoAFGjBiB++67DxkZGXjyyScr9G4KhvZfvHgx+vbtCyB4X+fn\nBdr+vTao7mMx8h22gf9i26ir/PHU3XffXbac7aKe+fPnY//+/Rg9enSFdmCb+N53332HNm3aoF69\nepVefy1t8pcFn/79+1/VJIwXD2vJzs5GbGwsYmNjkZaWdsny2uZy/webzYasrCzUrVu3bNlDDz1U\n9neXLl1w6NChWl3w+avnQNu2bXHmzBlERkZW+hyo7S73+BctWoS1a9fin//8J3Q6HQCgV69eZdf3\n6NEDP/74o89y+lJsbCzy8vLKLufk5JT1fAhkv/32Gz7//HN8+eWXCA0NRceOHcuu69GjR9lEqYEm\nLi4O999/PwAgKSkJ0dHR2LNnDxwOB4xGY8C81q8kOTkZEydOBBA8r/PyzGbzJe1d2ftAmzZtVEwZ\nuK73WIx8J1j3j7VFZe9l5HsXH0+xXdSVmpqKqKgoxMfHo3nz5vB6vQgJCWGbqGj9+vXIyMjA+vXr\nkZWVBb1eX+XXSbWdll2n06FRo0bYvn07gJJhTp07d8btt9+O9evXw+VyITs7Gzk5OWjcuHF1bVZ1\nBw4cQKNGjcouFxYWYsiQIXC5XACAbdu2lZ3ZJpB88cUX+OGHHwCUnAHBarVCr9dX+hwIRBkZGZg/\nfz4+/fTTsqFdQggMHjwYBQUFAEo+IAZi2wPAHXfcgVWrVgEA9u7di9jYWFgsFpVT1azCwkK8//77\n+Ne//lV2Vq6XXnoJGRkZAAK7vZcvX45Zs2YBAHJzc3H69Gk8/PDDZc+BQH6tAyUfmkNCQqDX64Pq\ndV5ep06dLmnv1q1bY8+ePSgoKEBxcTF27tyJdu3aqZw0uF3uWIx8Jxj3j7VJZe9l5FuVHU+xXdS1\nfft2fPXVVwBKhqXabDa2ico++ugjLFmyBAsXLkT//v0xfPjwKreJJKrQR2v9+vWYNWsWjh49CqvV\nipiYGHz11Vc4fPgw3nzzTSiKgtatW5dNajtnzhx8//33kCQJr7zySoVvxWu7VatWYdOmTRWGdM2e\nPRvfffcdDAYDbrrpJrzxxhuQJEnFlNUvKyurrLufx+MpO+3o5Z4DgWbmzJlYsWIFEhISypbNmjUL\na9aswZdffgmTyYS4uDi89957MJlMKiatOTNmzMD27dshSRImTZqEG2+8Ue1INWrBggX45JNP0LBh\nw7JlDz/8ML755huYTCaYzWZMnToVUVFRKqasGUVFRRg1ahQKCgrgdrsxYsQING/eHGPHjoXT6URC\nQgKmTp1a1tMt0KSmpuKjjz4qG7b6448/BvTrPDU1FdOnT8fJkyeh1WoRFxeHGTNmYNy4cZe098qV\nKzFr1ixIkoSBAweiT58+ascPGtd6LEa+E2z7R391Le9l5DuVHU9NmzYNEydOZLuoxOFw4PXXX0dm\nZiYcDgdGjBiBli1bBs1xnr/75JNPkJiYiDvvvLNKbVKlgg8REREREREREfmvahvSRURERERERERE\n/oEFHyIiIiIiIiKiAMOCDxERERERERFRgGHBh4iIiIiIiIgowLDgQ0RERORjhw4dwl133YVvvvnm\nkus2bdqEfv364W9/+xv+93//V4V0REREFAhY8CEKMoWFhXjzzTfRu3dv9O/fH/369cNPP/1U7dtZ\ntmwZAGD//v145513AACHDx/G3r17q31bRES1ic1mwzvvvIOOHTtWev27776LTz75BN9++y02btyI\nw4cP+zghEVWXEydOoEuXLpcs79KlC06cOFHpfXJzc/Hyyy8DALKzs7F582YAwNKlS9GpUycMGjQI\ngwYNQv/+/fHxxx9Xuo4NGzbgs88+q6ZHQUS1FQs+REFm2LBhaNSoEb7//nssWrQIH3/8MT799NOy\ng4nqkJ2djfnz5wMAmjdvjjfeeAMAsHr1auzbt6/atkNEVBvp9Xp88cUXiI2NveS6jIwMhIeHIz4+\nHrIso2vXrtX6/kxE/i8mJqaskJOcnIwtW7aUXdepUyfMmTMHc+bMwbx587Bp0yasW7fuknV06dIF\nL7zwgs8yE5F/0qodgIh8Z+PGjfB4PBg8eHDZsoSEBIwcORKffvop/vnPf+KFF15Ap06dcOLECTz+\n+OPYsGEDjhw5gkmTJkGj0aCoqAivvPIKOnfujE8++QTnzp1DVlYWjh8/jg4dOuCNN97Aa6+9hkOH\nDmHMmDF45JFH8NFHH2HMmDH45ptvYLFYkJ2dje+++w6rV6+GJEnIyclB//79sXbtWmg0GvX+QURE\nPqDVaqHVVn4IlpubC6vVWnbZarUiIyPDV9GIyIe2bt2KSZMmoU6dOjh8+DC0Wi2+/PJLnD59Go8/\n/jjmzp2Ljz76CEIIREREIDw8vML9dTod2rRpg6NHj6JJkyZ44YUX0LRpUzRp0gSxsbHYtGkTZsyY\ngZSUFEyZMgU6nQ7h4eGYPn06LBYLZs6ciZ07d8LhcKB9+/YYM2YMJElS6b9BRDVBEkKI6lpZbm5h\nda2q1oiMNOPsWZvaMagSbBv/xbbxX2wb/+UvbRMTE6p2hIDxySefIDIyEgMHDixbtnPnTsyaNats\n7p5FixYhIyMDI0eOvOx6hBD8kEZERESXYA+f66TVsjeCv2Lb+C+2jf9i2/gvtk1wiI2NRV5eXtnl\n7OzsSod+lSdJUlB+6ebvYmJC2S5+hm3in9gu/odt4p+q8qUb5/AhIiIi8hN169ZFUVERTpw4AY/H\ng3Xr1uGOO+5QOxYRERHVQuzhQ0RERORDqampmD59Ok6ePAmtVotVq1ahR48eqFu3Lnr16oXJkyfj\ntddeAwDcf//9aNiwocqJiYiIqDZiwYeIiIjIh1q2bIk5c+Zc9vr27dtjwYIFPkxEREREgYhDuoiI\niIiIiIiIAgwLPkREREREREREAYYFHyIiIiIiIiKiAMOCDxERERERERFRgGHBh4iIiIiIiIgowLDg\nQ0REREREREQUYFjwIe+l8rAAACAASURBVCIiIiIiIiIKMFq1AxAREVVGCIHCwkJkZZ1CZuYpZGdn\n4v+3d+fhUdb3/v+f9zKTfYUkQFhkEfWAKIu4QEU9YF16ek5bFbS0fm1raxet1qWKnmLPdUS06Lce\n6qltr/ZcR9tTaTVX6/fXHndRgUBYQxa2REASAtmXSWYyy33//pgkgCIo2z1JXo/L8b5n7snMO3kn\nzOSVz+dzd3Z2Eg6H6e7uJhzuJhaLkZ2dQ27uEIYOHUpu7lAKC0cydGie1+WLiIiIiHhKgY+IiCSE\nrq5Oqqp2UVW1k+rqXXz44R46OztP6LFGjhzNtGkzmDp1BqNHj8EwjFNcrYiIiIhIYlPgIyIinujq\n6mLHjm1UVpaxbVsl+/fXHHHc8GdgpxdiJmVg+jMx/ZkYdhIYNoZpgRl/CXOjQZxIF26kEyfSRayr\ngZraGmpqPuSVV4oYMmQoV1wxl7lzP09SUpIXn6qIiIiIyBmnwEdERM4Ix3H44IMqysu3UlFRxu7d\n1TiOEz9oWlip+VgpQ7FSh2KlDMWw/J/qcQ1fGqYvDTg0jcuNRYh21hHtqKG5pY6XX36RN998lX/+\n568we/YcbFsvfyIiIiIysOkdr4iInDZtba2UlZVSVlZKRUUZXV29U7QMzJQh+NMKsNIKsFKGYBjW\nKXtew/LhyxyNL3M0bixMuGk77S07ef753/Lqq/8fX/7yTVx00SWa6iUiIiIiA5YCHxEROWV6R/Fs\n3bqFsrIt7N27p++Y4UvFlz0eK304dmr+px7Bc7IMy09S/hR8uWcTbqykvqGK555bzvr167jttm+T\nmpp6RuoQERERETmTFPiIiMhJ6ewMUF6+ldLSzZSVbTm00LJhYqUVYKcNx0ofHl+Dx8MRNaadQvKw\n6fhzJxKqK2HjxhL27t3D9753F2edNc6zukRERERETgcFPiIi8pnV1e1ny5aNlJZupqpqZ99aPIad\ncmgUT1oBhunzuNKPM/0ZpIy+knBDOY2NlTz22KMsWPBVrrrqak3xEhEREZEBQ4GPiIgcl+M4VFfv\nYsuWTWzevIEDB+r6jpkpQ/Cnj8BOH4GZlN0vQhPDMEnKn4KVmkeobi1/+MN/s3PnDr71re/i8yVe\nSCUiIiIi8lkp8BERkaOKRqPs2LGNjRtL2LRpA+3tbQAYpo2dMRI7fQRW+ghMO9njSk+cnT6c1LOu\nIVS7mvXr19LZGeDOO39EUlL//Zykf1iyZAmlpaUYhsGiRYuYMmVK37E//OEPvPLKK5imyeTJk3n4\n4Yc9rFRERET6KwU+IiLSJxqNUl5eSknJWjZv3tC3Ho9hJeHLHoedPhIrLR/DHDgvH6YvhZTRVxKq\nXUNlZTnLlj3O3XffT1pautelyQBVUlLC3r17WbFiBdXV1SxatIgVK1YAEAgE+O1vf8vrr7+Obdt8\n4xvfYMuWLVx44YUeVy0iIiL9zcB5xy4iIifEcRyqqnaybt0aNmwsoaO9HehZjyfnbOyMkVipeRiG\n6XGlp49hWiSPnEVo/zqqq3fxxBP/zr33PkRWVpbXpckAVFxczNy5cwEYP348bW1tBAIB0tPT8fl8\n+Hw+urq6SE1NJRgM6vtQRERETogCHxGRQaqubj9r1rzPmjWraGlpAsCwk+MhT+ZorJSh/WI9nlPF\nMEySR1xCt+mjpqaKxx//Kfffv4ghQ4Z6XZoMMI2NjUyaNKnvem5uLg0NDaSnp5OUlMT3v/995s6d\nS1JSEtdffz1jx471sFoRERHprxT4iIgMIoFAgJKSYtaseY8PPqgGwDB92Flj8WWOiU/XGsAjeY7H\nMAyShk3HsHzU12/jZz97jIceelQjLOS0cl23bz8QCPCrX/2KV199lfT0dG699Va2b9/Oueeee8zH\nyMvLON1lyglQXxKPepKY1JfEo54MDAp8REQGOMdx2LatgvffX8mmTeuJRqOAgZU2HF/WWdgZhQNq\nTZ6TZRgGSfkXAAb19ZU8/fRSfvzjR0hNTfO6NBkg8vPzaWxs7LteX19PXl4eANXV1YwaNYrc3FwA\nZsyYQXl5+XEDn4aGjtNXsJyQvLwM9SXBqCeJSX1JPOpJYjqREE7v8EVEBqjm5iZWrXqX999fSVNT\n/JdL05+JP38svsyzMH0pHleY2Px55+PGutm3r5pnnlnGj370IElJSV6XJQPArFmzWL58OQsWLKCi\nooL8/HzS0+OLhBcWFlJdXU0oFCI5OZny8nLmzJnjccUiIiLSHynwEREZQKLRKKWlm3jvvXcoL9+K\n67oYpo0vaxy+7HGYKUMG1bo8J6N3epfrRNi1awf/+Z8/584778W29dIpJ2fatGlMmjSJBQsWYBgG\nixcvpqioiIyMDObNm8c3v/lNvv71r2NZFlOnTmXGjBlelywiIiL9kOEePnH8JA3GYV8a7pa41JvE\npd6cevv317Jq1busXv0eHR3xs2yZKUPwZY/Dlzkaw/R5XGH/5boxgvtWEeusY+bMS/n2t7+PaZ75\ndY4S5edGc/oTUyJ8b8iREuVnVg5RTxKT+pJ41JPEpCldIiKDSFdXFyUlxaxa9S4ffFAFgGH58eVO\nxJc1Dis52+MKBwbDsEgZOYvghyspKSkmOzuHBQsWel2WiIiIiMgxKfAREelHotEoFRVlrFu3mo0b\nNxCJhAHiCzBnj8VOL8QwLY+rHHgM0yZl1OV07XmT11//O0OH5jF37ue9LktERERE5BMp8BERSXCO\n4/DBB1UUF69m/fq1BALxIbamPx1/3jn4ssZi+lI9rnLgMyx/PPTZ+yZ//OPzDBkyhKlTtbaKiIiI\niCQmBT4iIgkoGo2yfXslmzatZ/PmjbS1tQJg2Mn4cs7Gl3UWZnKuFmA+w0x/OikjLyf44ds899wv\n+PGPH2HcuAlelyUiIiIi8jEKfEREEkRDQz3btlVQWVlGWdlWgsEuAAwrCTtrLL7M0VhpBRjGmV8w\nWA6xUnJJHnEpwZpV/PznP+ORR/6N/PwCr8sSERERETmCAh8REQ84jkNd3X727PmAXbt2UFlZTmNj\nQ99xw5eKL2cidkYhVmqeQp4EY2cUkjRsGoEDG/m///cJHn74p6Sn6+xVIiIiIpI4FPiIiJxGruvS\n2trKwYN1HDhQx4ED+9mzZzd79uwmHO7uu59h+rDTC7HSCrDSCjD9mZquleD8OWfjRjo5eHA7//Ef\nT3H//Yvw+fxelyUiIiIiAijwERlwIpEIHR3tBAIBgsEuuro66ezspKuri1AoSHd3N93dIbq7uwmH\nw0QiEaLRSN82Go3hOA6xWJRYzMFx4tdd18VxHZxYfB9cHCe+dd1PrieeWRgYxqGLz2fjumCaJqZp\nYlkWpmlhWfF9y7J7tha2bfdsfdi23XexLBuf78jbDr/PJx3rfezex+29mKaJYZhYVnx7eNbiuhz2\nNYkRjUaJRqN0d4cIBoMEg0FCoSCdnZ20tbX2XVpbW2loqD8i2On5qmAmZWJnjcBKzsVKycVMztEo\nnn7In3cBTqSLqqqd/OY3v+SOO+7ENNVHEREREfGeAh+RfiASCdPW1nZYmBDfb29v67ve0dFOR0c7\noVDoxJ/IMHtCBwMMsyet6bkYh7bxLOTw24+jLxFycXEh2A24Pbf3XFwHXBfXdQ5d7+cM04fhS8XO\nyMf0Zxy6JGdhmD6vy5NTwDAMkodfTDAaZMOGdfzpT0NYsGCh12WJiIiIiCjwETnTotEooVCoZ0RI\ngEAgQGdnoG+/vb2N9vb2nm0bbW1tdHV1HvtBDRPDSsKwkrFSszDspPh104dh+TEsP5i++HXTBtM+\ntDVMMKyesCdxphDFRxHFQyDcWPy6GwPXiYdCrhO/3Tls3z3avoPb83FHfmw8VHJ7n+Pw0ImPD1ky\n+kIw81AwZto9X+P41xbTh2mnYNjJ8Yupf2IHA8O0SBk5m649b/H6639nyJChzJt3jddliYiIiMgg\np99GJKG4rts3VSYUCvVMOYpPOzo0/Sg+raZ3ek0sFg8DHMfpu7iuQ1paEoFACNd1e8KD3oEmbt9z\nHZqKdPh9Pr7vug6O437keWJ9+731xKf6xIhGI321h8PxS3d3N8FgkEgk/Km/HvEQJwkrtaAnREjB\n7A0TeoIF004G059QYc2pEP98rPhAImwG1mcnA41hJZEy6nK69r7Jiy++QE5OLjNmzPS6LBEREREZ\nxBT4yGnhOA6BQICOjvhIlY6ODjo7OwgEAj2Xjp71ZXovnQSD8fVlHCfmdfmnjmFhmFZ8BI1pY1jp\nWL74iJD46BB/zwicpENbOyke6FhJWtNFpB8x/emkjLyc4Idv86tfLSc5+X4mT57idVkiIiIiMkgp\n8JHPxHVdOjo6aGlporm5mZaWZlpbW2htbaW1taVnodoWAoGOvlEyx9M7FcawfJCUgtUzTSYelthg\nWhhGb2hi9YQg8fVljN4pNoevMYPR81/vmBCDI4eHfHSsiPGRm40jjxlGz3Se3mMfXc/mKFN9eqZJ\nDbRRNyJybFZKLskjP0dw33ssX/409933EGeffY7XZYmIiIjIIKTAR44QiURobm6iqanxiEtzcxON\nTY20NDcRjUY/8eMN0wYrGTN5SM8oleRD68kcPorl8HVlFIqIyABipxWQUngZwdpV/PznT/LAA//K\nmDFneV2WiIiIiAwyCnwGCdd1CYVCfVOsWltbaG5u6rk009LSRGNjI+3tbZ84Mie+bkwmdkpqfC0Z\nX2rPOjIpPQvVpsRH6YiIDHJ2RiHJwy8huL+Yp556nIceWszw4SO8LktEREREBhEFPqeR67q0tDRz\n8OABGhsbaGpqpLGxgZaW5r6FfMPhbrrD3TgxB9u2sSwL07SwbQufz4/fH7/07vfeJ761MQzjiMWD\nHcfpOQNU/CxQoVCQrq4u2trajr1YsGFg2KmYKXkYvlRMXxqmL61v37BT42vRiIjIp+LLGoPrRAgc\n2MCyZUt44IFHKCgY5nVZIiIiIjJIKPA5haLRKPv27WXXrp1UVe2gqmoXra0tR7+zYcbXpeld0Ncw\nwQ3FTwvdc3po14nFT0N9EuLr49gYdhqWf0jPWZ2SekbmpPaM0kmNT7vSAsEiIqeUP2cCOFFa6rfw\n+OM/5d57H2LUqNFelyUJYMmSJZSWlmIYBosWLWLKlEMLfNfV1fGjH/2ISCTCP/zDP/Bv//ZvHlYq\nIiIi/ZUCn5MUiUTYunULGzasY9OmDXR1dfYdM+xk7IyRmElZJzxaxnXdeOjjxnoCICceBvVs40/0\nkQWEexc9Nm2tjyMi4jH/kHPBsGg/uJGlS/+Ne+55gAkTJnpdlniopKSEvXv3smLFCqqrq1m0aBEr\nVqzoO7506VK+8Y1vMG/ePH7605+yf/9+RozQlEARERH5bBT4nADXddm+vZLVq99jy5aNdHV1AWDY\nKfiyx2Ol5mGlDMXwpZ104GIYBhg2YGNoRpWISL/kzz0bw/IR3L+OZcuW8IMf3MPkyRd4XZZ4pLi4\nmLlz5wIwfvx42traCAQCpKen4zgOGzdu5OmnnwZg8eLFXpYqIiIi/ZgCn88gEAiwZs17rFz5FgcO\n1AFg+FLx5Z6DL2MUZsoQjagREZGj8mWdhWH6CNau4ZlnlnH77d9n5sxLvC5LPNDY2MikSZP6rufm\n5tLQ0EB6ejrNzc2kpaXx+OOPU1FRwYwZM7j33ns9rFZERET6KwU+n8LevXt4881XWbduTfyU5IaJ\nnXVWfDRPylCFPCIi8qnYGYWkjLqcUM37PPfcf7B3726+/OWbsCwN4RzMDj87puu6HDx4kK9//esU\nFhby7W9/m5UrV3LFFVcc8zHy8jJOc5VyItSXxKOeJCb1JfGoJwODAp9P4DgOW7du5rXX/s6OHdsA\nMP0ZJOWPx5c1FsNO8rhCERHpj+y0AlLGzCVUu5r//d//xwcfVHHHHXeSlZXtdWlyhuTn59PY2Nh3\nvb6+nry8PABycnIYMWIEo0fHF/e+9NJL2bVr13EDn4aGjtNWr5yYvLwM9SXBqCeJSX1JPOpJYjqR\nEE6nZfqI7u4Q77zzBg8/fB//8R9PsWPHNqy0AlJGzSF13HX4h5yrsEdERE6KlZxN6lnzsDNGsmPH\nNh59dBE7d273uiw5Q2bNmsVrr70GQEVFBfn5+aSnpwNg2zajRo1iz549fcfHjh3rVakiIiLSj2mE\nT4+Wlmbeeut1Vq58K36mLcPElzUWX+45WMn6q6uIiJxahuUnuXAWkeYdtNWX8uST/851132R66//\nZ5KS9IeFgWzatGlMmjSJBQsWYBgGixcvpqioiIyMDObNm8eiRYt48MEHcV2XiRMnctVVV3ldsoiI\niPRDhnv4xPGT1N+GfbmuS3X1Lt5++w1KStbiODEMKwlfzgR8ORMw7RSvSxQRkUEg2lVP9/61OJEu\nhgwZyi233MqFF047Yo24RBlerTn9iSkRvjfkSInyMyuHqCeJSX1JPOpJYjqR92CDcoRPMNjF2rWr\neeedt6ip+RAA059JUu45+LLGYJiD8ssiIiIesVPzscZdS7ixgqbmHSxf/hRTpkzlllu+Tn5+gdfl\niYiIiEg/NGiSDcdx2LFjG+vWrWHt2jWEw91gGNgZI/FlT8BKK9DZtkRExDOG6SMp/0LsrLF0H9jI\n1q2bqajYyiWXzGLevGvJy5vsdYkiIiIi0o8M6MDHcRyqq3dRUlLM+vXraG9vA8DwpeHPm4gvaxym\nT9O2REQkcVhJWaSMvpJoxz7CDWWsXv0eq1e/x5QpU7jiiquZMuVCTFPnXBARERGRYxtwgU9zcxMV\nFWVUVpZTWVlGR0d87qFhJeHLHo+dORorNQ/D0JtlERFJTIZh4MscjZ0xiligjnDzDrZu3crWrVvJ\nyRnC1KnTmDp1Bueccx62PeBeykVERETkFOjX7xKDwSAffriHvXv3sHfvbj74oJqDB+v6jht2Cr6s\nsfGQJ61AIY+cMU40CE7M6zJEEo9paUH8z8AwDOyMEdgZI4iFWok076C1vZa3336Dt99+g+TkFKZM\nuYCzzz6Xs84ax6hRo/H7/V6XLSIiIiIJoF8EPl1dXWzbVk59/UEaGuppaKjv2z+cYdpY6SOw0wqw\n0oZh+jO1Lo+cUbFQK8Ha1bhhrWrfn/n9foYOHUpjYyPhcNjrcgYcw59BSuEsrORsr0vpV6zkbKwR\nF+O6DrGuBqIdtXQHaikpWUtJyVoATNOksHAk5503mZtuukVTv0REREQGsX4R+PzXf/2ajRtLjrjN\nsJKxUvMxk3OxknOwknMw/BlnNOAJHdxCtOPDM/Z8kvjcSBBwvS5DToLf7+eOO+5g3rx5vPHGGzz3\n3HMKfU4xN9xB1+7XMPr5Gmp2xmiSCy48489rGCZ2WgF2WgGuOxUn3I4TbCYWil/21dSyb9+HzJlz\nJcOHF57x+kREREQkMfSLwKezMwCAL+ds7LRh8TV4LA1Zl8Tiui4Ke/q/oUOHMm/ePADmzZvHSy+9\nxP79+z2uaiBycV1XozBPkmEYWElZWElZ+BgLQKhuA5HWKhxH/x6JiIiIDGb9IvBJSYn/FTjSsotI\nyy4wDAw7DSspC7NndI+ZnIthJ5/RXx6SCy4ED/66K4krUP03Tefq5xobG3njjTf6Rvg0NjZ6XdKA\nZPozSBt/vddlDAhOpItYsBmnZ4RPLBj/nlWYJiIiIjK49YvA59Zbv8XUqTP61u+Jr+FzgI6OWgjU\n9t3PsOPTvOy0YVhpBZi+NA+rlsEopXAWodrVOAp9+q1wOMxzzz3HSy+9pDV8ThPTn0Fy4Syvy+i3\nXNfFCTUT7aglGqjF6W474nheXj7nnTeJgoJhHlUoIiIiIomgXwQ+mZlZzJ4952O3t7a29J2ha+/e\nPVRX76K9/UOi7fF1dUx/BlbacHyZozFThuivnXLaWcnZpI2/XmfpGgDaAN8o8HldyECjs3SdMCfc\nQbhlF9H2fbjRIAC2bTPp/AuYOPE8zjprLGPGjCU9Pd3jSkVEREQkEfSLwOeTZGfnkJ2dwwUXTAXi\nf/Xcv7+WysoyKivL2b59G90tO4m07MTwpWJnjI6HP8k5Cn/ktNIvtCJyKriuSyzYQKRpB9GeEa1p\naWlcMPNzTJ06nUmTppCcnOxxlSIiIiKSiPp14PNRhmFQWDiSwsKRzJt3LdFolG3bKigpKWbjxvWE\nmrcTad6O6c/ElzMBX9ZZWvxZREQSUjSwn+6GMpxQCwATJkzgqquuYcaMmdj2gHr5FhEREZHTwHDj\npxY6JRoaEnfdkkgkTHn5VtauXcOmTeuJxWIYhoWVORp/zgSslCFelygiIoITDtB9cDPRQC2GYTBt\n2gzmzbuWyy6bQWNjwOvyyMvL8LoEOYpEfg82WOXlZagvCUY9SUzqS+JRTxLTibwHGzR/IvT5/Eyd\nOoOpU2fQ3t7G6tXvsXLlWzQ07CbathszeQj+IedgZ4zEMEyvyxURkUHGdWKEm7cTbqwEN8bEieey\ncOFtjBw5CtBZt0RERETksxk0gc/hMjOzuPbaf+Lzn7+eyspy3n77dUpLNxOqXYPhS8WfMxFf9jhN\n9xIRkTMiFmwmtL8YJ9xBZmYW8+d/lUsumaWQR0RERERO2KAMfHqZpsnkyVOYPHkKBw/W8cYbr7Jq\n1bt0128h3FiOnTUOf+5ETL/OeCIiIqee67pEWqvpPrgJA5e5c6/hX/7lBlJTU70uTU6zJUuWUFpa\nimEYLFq0iClTpnzsPk899RRbtmzhhRde8KBCERER6e8GdeBzuIKC4SxceBtf+tKNvPvu27z55mu0\ntuwk0rILO6MQX+45WClD9ddWERE5JVwnSujABqJte0hNS+M73/4B559/gddlyRlQUlLC3r17WbFi\nBdXV1SxatIgVK1YccZ+qqirWr1+Pz+fzqEoRERHp7xT4fERaWjrXXfdFrr76OjZsWMdrr/2dvXt3\nE+2owUzOwZc9AV/WaAxTb8BEROTEOOEOgjWrcbpbOWvsOL733R8ydGie12XJGVJcXMzcuXMBGD9+\nPG1tbQQCAdLTD40oXrp0Kffccw+/+MUvvCpTRERE+jkFPp/Atm0uuWQWF198Gbt27eD11/+XLVs2\n0n1gPeH6LdhZY/BlT8BKzva6VBER6UdiwSaC+97FjYW58sq5LFjwNY3iGGQaGxuZNGlS3/Xc3Fwa\nGhr6Ap+ioiJmzpxJYWHhp35MnT0tMakviUc9SUzqS+JRTwYGBT7HYRgGEyeey8SJ59LS0sz776/k\n3XffpqWlikhLFWZSJnbGKOyMUZhJWZryJSIinyjaeYBQzSpwY/yf/3M7l19+pdclSQJwXbdvv7W1\nlaKiIv7rv/6LgwcPfurH0OlzE49Oa5x41JPEpL4kHvUkMem07KdZTk4uX/zil7n++n9m69YtrF79\nHmVlWwg3VhBurMD0Z2ClF2KlDsVKGYppJ3tdsoiIJIhIRw2h2jVYpskd372b6dMv8rok8Uh+fj6N\njY191+vr68nLi0/pW7t2Lc3NzXz1q18lHA7z4YcfsmTJEhYtWuRVuSIiItJPKfA5AZZlMXXqdKZO\nnU56us3bb69iw4a1lJZuIdK8nUhz/H6mPwMzZShWUiaGLw3Tl4bhS8Owkj7zSCDXdcCJgevE93HA\ndcB1wTAAo2drYpg2mLZGG4mIJIhI625CdSX4/X7uuute/uEfJntdknho1qxZLF++nAULFlBRUUF+\nfn7fdK5rrrmGa665BoCamhoeeughhT0iIiJyQhT4nKSUlBRmzryEmTMvobu7mw8+qGLXrh1UVe2k\nqmoXobbdRD/6QUZPKGNYPcGMFQ9regKcvkDHccCN4TpRwP34kx+HYfrij2/6MOwkDCsZw45fTDsF\nw07B8KVi+lK1CLWIyGkSbqmi+8AGUlPTuOeeHzN+/ASvSxKPTZs2jUmTJrFgwQIMw2Dx4sUUFRWR\nkZHBvHnzvC5PREREBgjDPXzi+EkajPP8jjW/0XEc6ur2U19/kMbGBpqaGmhsbKSlpZlwOEw43E13\ndzfhcBjHcbAsC9u2ME0Ly7Lw+/09lyR8Ph8+nx+fz8aybGzbxrIsDMPAcVwcJ4bjODhOjO7ubkKh\nEMFgkFAoSGdngM7OzmN+Hobpi48+8qVi+tLiIVDvqCQ7NR4UacSQiMhnEmnbQ2j/WjIyMrn//ocZ\nOXLUCT9Wosyn1yKOiSkRvjfkSInyMyuHqCeJSX1JPOpJYtIaPgnGNE0KC0dSWDjS61KIRqN0dHTQ\n0dFGe3sbLS0ttLQ009zcRHNzfNvU1Eh3oJXY0R7AMDHsntFAvlRMOxXDlxLf9o4WspMwDPNMf2oi\nIgkp0lFDaP86UlJSuffeh04q7BERERER+awU+AwStm2Tk5NDTk7OJ97HdV06OztpamqkqamhZxsP\ngpqbG2lsbKSjo/4Yz2LERwL1TR2LTyMz7eT4ukWW/4gtlk8BkYgMSNHOA4Rq1+D3+7nnngcYPXqM\n1yWJiIiIyCCjwEf6GIZBeno66enpjBlz1lHvE4mEjxgd1NLSTGtrC62trbS1tdLa1kpbawvhzpZP\n95ymDaYPw/LHt6Z9aNFp0wbDxjCt+AgjwwbTjO8T38YvBkbfotWHbePPcGj30E78/h+7+fApa4cf\nP/xx+chzmX1b4/B6FGSJDFqxrkZCNauwTJM77/wREyZM9LokERERERmEFPjIZ+Lz+cnPLyA/v+CY\n9+vuDtHW1kZHRzvt7W10dHTQ2RkgEAgQCHQQCAQIBrvo6uqkq6uLrq4ugsF2nFO3pJTHjJ6gygLT\n6gmr7J5Ayxcf3WT6Di2obfaMfrL9h0ZImfrxFOlvYt1tBGvew8Dhu9/9IZMmne91SSIiIiIySOk3\nSjktkpKSyc9PPm4wdDjXdYlGI4RCoZ6Fp4OEw2EikQiRSHwbDoeJxWLEYjGi0WjPfhTXdXsWrY5f\nXNclNdVPZ2c3juMc8Ry9Zzxz3d7rRx47dHvvvtPz+PEzqMWf48iFsg+vqfcSrz1Md3c3kUiYUChA\nJBT+1F8Pw7TBAmumpgAAGgNJREFUSoqfVc1K7lkn6fCzrB0665phWJ/6cUXk9HAiQUL73sWNhfnG\nN+9g2rSLvC5JRERERAYxBT6SMAzD6DkTmZ+MU3ASmERcXT4WixEKBQkG45eurk4CgUDPmdQCPQtr\nx0dFtbe30dbWRnt7K1HnqEtp9zm0PlLPxU7qWSfJ3zN6yNez7Z0md/i0OTOhp6D1hXSuA66D6zrg\nxg7tOzHAwXU+cvsR+70fG+vZj4d34PQkfw7xgK/3uY4y0swwPjKV0DxsxJbdM2LL3xPCpcR7ojPb\nDRpuLEJw37s4kS6+/OWbmDXrcq9LEhEREZFBToGPyBlkWRZpaemkpaV/6o9xHIfOzgBtbW3xdZJa\nW44Ig3pv7+hoJxBoOmLU0qdmGPFRQr1hxmHrExk96xL13PGYayQdHm+4h/0/vnEPHTlspBU9I6gO\nhS+9+4eFNf2RYcQXLfelYfgzMA+/JGUmdMgmn43rOgRrV+N0tzJnzlVcf/0/e12SiIiIiIgCH5FE\nZ5omGRmZZGRkHve0zr3hUEdHB4FAR8/aSF10dnbS1dXZN12uu7t32000GiEajRKJhPumozmO0zdN\nLT51LdYzpc3tmzIH8WluvVPfwOXjUZPRkxXFt0bPgtaWZfV9bqZpYtk2lmlhWRamaWLbNpZlYVl2\n375t2z0XX89tNj7foeuHjlnYtg+fz4dlWfh8hx+Pf1zv4/XuW1a8jt7ajjYyJ/41iRKNxnq2Ubq7\nuwkGuwgGg4RCoSOCuba2+GLmLS3NxIKNH/myWJjJ2VjJuVjJuZgpQzD9GRoR1A+5rkt33QZinQeY\nMuVCFi68TX0UERERkYSgwEdkADk8HEpkiTjd7nSJRqM0NNRz8GAdBw4coK6ulj17dlNbW0Mk2ESk\n536GnYKVVoCdWoCVVoDpS/W0bvl0wo0VRNo+YMyYsdxxx119YaaIiIiIiNcU+IiInEa2bTN8+AiG\nDx9xxO3hcJh9+z5kz55qdu3awbZtFXS07SHatgcAMykLO70QO2MkZnKORo0koEjrbsKN5eQOGcrd\nd99PcnKy1yWJiIiIiPRR4CMi4gG/38/48RMYP34C//iPn8dxHGpra9i2rZzKyvgl3FRJuKkSw5ca\nD38yR2OlDFX4kwCinQcIHSghJSWVH93zY7Kysr0uSURERETkCAp8REQSgGmajBo1mlGjRnP11dcR\nCoUoL9/Kpk3rKS3dTLBlF5GWXRi+NHyZY7CzxmAlZXld9qAUC7USqlmNZVrcdde9jBhR6HVJIiIi\nIiIfo8BHRCQBJScnM2PGTGbMmEk0GmXbtgrWrl3Npk0b6O4Z+WMm5eDLHosvcwyGneR1yYOCEwkS\nqnkP14nwzW//gHPOOc/rkkREREREjkqBj4hIgrNtm/PPv4Dzz7+A7u5utmzZyNq1qykr20r3wU10\n12/BTi/Elz0WK22YTvl+mrixCMF97+JEuvjKVxZwySWXeV2SiIiIiMgnUuAjItKPJCUlcfHFl3Hx\nxZfR1tZGcfEqVq1ayf79+4h27MOwU+OjfrLGYvrTvS53wHCdGMHaVTjdrcyZcxXXXfdPXpckIiIi\nInJMCnxERPqprKwsrrnmej7/+evYvbuaVaveZe3a1YQaKwg3VmClDcOXPQ47vRDD1OnCT5TrOoT2\nFxPrPMiFF05n4cLbtHC2nLQlS5ZQWlqKYRgsWrSIKVOm9B1bu3YtTz/9NKZpMnbsWB577DFMUyP3\nRERE5LNR4CMi0s8ZhsG4cRMYN24C8+d/lQ0bSnjvvXfYtWsHsc4DGFYSdtYYfFnjsJJ1NqnPwnVd\nuuvWE+2o4ZxzzuO7370Ty1J4JienpKSEvXv3smLFCqqrq1m0aBErVqzoO/6Tn/yE559/nmHDhnHX\nXXfx/vvvM2fOHA8rFhERkf5IgY+IyACSlJTMrFmXM2vW5dTV1fL++ytZvfp9Opp3EmneiZmciy97\nHL7M0RiW3+tyE5rrunTXlxJp282YMWO566578fn0NZOTV1xczNy5cwEYP348bW1tBAIB0tPj0zCL\nior69nNzc2lpafGsVhEREem/ND5YRGSAGj68kJtu+ipPPfULvv/9e5gyZSpudwvdBzYQ2PUXgjWr\niXbU4rqO16UmpHDTNiLN2xk2bAT33PNjUlJSvS5JBojGxkZycnL6rufm5tLQ0NB3vTfsqa+vZ/Xq\n1RrdIyIiIidEI3xERAY427aZPv0ipk+/iJaWZoqLV7F69XvU1fUu9JyMnTEaX9ZozOQhWp8G6G6s\nJNywlZycXO677yEyMzO9LkkGMNd1P3ZbU1MTd9xxB4sXLz4iHPokeXkZp6M0OUnqS+JRTxKT+pJ4\n1JOBQYGPiMggkpOTy3XXfZFrr/0n9uzZzZo177N27Wo6W3YSadmJ6UvDzhyNnTkGMylr0IU/rusS\nbthKuGkbublDuP/+ReTmDvG6LBlg8vPzaWxs7LteX19PXl5e3/VAIMDtt9/O3XffzezZsz/VYzY0\ndJzyOuXk5OVlqC8JRj1JTOpL4lFPEtOJhHAKfEREBiHDMBg7dhxjx45j/vyvUllZxrp1xWzevIFQ\n0zbCTdsw/RnYGSOxM0ZhJucM+PDHdR26D2wk0lpNQcEw7r//YYU9clrMmjWL5cuXs2DBAioqKsjP\nz++bxgWwdOlSbr31Vi6//HIPqxQREZH+ToGPiMggZ9s2U6ZMZcqUqWRk+HjnnVWsW1dMWdkWwj3h\nj+FL6wl/CrFShmIYA2sJuPip19cRbd/LqFFjuPfeB8nMzPK6LBmgpk2bxqRJk1iwYAGGYbB48WKK\niorIyMhg9uzZ/OUvf2Hv3r289NJLAHzhC19g/vz5HlctIiIi/Y0CHxER6ZOcnMyMGRczY8bFdHd3\nU15eysaNJWzZsolQ8w4izTswrCSs9OHY6YXYacMwLJ/XZZ8UJ9pNaH8xsc4DTJhwNnff/QCpqWle\nlyUD3H333XfE9XPPPbdvv7y8/EyXIyIiIgOQAh8RETmqpKQkpk+fyfTpM4lEImzfXsmWLRvZvHkj\nra17iLbtAcPESs2Lhz/pwzH9/WuBv2hXA6HaNbjRIOeffyHf+95dJCUle12WiIiIiMhJU+AjIiLH\n5fP5OP/8Czj//AtYuPA29uzZzZYtGykt3cyHH+4h1nmQ7oNg+jN7Rv+MwEodimFYXpd+VK7rxqer\nNZRhGPCVr8zn2mv/CdMcWFPVRERERGTwUuAjIiKfyeELPn/pSzfS0tLM1q1bKC3dTGVlGeHeqV+m\njZU2DCtteHz0jy/V69IBcCJBQnUlxDrryMrK4Y47fsA555zndVkiIiIiIqeUAh8RETkpOTm5zJlz\nFXPmXEUkEmbHju1s3bqFrWVbqD9YQ7Sjhm7ATMrqCX+GYaXkYZhndvSPGwsTbtpGpGUnrhNj8uQp\nfOtb3yMzM/OM1iEiIiIiciYo8BERkVPG5/MzefIUJk+ewi18nYMH6ygr20pZ2Ra2b68k0rydSPN2\nMKz42j9pw7DSCjCTsk/bad9dJ0q4eReR5m24sTDZ2Tn8y7/cwOzZczSFS0REREQGLAU+IiJy2hQU\nDKegYDhz536ecDjMzp3bKSsrpbKyjNraGmKdBwDiZ/5KGYqZOhQrZShWcu5JjQBynRixrnqiHfER\nRm6sm9TUNL7whRu46qqr8fv9p+pTFBERERFJSAp8RETkjPD7D43+AWhtbWHbtgoqK8vZtq2C5uZa\nCNTG72yYmElZmP5MzKTM+NafgWEnYRg2mBaGYeK6LjgRnEgnbqQLJ9JFLNhALFCH60QASE/PYM6c\nz3Pttf+k062LiIiIyKChwEdERDyRnZ3DpZfO5tJLZwPQ3NxEVdUuqqp2UlW1k5qaD4mGWj75AQwT\nMMCNfezQkCF5TJ8+g2nTLmLChImauiUiIiIig44CHxERSQi5uUOYOXMIM2deAoDjODQ2NnDgwH7q\n6vZz4EAdXV1dhMPddHd3Ew6HicWiZGfnkJs7lKFDh5KbO4TCwpEUFo46bWsCiYiIiIj0Bwp8REQk\nIZmmSX5+Afn5BUyZMtXrckRERERE+hWNcRcRERERERERGWAU+IiIiIiIiIiIDDAKfERERERERERE\nBhgFPiIiIiIiIiIiA4wCHxERERERERGRAUaBj4iIiIiIiIjIAKPAR0REROQMW7JkCfPnz2fBggVs\n3br1iGNr1qzhhhtuYP78+Tz77LMeVSgiIiL9nQIfERERkTOopKSEvXv3smLFCh577DEee+yxI47/\n+7//O8uXL+ePf/wjq1evpqqqyqNKRUREpD9T4CMiIiJyBhUXFzN37lwAxo8fT1tbG4FAAIB9+/aR\nlZXF8OHDMU2TOXPmUFxc7GW5IiIi0k8p8BERERE5gxobG8nJyem7npubS0NDAwANDQ3k5uYe9ZiI\niIjIZ2GfygfLy8s4lQ/XbwzWz7s/UG8Sl3qTuNSbxKXeDEyu6570Y+h7IzGpL4lHPUlM6kviUU8G\nBo3wERERETmD8vPzaWxs7LteX19PXl7eUY8dPHiQ/Pz8M16jiIiI9H8KfERERETOoFmzZvHaa68B\nUFFRQX5+Punp6QCMHDmSQCBATU0N0WiUd955h1mzZnlZroiIiPRThnsqxhGLiIiIyKe2bNkyNmzY\ngGEYLF68mMrKSjIyMpg3bx7r169n2bJlAFx99dV885vf9LhaERER6Y8U+IiIiIiIiIiIDDCa0iUi\nIiIiIiIiMsAo8BERERERERERGWAU+JyEJUuWMH/+fBYsWMDWrVu9LmdQ2rlzJ3PnzuX3v/89AHV1\ndXzta1/jlltu4Yc//CHhcBiAV155ha985SvceOON/PnPf/ay5EHjySefZP78+XzlK1/h9ddfV28S\nRDAY5Ic//CELFy7kxhtv5J133lFvEkwoFGLu3LkUFRWpN/Ixx3rvsWbNGm644Qbmz5/Ps88+61GF\ng8+xerJ27VpuuukmFixYwEMPPYTjOB5VOfh8mvfpTz31FF/72tfOcGWD17F6UldXx80338wNN9zA\nT37yE48qHJyO1Zc//OEPzJ8/n5tvvpnHHnvMowoHn4/+jnu4z/xa78oJWbdunfvtb3/bdV3Xraqq\ncm+66SaPKxp8Ojs73YULF7qPPPKI+8ILL7iu67oPPvig+/e//911Xdd96qmn3D/84Q9uZ2ene/XV\nV7vt7e1uM
Download .txt
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
Download .txt
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.

Copied to clipboard!