<commit_before>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h1> Feature Engineering </h1>\n",
    "\n",
    "In this notebook, you will learn how to incorporate feature engineering into your pipeline.\n",
    "<ul>\n",
    "<li> Working with feature columns </li>\n",
    "<li> Adding feature crosses in TensorFlow </li>\n",
    "<li> Reading data from BigQuery </li>\n",
    "<li> Creating datasets using Dataflow </li>\n",
    "<li> Using a wide-and-deep model </li>\n",
    "</ul>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Apache Beam works better with Python 2 at the moment, so we're going to work within the Python 2 kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "conda update -y -n base -c defaults conda\n",
    "source activate py2env\n",
    "pip uninstall -y google-cloud-dataflow\n",
    "conda install -y pytz \n",
    "pip install httplib2==0.11.3 apache-beam[gcp]==2.9.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**After doing a pip install, you have to reload the session so that the new packages are picked up.  Please click on the reload button in the above menu.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import apache_beam as beam\n",
    "import shutil\n",
    "print(tf.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 1. Environment variables for project and bucket </h2>\n",
    "\n",
    "<li> Your project id is the *unique* string that identifies your project (not the project name). You can find this from the GCP Console dashboard's Home page.  My dashboard reads:  <b>Project ID:</b> cloud-training-demos </li>\n",
    "<li> Cloud training often involves saving and restoring model files. Therefore, we should <b>create a single-region bucket</b>. If you don't have a bucket already, I suggest that you create one from the GCP console (because it will dynamically check whether the bucket name you want is available) </li>\n",
    "</ol>\n",
    "<b>Change the cell below</b> to reflect your Project ID and bucket name.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "PROJECT = 'cloud-training-demos'    # CHANGE THIS\n",
    "BUCKET = 'cloud-training-demos-ml' # REPLACE WITH YOUR BUCKET NAME. Use a regional bucket in the region you selected.\n",
    "REGION = 'us-central1' # Choose an available region for Cloud MLE from https://cloud.google.com/ml-engine/docs/regions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# for bash\n",
    "os.environ['PROJECT'] = PROJECT\n",
    "os.environ['BUCKET'] = BUCKET\n",
    "os.environ['REGION'] = REGION\n",
    "os.environ['TFVERSION'] = '1.8' \n",
    "\n",
    "## ensure we're using python2 env\n",
    "os.environ['CLOUDSDK_PYTHON'] = 'python2'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "## ensure gcloud is up to date\n",
    "gcloud components update\n",
    "\n",
    "gcloud config set project $PROJECT\n",
    "gcloud config set compute/region $REGION\n",
    "\n",
    "## ensure we predict locally with our current Python environment\n",
    "gcloud config set ml_engine/local_python `which python`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 2. Specifying query to pull the data </h2>\n",
    "\n",
    "Let's pull out a few extra columns from the timestamp."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_query(phase, EVERY_N):\n",
    "  if EVERY_N == None:\n",
    "    EVERY_N = 4 #use full dataset\n",
    "    \n",
    "  #select and pre-process fields\n",
    "  base_query = \"\"\"\n",
    "SELECT\n",
    "  (tolls_amount + fare_amount) AS fare_amount,\n",
    "  DAYOFWEEK(pickup_datetime) AS dayofweek,\n",
    "  HOUR(pickup_datetime) AS hourofday,\n",
    "  pickup_longitude AS pickuplon,\n",
    "  pickup_latitude AS pickuplat,\n",
    "  dropoff_longitude AS dropofflon,\n",
    "  dropoff_latitude AS dropofflat,\n",
    "  passenger_count*1.0 AS passengers,\n",
    "  CONCAT(STRING(pickup_datetime), STRING(pickup_longitude), STRING(pickup_latitude), STRING(dropoff_latitude), STRING(dropoff_longitude)) AS key\n",
    "FROM\n",
    "  [nyc-tlc:yellow.trips]\n",
    "WHERE\n",
    "  trip_distance > 0\n",
    "  AND fare_amount >= 2.5\n",
    "  AND pickup_longitude > -78\n",
    "  AND pickup_longitude < -70\n",
    "  AND dropoff_longitude > -78\n",
    "  AND dropoff_longitude < -70\n",
    "  AND pickup_latitude > 37\n",
    "  AND pickup_latitude < 45\n",
    "  AND dropoff_latitude > 37\n",
    "  AND dropoff_latitude < 45\n",
    "  AND passenger_count > 0\n",
    "  \"\"\"\n",
    "  \n",
    "  #add subsampling criteria by modding with hashkey\n",
    "  if phase == 'train': \n",
    "    query = \"{} AND ABS(HASH(pickup_datetime)) % {} < 2\".format(base_query,EVERY_N)\n",
    "  elif phase == 'valid': \n",
    "    query = \"{} AND ABS(HASH(pickup_datetime)) % {} == 2\".format(base_query,EVERY_N)\n",
    "  elif phase == 'test':\n",
    "    query = \"{} AND ABS(HASH(pickup_datetime)) % {} == 3\".format(base_query,EVERY_N)\n",
    "  return query\n",
    "    \n",
    "print(create_query('valid', 100)) #example query using 1% of data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try the query above in https://bigquery.cloud.google.com/table/nyc-tlc:yellow.trips if you want to see what it does (ADD LIMIT 10 to the query!)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 3. Preprocessing Dataflow job from BigQuery </h2>\n",
    "\n",
    "This code reads from BigQuery and saves the data as-is on Google Cloud Storage.  We can do additional preprocessing and cleanup inside Dataflow, but then we'll have to remember to repeat that prepreprocessing during inference. It is better to use tf.transform which will do this book-keeping for you, or to do preprocessing within your TensorFlow model. We will look at this in future notebooks. For now, we are simply moving data from BigQuery to CSV using Dataflow.\n",
    "\n",
    "While we could read from BQ directly from TensorFlow (See: https://www.tensorflow.org/api_docs/python/tf/contrib/cloud/BigQueryReader), it is quite convenient to export to CSV and do the training off CSV.  Let's use Dataflow to do this at scale.\n",
    "\n",
    "Because we are running this on the Cloud, you should go to the GCP Console (https://console.cloud.google.com/dataflow) to look at the status of the job. It will take several minutes for the preprocessing job to launch."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "if gsutil ls | grep -q gs://${BUCKET}/taxifare/ch4/taxi_preproc/; then\n",
    "  gsutil -m rm -rf gs://$BUCKET/taxifare/ch4/taxi_preproc/\n",
    "fi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, let's define a function for preprocessing the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import datetime\n",
    "\n",
    "####\n",
    "# Arguments:\n",
    "#   -rowdict: Dictionary. The beam bigquery reader returns a PCollection in\n",
    "#     which each row is represented as a python dictionary\n",
    "# Returns:\n",
    "#   -rowstring: a comma separated string representation of the record with dayofweek\n",
    "#     converted from int to string (e.g. 3 --> Tue)\n",
    "####\n",
    "def to_csv(rowdict):\n",
    "  days = ['null', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']\n",
    "  CSV_COLUMNS = 'fare_amount,dayofweek,hourofday,pickuplon,pickuplat,dropofflon,dropofflat,passengers,key'.split(',')\n",
    "  rowdict['dayofweek'] = days[rowdict['dayofweek']]\n",
    "  rowstring = ','.join([str(rowdict[k]) for k in CSV_COLUMNS])\n",
    "  return rowstring\n",
    "\n",
    "\n",
    "####\n",
    "# Arguments:\n",
    "#   -EVERY_N: Integer. Sample one out of every N rows from the full dataset.\n",
    "#     Larger values will yield smaller sample\n",
    "#   -RUNNER: 'DirectRunner' or 'DataflowRunner'. Specfy to run the pipeline\n",
    "#     locally or on Google Cloud respectively. \n",
    "# Side-effects:\n",
    "#   -Creates and executes dataflow pipeline. \n",
    "#     See https://beam.apache.org/documentation/programming-guide/#creating-a-pipeline\n",
    "####\n",
    "def preprocess(EVERY_N, RUNNER):\n",
    "  job_name = 'preprocess-taxifeatures' + '-' + datetime.datetime.now().strftime('%y%m%d-%H%M%S')\n",
    "  print('Launching Dataflow job {} ... hang on'.format(job_name))\n",
    "  OUTPUT_DIR = 'gs://{0}/taxifare/ch4/taxi_preproc/'.format(BUCKET)\n",
    "\n",
    "  #dictionary of pipeline options\n",
    "  options = {\n",
    "    'staging_location': os.path.join(OUTPUT_DIR, 'tmp', 'staging'),\n",
    "    'temp_location': os.path.join(OUTPUT_DIR, 'tmp'),\n",
    "    'job_name': 'preprocess-taxifeatures' + '-' + datetime.datetime.now().strftime('%y%m%d-%H%M%S'),\n",
    "    'project': PROJECT,\n",
    "    'runner': RUNNER\n",
    "  }\n",
    "  #instantiate PipelineOptions object using options dictionary\n",
    "  opts = beam.pipeline.PipelineOptions(flags=[], **options)\n",
    "  #instantantiate Pipeline object using PipelineOptions\n",
    "  with beam.Pipeline(options=opts) as p:\n",
    "      for phase in ['train', 'valid']:\n",
    "        query = create_query(phase, EVERY_N) \n",
    "        outfile = os.path.join(OUTPUT_DIR, '{}.csv'.format(phase))\n",
    "        (\n",
    "          p | 'read_{}'.format(phase) >> beam.io.Read(beam.io.BigQuerySource(query=query))\n",
    "            | 'tocsv_{}'.format(phase) >> beam.Map(to_csv)\n",
    "            | 'write_{}'.format(phase) >> beam.io.Write(beam.io.WriteToText(outfile))\n",
    "        )\n",
    "  print(\"Done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's run pipeline locally. This takes upto <b>5 minutes</b>.  You will see a message \"Done\" when it is done."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "preprocess(50*10000, 'DirectRunner') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil ls gs://$BUCKET/taxifare/ch4/taxi_preproc/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Run Beam pipeline on Cloud Dataflow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Run pipeline on cloud on a larger sample size."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "if gsutil ls | grep -q gs://${BUCKET}/taxifare/ch4/taxi_preproc/; then\n",
    "  gsutil -m rm -rf gs://$BUCKET/taxifare/ch4/taxi_preproc/\n",
    "fi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following step will take <b>15-20 minutes.</b> Monitor job progress on the [Cloud Console, in the Dataflow](https://console.cloud.google.com/dataflow) section"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "preprocess(50*100, 'DataflowRunner') \n",
    "#change first arg to None to preprocess full dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once the job completes, observe the files created in Google Cloud Storage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil ls -l gs://$BUCKET/taxifare/ch4/taxi_preproc/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "#print first 10 lines of first shard of train.csv\n",
    "gsutil cat \"gs://$BUCKET/taxifare/ch4/taxi_preproc/train.csv-00000-of-*\" | head"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Develop model with new inputs\n",
    "\n",
    "Download the first shard of the preprocessed data to enable local development."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "if [ -d sample ]; then\n",
    "  rm -rf sample\n",
    "fi\n",
    "mkdir sample\n",
    "gsutil cat \"gs://$BUCKET/taxifare/ch4/taxi_preproc/train.csv-00000-of-*\" > sample/train.csv\n",
    "gsutil cat \"gs://$BUCKET/taxifare/ch4/taxi_preproc/valid.csv-00000-of-*\" > sample/valid.csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have two new inputs in the INPUT_COLUMNS, three engineered features, and the estimator involves bucketization and feature crosses."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "grep -A 20 \"INPUT_COLUMNS =\" taxifare/trainer/model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "grep -A 50 \"build_estimator\" taxifare/trainer/model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "grep -A 15 \"add_engineered(\" taxifare/trainer/model.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try out the new model on the local sample (this takes <b>5 minutes</b>) to make sure it works fine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "rm -rf taxifare.tar.gz taxi_trained\n",
    "export PYTHONPATH=${PYTHONPATH}:${PWD}/taxifare\n",
    "python -m trainer.task \\\n",
    "  --train_data_paths=${PWD}/sample/train.csv \\\n",
    "  --eval_data_paths=${PWD}/sample/valid.csv  \\\n",
    "  --output_dir=${PWD}/taxi_trained \\\n",
    "  --train_steps=10 \\\n",
    "  --job-dir=/tmp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "ls taxi_trained/export/exporter/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can use ```saved_model_cli``` to look at the exported signature. Note that the model doesn't need any of the engineered features as inputs. It will compute latdiff, londiff, euclidean from the provided inputs, thanks to the ```add_engineered``` call in the serving_input_fn."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(ls ${PWD}/taxi_trained/export/exporter | tail -1)\n",
    "saved_model_cli show --dir ${PWD}/taxi_trained/export/exporter/${model_dir} --all"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%writefile /tmp/test.json\n",
    "{\"dayofweek\": \"Sun\", \"hourofday\": 17, \"pickuplon\": -73.885262, \"pickuplat\": 40.773008, \"dropofflon\": -73.987232, \"dropofflat\": 40.732403, \"passengers\": 2}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(ls ${PWD}/taxi_trained/export/exporter)\n",
    "gcloud ml-engine local predict \\\n",
    "  --model-dir=${PWD}/taxi_trained/export/exporter/${model_dir} \\\n",
    "  --json-instances=/tmp/test.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Train on cloud\n",
    "\n",
    "This will take <b> 10-15 minutes </b> even though the prompt immediately returns after the job is submitted. Monitor job progress on the [Cloud Console, in the AI Platform](https://console.cloud.google.com/mlengine) section and wait for the training job to complete.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "OUTDIR=gs://${BUCKET}/taxifare/ch4/taxi_trained\n",
    "JOBNAME=lab4a_$(date -u +%y%m%d_%H%M%S)\n",
    "echo $OUTDIR $REGION $JOBNAME\n",
    "gsutil -m rm -rf $OUTDIR\n",
    "gcloud ai-platform jobs submit training $JOBNAME \\\n",
    "  --region=$REGION \\\n",
    "  --module-name=trainer.task \\\n",
    "  --package-path=${PWD}/taxifare/trainer \\\n",
    "  --job-dir=$OUTDIR \\\n",
    "  --staging-bucket=gs://$BUCKET \\\n",
    "  --scale-tier=BASIC \\\n",
    "  --runtime-version=$TFVERSION \\\n",
    "  -- \\\n",
    "  --train_data_paths=\"gs://$BUCKET/taxifare/ch4/taxi_preproc/train*\" \\\n",
    "  --eval_data_paths=\"gs://${BUCKET}/taxifare/ch4/taxi_preproc/valid*\"  \\\n",
    "  --train_steps=5000 \\\n",
    "  --output_dir=$OUTDIR"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The RMSE is now 8.33249, an improvement over the 9.3 that we were getting ... of course, we won't know until we train/validate on a larger dataset. Still, this is promising. But before we do that, let's do hyper-parameter tuning.\n",
    "\n",
    "<b>Use the Cloud Console link to monitor the job and do NOT proceed until the job is done.</b>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1)\n",
    "saved_model_cli show --dir ${model_dir} --all"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1)\n",
    "gcloud ml-engine local predict \\\n",
    "  --model-dir=${model_dir} \\\n",
    "  --json-instances=/tmp/test.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Optional: deploy model to cloud"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "MODEL_NAME=\"feateng\"\n",
    "MODEL_VERSION=\"v1\"\n",
    "MODEL_LOCATION=$(gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1)\n",
    "echo \"Run these commands one-by-one (the very first time, you'll create a model and then create a version)\"\n",
    "#gcloud ai-platform versions delete ${MODEL_VERSION} --model ${MODEL_NAME}\n",
    "#gcloud ai-platform delete ${MODEL_NAME}\n",
    "gcloud ai-platform models create ${MODEL_NAME} --regions $REGION\n",
    "gcloud ai-platform versions create ${MODEL_VERSION} --model ${MODEL_NAME} --origin ${MODEL_LOCATION} --runtime-version $TFVERSION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gcloud ai-platform predict --model=feateng --version=v1 --json-instances=/tmp/test.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 6. Hyper-parameter tune </h2>\n",
    "\n",
    "Look at <a href=\"hyperparam.ipynb\">hyper-parameter tuning notebook</a> to decide what parameters to use for model. Based on that run, I ended up choosing:\n",
    "<ol>\n",
    "<li> train_batch_size: 512 </li>\n",
    "<li> nbuckets: 16 </li>\n",
    "<li> hidden_units: \"64 64 64 8\" </li>    \n",
    "</ol>\n",
    "\n",
    "This gives an RMSE of 5, a considerable improvement from the 8.3 we were getting earlier ... Let's try this over a larger dataset."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Optional: Run Cloud training on 2 million row dataset\n",
    "\n",
    "This run uses as input 2 million rows and takes ~20 minutes with 10 workers (STANDARD_1 pricing tier). The model is exactly the same as above. The only changes are to the input (to use the larger dataset) and to the Cloud MLE tier (to use STANDARD_1 instead of BASIC -- STANDARD_1 is approximately 10x more powerful than BASIC). Because the Dataflow preprocessing takes about 15 minutes, we train here using CSV files in a public bucket.\n",
    "\n",
    "When doing distributed training, use train_steps instead of num_epochs. The distributed workers don't know how many rows there are, but we can calculate train_steps = num_rows \\* num_epochs / train_batch_size. In this case, we have 2141023 * 100 / 512 = 418168 train steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "\n",
    "WARNING -- this uses significant resources and is optional. Remove this line to run the block.\n",
    "\n",
    "OUTDIR=gs://${BUCKET}/taxifare/feateng2m\n",
    "JOBNAME=lab4a_$(date -u +%y%m%d_%H%M%S)\n",
    "TIER=STANDARD_1 \n",
    "echo $OUTDIR $REGION $JOBNAME\n",
    "gsutil -m rm -rf $OUTDIR\n",
    "gcloud ai-platform jobs submit training $JOBNAME \\\n",
    "   --region=$REGION \\\n",
    "   --module-name=trainer.task \\\n",
    "   --package-path=${PWD}/taxifare/trainer \\\n",
    "   --job-dir=$OUTDIR \\\n",
    "   --staging-bucket=gs://$BUCKET \\\n",
    "   --scale-tier=$TIER \\\n",
    "   --runtime-version=$TFVERSION \\\n",
    "   -- \\\n",
    "   --train_data_paths=\"gs://${BUCKET}/taxifare/train*\" \\\n",
    "   --eval_data_paths=\"gs://${BUCKET}/taxifare/valid*\"  \\\n",
    "   --output_dir=$OUTDIR \\\n",
    "   --train_steps=418168 \\\n",
    "   --train_batch_size=512 --nbuckets=16 --hidden_units=\"64 64 64 8\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The RMSE after training on the 2-million-row dataset is \\$3.03.  This graph shows the improvements so far ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "df = pd.DataFrame({'Lab' : pd.Series(['1a', '2-3', '4a', '4b', '4c']),\n",
    "              'Method' : pd.Series(['Heuristic Benchmark', 'tf.learn', '+Feature Eng.', '+ Hyperparam', '+ 2m rows']),\n",
    "              'RMSE': pd.Series([8.026, 9.4, 8.3, 5.0, 3.03]) })\n",
    "\n",
    "ax = sns.barplot(data = df, x = 'Method', y = 'RMSE')\n",
    "ax.set_ylabel('RMSE (dollars)')\n",
    "ax.set_xlabel('Labs/Methods')\n",
    "plt.plot(np.linspace(-20, 120, 1000), [5] * 1000, 'b');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil -m mv gs://${BUCKET}/taxifare/ch4/ gs://${BUCKET}/taxifare/ch4_1m/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright 2016 Google Inc. 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"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
<commit_msg>- Removed the unnecessary code from the notebook to avoid the error reported in buganizer b/138061762.<commit_after>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h1> Feature Engineering </h1>\n",
    "\n",
    "In this notebook, you will learn how to incorporate feature engineering into your pipeline.\n",
    "<ul>\n",
    "<li> Working with feature columns </li>\n",
    "<li> Adding feature crosses in TensorFlow </li>\n",
    "<li> Reading data from BigQuery </li>\n",
    "<li> Creating datasets using Dataflow </li>\n",
    "<li> Using a wide-and-deep model </li>\n",
    "</ul>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Apache Beam works better with Python 2 at the moment, so we're going to work within the Python 2 kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "pip install httplib2==0.11.3 apache-beam[gcp]==2.9.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**After doing a pip install, you have to reload the session so that the new packages are picked up.  Please click on the reload button in the above menu.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import apache_beam as beam\n",
    "import shutil\n",
    "print(tf.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 1. Environment variables for project and bucket </h2>\n",
    "\n",
    "<li> Your project id is the *unique* string that identifies your project (not the project name). You can find this from the GCP Console dashboard's Home page.  My dashboard reads:  <b>Project ID:</b> cloud-training-demos </li>\n",
    "<li> Cloud training often involves saving and restoring model files. Therefore, we should <b>create a single-region bucket</b>. If you don't have a bucket already, I suggest that you create one from the GCP console (because it will dynamically check whether the bucket name you want is available) </li>\n",
    "</ol>\n",
    "<b>Change the cell below</b> to reflect your Project ID and bucket name.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "PROJECT = 'cloud-training-demos'    # CHANGE THIS\n",
    "BUCKET = 'cloud-training-demos-ml' # REPLACE WITH YOUR BUCKET NAME. Use a regional bucket in the region you selected.\n",
    "REGION = 'us-central1' # Choose an available region for Cloud MLE from https://cloud.google.com/ml-engine/docs/regions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# for bash\n",
    "os.environ['PROJECT'] = PROJECT\n",
    "os.environ['BUCKET'] = BUCKET\n",
    "os.environ['REGION'] = REGION\n",
    "os.environ['TFVERSION'] = '1.8' \n",
    "\n",
    "## ensure we're using python2 env\n",
    "os.environ['CLOUDSDK_PYTHON'] = 'python2'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "gcloud config set project $PROJECT\n",
    "gcloud config set compute/region $REGION\n",
    "\n",
    "## ensure we predict locally with our current Python environment\n",
    "gcloud config set ml_engine/local_python `which python`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 2. Specifying query to pull the data </h2>\n",
    "\n",
    "Let's pull out a few extra columns from the timestamp."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_query(phase, EVERY_N):\n",
    "  if EVERY_N == None:\n",
    "    EVERY_N = 4 #use full dataset\n",
    "    \n",
    "  #select and pre-process fields\n",
    "  base_query = \"\"\"\n",
    "SELECT\n",
    "  (tolls_amount + fare_amount) AS fare_amount,\n",
    "  DAYOFWEEK(pickup_datetime) AS dayofweek,\n",
    "  HOUR(pickup_datetime) AS hourofday,\n",
    "  pickup_longitude AS pickuplon,\n",
    "  pickup_latitude AS pickuplat,\n",
    "  dropoff_longitude AS dropofflon,\n",
    "  dropoff_latitude AS dropofflat,\n",
    "  passenger_count*1.0 AS passengers,\n",
    "  CONCAT(STRING(pickup_datetime), STRING(pickup_longitude), STRING(pickup_latitude), STRING(dropoff_latitude), STRING(dropoff_longitude)) AS key\n",
    "FROM\n",
    "  [nyc-tlc:yellow.trips]\n",
    "WHERE\n",
    "  trip_distance > 0\n",
    "  AND fare_amount >= 2.5\n",
    "  AND pickup_longitude > -78\n",
    "  AND pickup_longitude < -70\n",
    "  AND dropoff_longitude > -78\n",
    "  AND dropoff_longitude < -70\n",
    "  AND pickup_latitude > 37\n",
    "  AND pickup_latitude < 45\n",
    "  AND dropoff_latitude > 37\n",
    "  AND dropoff_latitude < 45\n",
    "  AND passenger_count > 0\n",
    "  \"\"\"\n",
    "  \n",
    "  #add subsampling criteria by modding with hashkey\n",
    "  if phase == 'train': \n",
    "    query = \"{} AND ABS(HASH(pickup_datetime)) % {} < 2\".format(base_query,EVERY_N)\n",
    "  elif phase == 'valid': \n",
    "    query = \"{} AND ABS(HASH(pickup_datetime)) % {} == 2\".format(base_query,EVERY_N)\n",
    "  elif phase == 'test':\n",
    "    query = \"{} AND ABS(HASH(pickup_datetime)) % {} == 3\".format(base_query,EVERY_N)\n",
    "  return query\n",
    "    \n",
    "print(create_query('valid', 100)) #example query using 1% of data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try the query above in https://bigquery.cloud.google.com/table/nyc-tlc:yellow.trips if you want to see what it does (ADD LIMIT 10 to the query!)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 3. Preprocessing Dataflow job from BigQuery </h2>\n",
    "\n",
    "This code reads from BigQuery and saves the data as-is on Google Cloud Storage.  We can do additional preprocessing and cleanup inside Dataflow, but then we'll have to remember to repeat that prepreprocessing during inference. It is better to use tf.transform which will do this book-keeping for you, or to do preprocessing within your TensorFlow model. We will look at this in future notebooks. For now, we are simply moving data from BigQuery to CSV using Dataflow.\n",
    "\n",
    "While we could read from BQ directly from TensorFlow (See: https://www.tensorflow.org/api_docs/python/tf/contrib/cloud/BigQueryReader), it is quite convenient to export to CSV and do the training off CSV.  Let's use Dataflow to do this at scale.\n",
    "\n",
    "Because we are running this on the Cloud, you should go to the GCP Console (https://console.cloud.google.com/dataflow) to look at the status of the job. It will take several minutes for the preprocessing job to launch."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "if gsutil ls | grep -q gs://${BUCKET}/taxifare/ch4/taxi_preproc/; then\n",
    "  gsutil -m rm -rf gs://$BUCKET/taxifare/ch4/taxi_preproc/\n",
    "fi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, let's define a function for preprocessing the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import datetime\n",
    "\n",
    "####\n",
    "# Arguments:\n",
    "#   -rowdict: Dictionary. The beam bigquery reader returns a PCollection in\n",
    "#     which each row is represented as a python dictionary\n",
    "# Returns:\n",
    "#   -rowstring: a comma separated string representation of the record with dayofweek\n",
    "#     converted from int to string (e.g. 3 --> Tue)\n",
    "####\n",
    "def to_csv(rowdict):\n",
    "  days = ['null', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']\n",
    "  CSV_COLUMNS = 'fare_amount,dayofweek,hourofday,pickuplon,pickuplat,dropofflon,dropofflat,passengers,key'.split(',')\n",
    "  rowdict['dayofweek'] = days[rowdict['dayofweek']]\n",
    "  rowstring = ','.join([str(rowdict[k]) for k in CSV_COLUMNS])\n",
    "  return rowstring\n",
    "\n",
    "\n",
    "####\n",
    "# Arguments:\n",
    "#   -EVERY_N: Integer. Sample one out of every N rows from the full dataset.\n",
    "#     Larger values will yield smaller sample\n",
    "#   -RUNNER: 'DirectRunner' or 'DataflowRunner'. Specfy to run the pipeline\n",
    "#     locally or on Google Cloud respectively. \n",
    "# Side-effects:\n",
    "#   -Creates and executes dataflow pipeline. \n",
    "#     See https://beam.apache.org/documentation/programming-guide/#creating-a-pipeline\n",
    "####\n",
    "def preprocess(EVERY_N, RUNNER):\n",
    "  job_name = 'preprocess-taxifeatures' + '-' + datetime.datetime.now().strftime('%y%m%d-%H%M%S')\n",
    "  print('Launching Dataflow job {} ... hang on'.format(job_name))\n",
    "  OUTPUT_DIR = 'gs://{0}/taxifare/ch4/taxi_preproc/'.format(BUCKET)\n",
    "\n",
    "  #dictionary of pipeline options\n",
    "  options = {\n",
    "    'staging_location': os.path.join(OUTPUT_DIR, 'tmp', 'staging'),\n",
    "    'temp_location': os.path.join(OUTPUT_DIR, 'tmp'),\n",
    "    'job_name': 'preprocess-taxifeatures' + '-' + datetime.datetime.now().strftime('%y%m%d-%H%M%S'),\n",
    "    'project': PROJECT,\n",
    "    'runner': RUNNER\n",
    "  }\n",
    "  #instantiate PipelineOptions object using options dictionary\n",
    "  opts = beam.pipeline.PipelineOptions(flags=[], **options)\n",
    "  #instantantiate Pipeline object using PipelineOptions\n",
    "  with beam.Pipeline(options=opts) as p:\n",
    "      for phase in ['train', 'valid']:\n",
    "        query = create_query(phase, EVERY_N) \n",
    "        outfile = os.path.join(OUTPUT_DIR, '{}.csv'.format(phase))\n",
    "        (\n",
    "          p | 'read_{}'.format(phase) >> beam.io.Read(beam.io.BigQuerySource(query=query))\n",
    "            | 'tocsv_{}'.format(phase) >> beam.Map(to_csv)\n",
    "            | 'write_{}'.format(phase) >> beam.io.Write(beam.io.WriteToText(outfile))\n",
    "        )\n",
    "  print(\"Done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's run pipeline locally. This takes upto <b>5 minutes</b>.  You will see a message \"Done\" when it is done."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "preprocess(50*10000, 'DirectRunner') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil ls gs://$BUCKET/taxifare/ch4/taxi_preproc/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Run Beam pipeline on Cloud Dataflow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Run pipeline on cloud on a larger sample size."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "if gsutil ls | grep -q gs://${BUCKET}/taxifare/ch4/taxi_preproc/; then\n",
    "  gsutil -m rm -rf gs://$BUCKET/taxifare/ch4/taxi_preproc/\n",
    "fi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following step will take <b>15-20 minutes.</b> Monitor job progress on the [Cloud Console, in the Dataflow](https://console.cloud.google.com/dataflow) section"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "preprocess(50*100, 'DataflowRunner') \n",
    "#change first arg to None to preprocess full dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once the job completes, observe the files created in Google Cloud Storage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil ls -l gs://$BUCKET/taxifare/ch4/taxi_preproc/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "#print first 10 lines of first shard of train.csv\n",
    "gsutil cat \"gs://$BUCKET/taxifare/ch4/taxi_preproc/train.csv-00000-of-*\" | head"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Develop model with new inputs\n",
    "\n",
    "Download the first shard of the preprocessed data to enable local development."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "if [ -d sample ]; then\n",
    "  rm -rf sample\n",
    "fi\n",
    "mkdir sample\n",
    "gsutil cat \"gs://$BUCKET/taxifare/ch4/taxi_preproc/train.csv-00000-of-*\" > sample/train.csv\n",
    "gsutil cat \"gs://$BUCKET/taxifare/ch4/taxi_preproc/valid.csv-00000-of-*\" > sample/valid.csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have two new inputs in the INPUT_COLUMNS, three engineered features, and the estimator involves bucketization and feature crosses."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "grep -A 20 \"INPUT_COLUMNS =\" taxifare/trainer/model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "grep -A 50 \"build_estimator\" taxifare/trainer/model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "grep -A 15 \"add_engineered(\" taxifare/trainer/model.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try out the new model on the local sample (this takes <b>5 minutes</b>) to make sure it works fine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "rm -rf taxifare.tar.gz taxi_trained\n",
    "export PYTHONPATH=${PYTHONPATH}:${PWD}/taxifare\n",
    "python -m trainer.task \\\n",
    "  --train_data_paths=${PWD}/sample/train.csv \\\n",
    "  --eval_data_paths=${PWD}/sample/valid.csv  \\\n",
    "  --output_dir=${PWD}/taxi_trained \\\n",
    "  --train_steps=10 \\\n",
    "  --job-dir=/tmp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "ls taxi_trained/export/exporter/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can use ```saved_model_cli``` to look at the exported signature. Note that the model doesn't need any of the engineered features as inputs. It will compute latdiff, londiff, euclidean from the provided inputs, thanks to the ```add_engineered``` call in the serving_input_fn."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(ls ${PWD}/taxi_trained/export/exporter | tail -1)\n",
    "saved_model_cli show --dir ${PWD}/taxi_trained/export/exporter/${model_dir} --all"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%writefile /tmp/test.json\n",
    "{\"dayofweek\": \"Sun\", \"hourofday\": 17, \"pickuplon\": -73.885262, \"pickuplat\": 40.773008, \"dropofflon\": -73.987232, \"dropofflat\": 40.732403, \"passengers\": 2}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(ls ${PWD}/taxi_trained/export/exporter)\n",
    "gcloud ml-engine local predict \\\n",
    "  --model-dir=${PWD}/taxi_trained/export/exporter/${model_dir} \\\n",
    "  --json-instances=/tmp/test.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Train on cloud\n",
    "\n",
    "This will take <b> 10-15 minutes </b> even though the prompt immediately returns after the job is submitted. Monitor job progress on the [Cloud Console, in the AI Platform](https://console.cloud.google.com/mlengine) section and wait for the training job to complete.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "OUTDIR=gs://${BUCKET}/taxifare/ch4/taxi_trained\n",
    "JOBNAME=lab4a_$(date -u +%y%m%d_%H%M%S)\n",
    "echo $OUTDIR $REGION $JOBNAME\n",
    "gsutil -m rm -rf $OUTDIR\n",
    "gcloud ai-platform jobs submit training $JOBNAME \\\n",
    "  --region=$REGION \\\n",
    "  --module-name=trainer.task \\\n",
    "  --package-path=${PWD}/taxifare/trainer \\\n",
    "  --job-dir=$OUTDIR \\\n",
    "  --staging-bucket=gs://$BUCKET \\\n",
    "  --scale-tier=BASIC \\\n",
    "  --runtime-version=$TFVERSION \\\n",
    "  -- \\\n",
    "  --train_data_paths=\"gs://$BUCKET/taxifare/ch4/taxi_preproc/train*\" \\\n",
    "  --eval_data_paths=\"gs://${BUCKET}/taxifare/ch4/taxi_preproc/valid*\"  \\\n",
    "  --train_steps=5000 \\\n",
    "  --output_dir=$OUTDIR"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The RMSE is now 8.33249, an improvement over the 9.3 that we were getting ... of course, we won't know until we train/validate on a larger dataset. Still, this is promising. But before we do that, let's do hyper-parameter tuning.\n",
    "\n",
    "<b>Use the Cloud Console link to monitor the job and do NOT proceed until the job is done.</b>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1)\n",
    "saved_model_cli show --dir ${model_dir} --all"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "model_dir=$(gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1)\n",
    "gcloud ml-engine local predict \\\n",
    "  --model-dir=${model_dir} \\\n",
    "  --json-instances=/tmp/test.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Optional: deploy model to cloud"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "MODEL_NAME=\"feateng\"\n",
    "MODEL_VERSION=\"v1\"\n",
    "MODEL_LOCATION=$(gsutil ls gs://${BUCKET}/taxifare/ch4/taxi_trained/export/exporter | tail -1)\n",
    "echo \"Run these commands one-by-one (the very first time, you'll create a model and then create a version)\"\n",
    "#gcloud ai-platform versions delete ${MODEL_VERSION} --model ${MODEL_NAME}\n",
    "#gcloud ai-platform delete ${MODEL_NAME}\n",
    "gcloud ai-platform models create ${MODEL_NAME} --regions $REGION\n",
    "gcloud ai-platform versions create ${MODEL_VERSION} --model ${MODEL_NAME} --origin ${MODEL_LOCATION} --runtime-version $TFVERSION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gcloud ai-platform predict --model=feateng --version=v1 --json-instances=/tmp/test.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2> 6. Hyper-parameter tune </h2>\n",
    "\n",
    "Look at <a href=\"hyperparam.ipynb\">hyper-parameter tuning notebook</a> to decide what parameters to use for model. Based on that run, I ended up choosing:\n",
    "<ol>\n",
    "<li> train_batch_size: 512 </li>\n",
    "<li> nbuckets: 16 </li>\n",
    "<li> hidden_units: \"64 64 64 8\" </li>    \n",
    "</ol>\n",
    "\n",
    "This gives an RMSE of 5, a considerable improvement from the 8.3 we were getting earlier ... Let's try this over a larger dataset."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Optional: Run Cloud training on 2 million row dataset\n",
    "\n",
    "This run uses as input 2 million rows and takes ~20 minutes with 10 workers (STANDARD_1 pricing tier). The model is exactly the same as above. The only changes are to the input (to use the larger dataset) and to the Cloud MLE tier (to use STANDARD_1 instead of BASIC -- STANDARD_1 is approximately 10x more powerful than BASIC). Because the Dataflow preprocessing takes about 15 minutes, we train here using CSV files in a public bucket.\n",
    "\n",
    "When doing distributed training, use train_steps instead of num_epochs. The distributed workers don't know how many rows there are, but we can calculate train_steps = num_rows \\* num_epochs / train_batch_size. In this case, we have 2141023 * 100 / 512 = 418168 train steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "\n",
    "WARNING -- this uses significant resources and is optional. Remove this line to run the block.\n",
    "\n",
    "OUTDIR=gs://${BUCKET}/taxifare/feateng2m\n",
    "JOBNAME=lab4a_$(date -u +%y%m%d_%H%M%S)\n",
    "TIER=STANDARD_1 \n",
    "echo $OUTDIR $REGION $JOBNAME\n",
    "gsutil -m rm -rf $OUTDIR\n",
    "gcloud ai-platform jobs submit training $JOBNAME \\\n",
    "   --region=$REGION \\\n",
    "   --module-name=trainer.task \\\n",
    "   --package-path=${PWD}/taxifare/trainer \\\n",
    "   --job-dir=$OUTDIR \\\n",
    "   --staging-bucket=gs://$BUCKET \\\n",
    "   --scale-tier=$TIER \\\n",
    "   --runtime-version=$TFVERSION \\\n",
    "   -- \\\n",
    "   --train_data_paths=\"gs://${BUCKET}/taxifare/train*\" \\\n",
    "   --eval_data_paths=\"gs://${BUCKET}/taxifare/valid*\"  \\\n",
    "   --output_dir=$OUTDIR \\\n",
    "   --train_steps=418168 \\\n",
    "   --train_batch_size=512 --nbuckets=16 --hidden_units=\"64 64 64 8\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The RMSE after training on the 2-million-row dataset is \\$3.03.  This graph shows the improvements so far ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "df = pd.DataFrame({'Lab' : pd.Series(['1a', '2-3', '4a', '4b', '4c']),\n",
    "              'Method' : pd.Series(['Heuristic Benchmark', 'tf.learn', '+Feature Eng.', '+ Hyperparam', '+ 2m rows']),\n",
    "              'RMSE': pd.Series([8.026, 9.4, 8.3, 5.0, 3.03]) })\n",
    "\n",
    "ax = sns.barplot(data = df, x = 'Method', y = 'RMSE')\n",
    "ax.set_ylabel('RMSE (dollars)')\n",
    "ax.set_xlabel('Labs/Methods')\n",
    "plt.plot(np.linspace(-20, 120, 1000), [5] * 1000, 'b');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "gsutil -m mv gs://${BUCKET}/taxifare/ch4/ gs://${BUCKET}/taxifare/ch4_1m/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright 2016 Google Inc. 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"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

{'fraction_non_alphanumeric': 0.18976058060190115, 'fraction_numerical': 0.012503451268094505, 'mean_word_length': 2.758301215535132, 'pattern_counts': {'":': 562, '<': 119, '<?xml version=': 0, '>': 141, 'https://': 14, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4731224', 'n_tokens_mistral': 19002, 'n_tokens_neox': 18348, 'n_words': 4489}