├── .gitignore ├── CONTRIBUTING.MD ├── LICENSE ├── README.md ├── customer_site ├── firebase.json └── public │ └── index.html ├── firebase.json ├── functions ├── index.js └── package.json └── ml ├── .ipynb_checkpoints └── priority_classification-checkpoint.ipynb ├── example_fields_analytics.ipynb ├── priority_classification.ipynb └── resolution_time_regression.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .pyc 4 | .csv -------------------------------------------------------------------------------- /CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Google Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | For a detailed Tutorial, follow this link: 2 | https://cloud.google.com/solutions/smartening-up-support-tickets-with-serverless-ml 3 | 4 | Disclaimer: This is not an official Google product -------------------------------------------------------------------------------- /customer_site/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "public", 4 | "rewrites": [ 5 | { 6 | "source": "**", 7 | "destination": "/index.html" 8 | } 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /customer_site/public/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 119 | 120 | 121 | 122 |

Create ticket

123 | 124 |
126 |
128 |
135 |
142 |
147 |
155 | 157 | 158 | 159 | 160 |

Tickets List

161 |
162 | 163 |
164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | Copyright 2017 Google Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | const functions = require('firebase-functions'); 20 | const admin = require('firebase-admin'); 21 | admin.initializeApp(functions.config().firebase); 22 | const Language = require('@google-cloud/language'); 23 | const language = Language({ apiVersion: 'v1beta2' }); 24 | const google = require('googleapis'); 25 | const jsforce = require('jsforce'); 26 | 27 | //[START model_configurations] 28 | const MDL_PROJECT_NAME = ; 29 | const RESOLUTION_TIME_MODEL_NAME = 'mdl_helpdesk_priority'; # Matches Notebook 30 | const PRIORITY_MODEL_NAME = 'mdl_helpdesk_resolution_time'; # Matches Notebook 31 | const SFDC_URL = ; 32 | const SFDC_LOGIN = ; 33 | const SFDC_PASSWORD = ; 34 | const SFDC_TOKEN = ; 35 | //[END model_configurations] 36 | 37 | /* 38 | * PRIORITY 39 | * Priority prediction using a custom classifying model created in the ml folder 40 | * calling it through the ML engine API. Because there is no google-cloud nodejs 41 | * library for this yet, we need to do several steps before we can call it. 42 | * This write back the priority position in the array to Firebase 43 | * @params ml : an authenticated ML client 44 | */ 45 | exports.priority = functions.database.ref('/tickets/{ticketID}').onCreate(event => { 46 | const snapshot = event.data; 47 | const key = snapshot.key; 48 | const ticket = snapshot.val(); 49 | 50 | if (ticket.hasOwnProperty("pred_priority")){ 51 | console.log("Priority has been done") 52 | return; 53 | } 54 | 55 | // Auth 56 | google.auth.getApplicationDefault(function(err, authClient) { 57 | if (err) { 58 | return cb(err); 59 | } 60 | 61 | //[START ml_engine_auth] 62 | if (authClient.createScopedRequired && authClient.createScopedRequired()) { 63 | // https://developers.google.com/identity/protocols/googlescopes#mlv1 64 | authClient = authClient.createScoped([ 65 | 'https://www.googleapis.com/auth/cloud-platform' 66 | ]); 67 | } 68 | 69 | //Create authenticated ml engine client 70 | var ml = google.ml({ 71 | version: 'v1', 72 | auth: authClient 73 | }); 74 | //[END ml_engine_auth] 75 | 76 | // Prediction 77 | ml.projects.predict({ 78 | name: `projects/${MDL_PROJECT_NAME}/models/${PRIORITY_MODEL_NAME}`, 79 | resource: { 80 | name: `projects/${MDL_PROJECT_NAME}/models/${PRIORITY_MODEL_NAME}`, 81 | instances: [ 82 | `${key},${ticket.seniority},${ticket.experience},${ticket.category}, 83 | ${ticket.type},${ticket.impact}` 84 | ] 85 | } 86 | }, function (err, result){ 87 | if (err){ 88 | console.error('ERROR PRIORITY', err) 89 | } 90 | if (result.predictions[0].predicted){ 91 | admin.database().ref(`/tickets/${key}/pred_priority`).set( 92 | result.predictions[0].predicted 93 | ); 94 | } 95 | }); 96 | }); 97 | }); 98 | 99 | 100 | /* 101 | * RESOLUTION TIME 102 | * Resolution time prediction using a custom regressive model created 103 | * calling it through the ML engine API. Because there is no google-cloud nodejs 104 | * library for this yet, we need to do several steps before we can call it. 105 | * This returns a float representing the amount of days that it will be open 106 | * @params ml : an authenticated ML client 107 | */ 108 | exports.resolutiontime = functions.database.ref('/tickets/{ticketID}').onCreate(event => { 109 | 110 | const snapshot = event.data; 111 | const key = snapshot.key; 112 | const ticket = snapshot.val(); 113 | 114 | if (ticket.hasOwnProperty("pred_resolution_time")){ 115 | console.log("Resolution time has been done") 116 | return; 117 | } 118 | 119 | //[START ml_auth] 120 | google.auth.getApplicationDefault(function(err, authClient) { 121 | if (err) { 122 | return cb(err); 123 | } 124 | 125 | if (authClient.createScopedRequired && authClient.createScopedRequired()) { 126 | // Ml Engine does not have its own scope. Needs to use global 127 | // https://developers.google.com/identity/protocols/googlescopes#mlv1 128 | authClient = authClient.createScoped([ 129 | 'https://www.googleapis.com/auth/cloud-platform' 130 | ]); 131 | } 132 | 133 | var ml = google.ml({ 134 | version: 'v1', 135 | auth: authClient 136 | }); 137 | //[END ml_auth] 138 | 139 | //[START resolution_prediction] 140 | ml.projects.predict({ 141 | name: `projects/${MDL_PROJECT_NAME}/models/${RESOLUTION_TIME_MODEL_NAME}`, 142 | resource: { 143 | name: `projects/${MDL_PROJECT_NAME}/models/${RESOLUTION_TIME_MODEL_NAME}`, 144 | instances: [ 145 | `${key},${ticket.seniority},${ticket.experience},${ticket.category}, 146 | ${ticket.type},${ticket.impact}` 147 | ] 148 | } 149 | }, 150 | //[END resolution_prediction] 151 | function (err, result){ 152 | if (err){ 153 | console.error('ERROR RESOLUTION TIME', err) 154 | } 155 | if (result.predictions[0].predicted){ 156 | admin.database().ref(`/tickets/${key}/pred_resolution_time`).set( 157 | result.predictions[0].predicted 158 | ); 159 | } 160 | }); 161 | }); 162 | }); 163 | 164 | /* 165 | * SENTIMENT 166 | * NLP Enrichment. This is calling directly the nlp API which has a google-cloud 167 | * nodeJS library so the authentication is quite straight forward. 168 | * It writes back to Firebase the tags. 169 | */ 170 | exports.sentiment = functions.database.ref('/tickets/{ticketID}').onCreate(event => { 171 | 172 | const snapshot = event.data; 173 | const key = snapshot.key; 174 | const ticket = snapshot.val(); 175 | 176 | // Make sure that after we write, it does not call the function again 177 | if (!ticket){ 178 | console.log("No ticket yet") 179 | return; 180 | } 181 | if (ticket.hasOwnProperty("pred_sentiment")){ 182 | console.log("Sentiment has been done") 183 | return; 184 | } 185 | 186 | //[START nlp_prediction] 187 | const text = ticket.description; 188 | const document = language.document({content: text}); 189 | 190 | document.detectSentiment() 191 | .then((results) => { 192 | const sentiment = results[1].documentSentiment; 193 | admin.database().ref(`/tickets/${key}/pred_sentiment`).set(sentiment.score); 194 | }) 195 | .catch((err) => { 196 | console.error('ERROR detectSentiment:', err); 197 | }); 198 | //[END nlp_prediction] 199 | 200 | }); 201 | 202 | /* 203 | * TAGS 204 | * NLP Enrichment. This is calling directly the nlp API which has a google-cloud 205 | * nodeJS library so the authentication is quite straight forward. 206 | * It writes back to Firebase the tags. 207 | */ 208 | exports.tags = functions.database.ref('/tickets/{ticketID}').onCreate(event => { 209 | 210 | const snapshot = event.data; 211 | const key = snapshot.key; 212 | const ticket = snapshot.val(); 213 | 214 | // Make sure that after we write, it does not call the function again 215 | if (ticket.hasOwnProperty("tags")){ 216 | console.log("Tagging has been done") 217 | return; 218 | } 219 | 220 | const text = ticket.description; 221 | const document = language.document({content: text}); 222 | 223 | document.detectEntities() 224 | .then((results) => { 225 | const entities = results[0]; 226 | const writeEntities = [] 227 | entities.forEach((entity) => { 228 | writeEntities.push(entity.name) 229 | //admin.database().ref(`/tickets/${key}/tags`).push(entity.name); 230 | }); 231 | // We overwrite the whole thing to prevent duplicates mentioned above 232 | admin.database().ref(`/tickets/${key}`).update({'tags':writeEntities}); 233 | }) 234 | .catch((err) => { 235 | console.error('ERROR detectEntities:', err); 236 | }); 237 | }); 238 | 239 | /* 240 | * UPDATESFDC 241 | * Write to Salesforce some of the ticket data that was created in Firebase and 242 | * enriched using machine learning. 243 | */ 244 | exports.updateSFDC = functions.database.ref('/tickets/{ticketID}').onWrite(event => { 245 | const snapshot = event.data; 246 | const key = snapshot.key; 247 | const ticket = snapshot.val(); 248 | 249 | if (ticket.hasOwnProperty("sfdc_key")){ 250 | console.log("Ticket has been created already"); 251 | return; 252 | } 253 | 254 | // Makes sure that we do not try to write to Salesforce before the enrichment 255 | if ((!ticket.pred_priority) || (!ticket.pred_sentiment) || (!ticket.pred_resolution_time)){ 256 | console.log("Still waiting for some values");; 257 | return; 258 | } 259 | 260 | var jsforce = require('jsforce'); 261 | var conn = new jsforce.Connection(); 262 | 263 | //[START conn_sfdc] 264 | conn = new jsforce.Connection({ 265 | loginUrl : SFDC_URL 266 | }); 267 | conn.login(SFDC_LOGIN, SFDC_PASSWORD + SFDC_TOKEN, function(err, res) { 268 | //[END conn_sfdc] 269 | if (err) { 270 | return console.error('SFDC ERROR', err); 271 | } 272 | //[START create_ticket_sfdc] 273 | conn.sobject("Case").create({ 274 | SuppliedEmail: 'user@example.com', 275 | Description: ticket.description, 276 | Type: ticket.type, 277 | Reason: ticket.category, 278 | Priority: ticket.priority, 279 | ResolutionTime__c: ticket.t_resolution 280 | }, function(err, ret) { 281 | //[END create_ticket_sfdc] 282 | if (err || !ret.success) { 283 | return console.error(err, ret); 284 | } 285 | admin.database().ref(`/tickets/${key}/sfdc_key`).set(ret.id); 286 | }); 287 | }); 288 | }); 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "dependencies": { 5 | "firebase-admin": "~4.2.1", 6 | "firebase-functions": "^0.5.7", 7 | "@google-cloud/language": "0.10.3", 8 | "googleapis": "^19.0.0", 9 | "request-promise": "^2.0.0", 10 | "jsforce":"1.8.0" 11 | }, 12 | "private": true 13 | } 14 | -------------------------------------------------------------------------------- /ml/.ipynb_checkpoints/priority_classification-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from __future__ import division\n", 10 | "import os, hashlib, math\n", 11 | "import numpy as np\n", 12 | "import pandas as pd\n", 13 | "\n", 14 | "import sklearn.metrics as metrics\n", 15 | "import seaborn as sns\n", 16 | "import matplotlib.pyplot as plt\n", 17 | "\n", 18 | "import google.datalab.contrib.mlworkbench.commands\n", 19 | "import google.datalab.ml as ml" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "This Notebook shows you how to perform the basic steps in order to build, train and deploy a model on Google Cloud Platform using ML Toolbox\n", 27 | "\n", 28 | "1. Collect data\n", 29 | "2. Organize data\n", 30 | "3. Design the model\n", 31 | "4. Train and generate the model\n", 32 | "5. Deploy the model\n", 33 | "\n", 34 | "Note that we will build, train and deploy our model on this machine only as our dataset is small enough and a model created locally can still be deployed on Google ML Engine." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 2, 40 | "metadata": { 41 | "collapsed": true 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "CSV_FILE = \"issues.csv\"" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "# 1 of 7 - Collect data\n", 53 | "In a use case like this, data can be collected in different ways usually through dump of your database or export from your CRM into CSV. \n", 54 | "\n", 55 | "The data that we have collected is available on Google Cloud Storage as gs://solutions-public-assets/smartenup-helpdesk/ml/data.csv (public dataset).\n", 56 | "\n", 57 | "Note that in some cases, data might be saved in BigQuery (which is both a storage and queryuing engine) which is perfectly fine and would actually facilitate the filtering of the data specially with big datasets." 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 3, 63 | "metadata": {}, 64 | "outputs": [ 65 | { 66 | "name": "stdout", 67 | "output_type": "stream", 68 | "text": [ 69 | "Copying gs://solutions-public-assets/smartenup-helpdesk/ml/issues.csv...\n", 70 | "\\ [1 files][ 6.5 MiB/ 6.5 MiB] \n", 71 | "Operation completed over 1 objects/6.5 MiB. \n" 72 | ] 73 | } 74 | ], 75 | "source": [ 76 | "# Copy data from the cloud to this instance\n", 77 | "!gsutil cp gs://solutions-public-assets/smartenup-helpdesk/ml/issues.csv $CSV_FILE" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 4, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "100000 rows\n" 90 | ] 91 | }, 92 | { 93 | "data": { 94 | "text/html": [ 95 | "
\n", 96 | "\n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | "
ticketidcontactidseniorityexperiencecategorytypeimpactpriorityresolutiontime
0t0Patrick Blevins123-AdvancedPerformanceRequest4-CriticalP15
1t1Kaitlyn Ruiz32-ExperiencedTechnicalIssue1-MinorP46
2t2Chelsea Martin112-ExperiencedTechnicalRequest4-CriticalP12
3t3Richard Arnold82-ExperiencedPerformanceRequest1-MinorP35
4t4Kelly Jackson74-TrainerBillingRequest0-UnclassifiedP43
\n", 174 | "
" 175 | ], 176 | "text/plain": [ 177 | " ticketid contactid seniority experience category type \\\n", 178 | "0 t0 Patrick Blevins 12 3-Advanced Performance Request \n", 179 | "1 t1 Kaitlyn Ruiz 3 2-Experienced Technical Issue \n", 180 | "2 t2 Chelsea Martin 11 2-Experienced Technical Request \n", 181 | "3 t3 Richard Arnold 8 2-Experienced Performance Request \n", 182 | "4 t4 Kelly Jackson 7 4-Trainer Billing Request \n", 183 | "\n", 184 | " impact priority resolutiontime \n", 185 | "0 4-Critical P1 5 \n", 186 | "1 1-Minor P4 6 \n", 187 | "2 4-Critical P1 2 \n", 188 | "3 1-Minor P3 5 \n", 189 | "4 0-Unclassified P4 3 " 190 | ] 191 | }, 192 | "execution_count": 4, 193 | "metadata": {}, 194 | "output_type": "execute_result" 195 | } 196 | ], 197 | "source": [ 198 | "# Read data from csv into a Panda dataframe\n", 199 | "df_data = pd.read_csv(CSV_FILE, dtype=str)\n", 200 | "print '%d rows' % len(df_data)\n", 201 | "df_data.head()" 202 | ] 203 | }, 204 | { 205 | "cell_type": "markdown", 206 | "metadata": {}, 207 | "source": [ 208 | "We need to make sure that we do not have any duplicate. If that was the case some identical rows could be found in the training set and validation set for example which would have an impact on the model training (remember, validation and training set can't overlap)" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": 5, 214 | "metadata": {}, 215 | "outputs": [ 216 | { 217 | "name": "stdout", 218 | "output_type": "stream", 219 | "text": [ 220 | "99993 rows\n" 221 | ] 222 | } 223 | ], 224 | "source": [ 225 | "df_data = df_data.drop_duplicates(df_data.columns.difference(['ticketid']))\n", 226 | "print '%d rows' % len(df_data)" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "# 2 of 7 - Organize data" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "## Filter data\n", 241 | "\n", 242 | "We keep only the columns that we are interested and discard some others:\n", 243 | "- ownerid because we won't know its value when doing a prediction\n", 244 | "- priority as it is a value that we will predict in another Notebook\n", 245 | "- statisfaction as it is not a value that we know when doing a prediction. It might also be a value we woud like to predict later" 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": 6, 251 | "metadata": {}, 252 | "outputs": [ 253 | { 254 | "name": "stdout", 255 | "output_type": "stream", 256 | "text": [ 257 | "99993 rows\n" 258 | ] 259 | }, 260 | { 261 | "data": { 262 | "text/html": [ 263 | "
\n", 264 | "\n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | "
ticketidseniorityexperiencecategorytypeimpactpriority
0t0123-AdvancedPerformanceRequest4-CriticalP1
1t132-ExperiencedTechnicalIssue1-MinorP4
2t2112-ExperiencedTechnicalRequest4-CriticalP1
3t382-ExperiencedPerformanceRequest1-MinorP3
4t474-TrainerBillingRequest0-UnclassifiedP4
\n", 330 | "
" 331 | ], 332 | "text/plain": [ 333 | " ticketid seniority experience category type impact \\\n", 334 | "0 t0 12 3-Advanced Performance Request 4-Critical \n", 335 | "1 t1 3 2-Experienced Technical Issue 1-Minor \n", 336 | "2 t2 11 2-Experienced Technical Request 4-Critical \n", 337 | "3 t3 8 2-Experienced Performance Request 1-Minor \n", 338 | "4 t4 7 4-Trainer Billing Request 0-Unclassified \n", 339 | "\n", 340 | " priority \n", 341 | "0 P1 \n", 342 | "1 P4 \n", 343 | "2 P1 \n", 344 | "3 P3 \n", 345 | "4 P4 " 346 | ] 347 | }, 348 | "execution_count": 6, 349 | "metadata": {}, 350 | "output_type": "execute_result" 351 | } 352 | ], 353 | "source": [ 354 | "def transform_data(df):\n", 355 | " # Lists the column names that we want to keep from our dataframe. \n", 356 | " interesting_columns = ['ticketid', 'seniority', 'experience', 'category', 'type', 'impact', 'priority']\n", 357 | " \n", 358 | " # Filters the dataframe to keep only the relevant data and return the dataframe.\n", 359 | " df = df[interesting_columns]\n", 360 | " return df\n", 361 | " \n", 362 | "df_data = transform_data(df_data)\n", 363 | "# Displays the new dataframe.\n", 364 | "print '%d rows' % len(df_data)\n", 365 | "df_data.head()" 366 | ] 367 | }, 368 | { 369 | "cell_type": "markdown", 370 | "metadata": {}, 371 | "source": [ 372 | "## Create datasets\n", 373 | "Here we create training and test datasets on a 80/20 basis. To keep consistency for every load of data we use a column that follows these two requirements:\n", 374 | "- Is a unique identifer for each row\n", 375 | "- Will not be used as a training input\n", 376 | "\n", 377 | "ticketid is a good candidate" 378 | ] 379 | }, 380 | { 381 | "cell_type": "code", 382 | "execution_count": 7, 383 | "metadata": { 384 | "collapsed": true 385 | }, 386 | "outputs": [], 387 | "source": [ 388 | "def is_test_set(identifier, test_ratio, hash):\n", 389 | " h = int(hash(identifier.encode('ascii')).hexdigest()[-7:], 16)\n", 390 | " return (h/0xFFFFFFF) < test_ratio\n", 391 | " \n", 392 | "def create_datasets(df, id_column, test_ratio=0.2, hash=hashlib.md5):\n", 393 | " ids = df[id_column]\n", 394 | " ids_test_set = ids.apply(lambda x: is_test_set(x, test_ratio, hash))\n", 395 | " return df.loc[~ids_test_set], df.loc[ids_test_set]\n", 396 | "\n", 397 | "df_train, df_eval = create_datasets(df_data, 'ticketid')" 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": 8, 403 | "metadata": { 404 | "collapsed": true 405 | }, 406 | "outputs": [], 407 | "source": [ 408 | "# Set paths for CSV datasets\n", 409 | "training_data_path = 'train.csv'\n", 410 | "test_data_path = 'eval.csv'\n", 411 | "\n", 412 | "# Write Panda Dataframes to CSV files\n", 413 | "df_train.to_csv(training_data_path, header=False, index=False)\n", 414 | "df_eval.to_csv(test_data_path, header=False, index=False)" 415 | ] 416 | }, 417 | { 418 | "cell_type": "markdown", 419 | "metadata": {}, 420 | "source": [ 421 | "## Explore\n", 422 | "One of the most important part of Machine Learning is to explore the data before building a model. A few things will help improving your model quality such as\n", 423 | "- Normalization\n", 424 | "- Look at feature correlation\n", 425 | "- Feature crossing\n", 426 | "- ...\n", 427 | "\n", 428 | "Because the main goal of this Notebook and solution is to show how to build and deploy a model for serverless enrichment, we won't spend too much time here (also because the provided dataset is fake) but keep in mind that this is not a part that should be ignored in a real world example." 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "metadata": {}, 434 | "source": [ 435 | "One important thing in Classification though is to have a balanced set of Labels. " 436 | ] 437 | }, 438 | { 439 | "cell_type": "code", 440 | "execution_count": 9, 441 | "metadata": {}, 442 | "outputs": [ 443 | { 444 | "name": "stdout", 445 | "output_type": "stream", 446 | "text": [ 447 | "Training set:\n", 448 | "P1 20099\n", 449 | "P3 20060\n", 450 | "P4 20019\n", 451 | "P2 19962\n", 452 | "Name: priority, dtype: int64\n" 453 | ] 454 | } 455 | ], 456 | "source": [ 457 | "print \"Training set:\\n{}\".format(df_train.priority.value_counts())" 458 | ] 459 | }, 460 | { 461 | "cell_type": "code", 462 | "execution_count": 10, 463 | "metadata": {}, 464 | "outputs": [ 465 | { 466 | "name": "stdout", 467 | "output_type": "stream", 468 | "text": [ 469 | "Eval set:\n", 470 | "P2 5037\n", 471 | "P4 4979\n", 472 | "P3 4939\n", 473 | "P1 4898\n", 474 | "Name: priority, dtype: int64\n" 475 | ] 476 | } 477 | ], 478 | "source": [ 479 | "print \"Eval set:\\n{}\".format(df_eval.priority.value_counts())" 480 | ] 481 | }, 482 | { 483 | "cell_type": "markdown", 484 | "metadata": {}, 485 | "source": [ 486 | "# 3 of 7: Analyse\n", 487 | "ML Workbench comes with a pre-buit function that analyzes training data and generate stats, such as min/max/mean for numeric values, vocabulary for text columns. Note that if cloud is set to True, the function leverages BigQuery making the switch from small dataset to big data seamless." 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 11, 493 | "metadata": { 494 | "collapsed": true 495 | }, 496 | "outputs": [], 497 | "source": [ 498 | "!rm -rf analysis" 499 | ] 500 | }, 501 | { 502 | "cell_type": "code", 503 | "execution_count": 12, 504 | "metadata": {}, 505 | "outputs": [ 506 | { 507 | "name": "stdout", 508 | "output_type": "stream", 509 | "text": [ 510 | "Expanding any file patterns...\n", 511 | "file list computed.\n", 512 | "Analyzing file /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train.csv...\n", 513 | "file /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train.csv analyzed.\n" 514 | ] 515 | } 516 | ], 517 | "source": [ 518 | "%%ml analyze --output analysis\n", 519 | "training_data:\n", 520 | " csv: train.csv\n", 521 | " schema:\n", 522 | " - name: ticketid\n", 523 | " type: STRING\n", 524 | " - name: seniority\n", 525 | " type: FLOAT\n", 526 | " - name: experience\n", 527 | " type: STRING\n", 528 | " - name: category\n", 529 | " type: STRING\n", 530 | " - name: type\n", 531 | " type: STRING\n", 532 | " - name: impact\n", 533 | " type: STRING\n", 534 | " - name: priority\n", 535 | " type: STRING\n", 536 | "features:\n", 537 | " ticketid: \n", 538 | " transform: key\n", 539 | " seniority:\n", 540 | " transform: identity\n", 541 | " experience:\n", 542 | " transform: one_hot\n", 543 | " category:\n", 544 | " transform: one_hot\n", 545 | " type:\n", 546 | " transform: one_hot\n", 547 | " impact:\n", 548 | " transform: one_hot\n", 549 | " priority:\n", 550 | " transform: target\n" 551 | ] 552 | }, 553 | { 554 | "cell_type": "markdown", 555 | "metadata": {}, 556 | "source": [ 557 | "# 4 of 7: Transform\n", 558 | "This section is optional but can be an important step when dealing with big data. While the Analysis phase provides enough details for the training step to append, the transform phase creates tfRecord files which is required for Tensorflow processing. Doing it now make sure that the training step can start from the preprocessed data and does not have to do this for each row for every pass of the data which is not recommended when handling text or image data." 559 | ] 560 | }, 561 | { 562 | "cell_type": "code", 563 | "execution_count": 13, 564 | "metadata": { 565 | "collapsed": true 566 | }, 567 | "outputs": [], 568 | "source": [ 569 | "!rm -rf transform" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": 14, 575 | "metadata": {}, 576 | "outputs": [ 577 | { 578 | "name": "stdout", 579 | "output_type": "stream", 580 | "text": [ 581 | "2017-10-24 10:50:17.199230: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.\n", 582 | "2017-10-24 10:50:17.199548: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.\n", 583 | "2017-10-24 10:50:17.199673: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.\n", 584 | "2017-10-24 10:50:17.199716: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.\n", 585 | "2017-10-24 10:50:17.199722: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.\n", 586 | "WARNING:root:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.\n" 587 | ] 588 | } 589 | ], 590 | "source": [ 591 | "%%ml transform --output transform --analysis analysis\n", 592 | "prefix: train\n", 593 | "training_data:\n", 594 | " csv: train.csv\n", 595 | " schema:\n", 596 | " - name: ticketid\n", 597 | " type: STRING\n", 598 | " - name: seniority\n", 599 | " type: FLOAT\n", 600 | " - name: experience\n", 601 | " type: STRING\n", 602 | " - name: category\n", 603 | " type: STRING\n", 604 | " - name: type\n", 605 | " type: STRING\n", 606 | " - name: impact\n", 607 | " type: STRING\n", 608 | " - name: priority\n", 609 | " type: STRING" 610 | ] 611 | }, 612 | { 613 | "cell_type": "code", 614 | "execution_count": 15, 615 | "metadata": {}, 616 | "outputs": [ 617 | { 618 | "name": "stdout", 619 | "output_type": "stream", 620 | "text": [ 621 | "2017-10-24 10:50:49.733806: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.\n", 622 | "2017-10-24 10:50:49.734154: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.\n", 623 | "2017-10-24 10:50:49.734201: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.\n", 624 | "2017-10-24 10:50:49.734208: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.\n", 625 | "2017-10-24 10:50:49.734213: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.\n", 626 | "WARNING:root:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.\n" 627 | ] 628 | } 629 | ], 630 | "source": [ 631 | "%%ml transform --output transform --analysis analysis\n", 632 | "prefix: eval\n", 633 | "training_data:\n", 634 | " csv: eval.csv\n", 635 | " schema:\n", 636 | " - name: ticketid\n", 637 | " type: STRING\n", 638 | " - name: seniority\n", 639 | " type: FLOAT\n", 640 | " - name: experience\n", 641 | " type: STRING\n", 642 | " - name: category\n", 643 | " type: STRING\n", 644 | " - name: type\n", 645 | " type: STRING\n", 646 | " - name: impact\n", 647 | " type: STRING\n", 648 | " - name: priority\n", 649 | " type: STRING" 650 | ] 651 | }, 652 | { 653 | "cell_type": "markdown", 654 | "metadata": {}, 655 | "source": [ 656 | "# 5 of 7 Train\n", 657 | "This steps leverages Tensorflow canned models in the background without you having to write any code." 658 | ] 659 | }, 660 | { 661 | "cell_type": "code", 662 | "execution_count": 16, 663 | "metadata": { 664 | "collapsed": true 665 | }, 666 | "outputs": [], 667 | "source": [ 668 | "!rm -rf train" 669 | ] 670 | }, 671 | { 672 | "cell_type": "code", 673 | "execution_count": 17, 674 | "metadata": {}, 675 | "outputs": [ 676 | { 677 | "name": "stdout", 678 | "output_type": "stream", 679 | "text": [ 680 | "INFO:tensorflow:Using config: {'_save_checkpoints_secs': 300, '_num_ps_replicas': 0, '_keep_checkpoint_max': 5, '_task_type': None, '_is_chief': True, '_cluster_spec': , '_model_dir': '/content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train', '_save_checkpoints_steps': None, '_keep_checkpoint_every_n_hours': 10000, '_session_config': None, '_tf_random_seed': None, '_environment': 'local', '_num_worker_replicas': 0, '_task_id': 0, '_save_summary_steps': 100, '_tf_config': gpu_options {\n", 681 | " per_process_gpu_memory_fraction: 1.0\n", 682 | "}\n", 683 | ", '_evaluation_master': '', '_master': ''}\n", 684 | "WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/monitors.py:268: __init__ (from tensorflow.contrib.learn.python.learn.monitors) is deprecated and will be removed after 2016-12-05.\n", 685 | "Instructions for updating:\n", 686 | "Monitors are deprecated. Please use tf.train.SessionRunHook.\n", 687 | "WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:625: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 688 | "Instructions for updating:\n", 689 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 690 | "INFO:tensorflow:Create CheckpointSaverHook.\n", 691 | "2017-10-24 10:51:03.923540: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.\n", 692 | "2017-10-24 10:51:03.923604: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.\n", 693 | "2017-10-24 10:51:03.923619: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.\n", 694 | "2017-10-24 10:51:03.923632: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.\n", 695 | "2017-10-24 10:51:03.923644: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.\n", 696 | "INFO:tensorflow:Saving checkpoints for 1 into /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/model.ckpt.\n", 697 | "INFO:tensorflow:loss = 1.12583, step = 1\n", 698 | "WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:625: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 699 | "Instructions for updating:\n", 700 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 701 | "INFO:tensorflow:Starting evaluation at 2017-10-24-10:51:04\n", 702 | "INFO:tensorflow:Restoring parameters from /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/model.ckpt-1\n", 703 | "INFO:tensorflow:Finished evaluation at 2017-10-24-10:51:05\n", 704 | "INFO:tensorflow:Saving dict for global step 1: accuracy = 0.25573, global_step = 1, loss = 1.91327\n", 705 | "INFO:tensorflow:Validation (step 100): loss = 1.91327, global_step = 1, accuracy = 0.25573\n", 706 | "INFO:tensorflow:global_step/sec: 84.1192\n", 707 | "INFO:tensorflow:loss = 0.989993, step = 101 (1.189 sec)\n", 708 | "INFO:tensorflow:global_step/sec: 483.917\n", 709 | "INFO:tensorflow:loss = 1.05279, step = 201 (0.206 sec)\n", 710 | "INFO:tensorflow:global_step/sec: 533.906\n", 711 | "INFO:tensorflow:loss = 0.350223, step = 301 (0.189 sec)\n", 712 | "INFO:tensorflow:global_step/sec: 420.136\n", 713 | "INFO:tensorflow:loss = 0.634435, step = 401 (0.237 sec)\n", 714 | "INFO:tensorflow:global_step/sec: 362.673\n", 715 | "INFO:tensorflow:loss = 0.635942, step = 501 (0.276 sec)\n", 716 | "INFO:tensorflow:global_step/sec: 446.325\n", 717 | "INFO:tensorflow:loss = 0.501988, step = 601 (0.223 sec)\n", 718 | "INFO:tensorflow:global_step/sec: 439.047\n", 719 | "INFO:tensorflow:loss = 0.802113, step = 701 (0.228 sec)\n", 720 | "INFO:tensorflow:global_step/sec: 375.868\n", 721 | "INFO:tensorflow:loss = 0.544716, step = 801 (0.267 sec)\n", 722 | "INFO:tensorflow:global_step/sec: 374.925\n", 723 | "INFO:tensorflow:loss = 0.687425, step = 901 (0.266 sec)\n", 724 | "INFO:tensorflow:global_step/sec: 471.598\n", 725 | "INFO:tensorflow:loss = 0.824641, step = 1001 (0.212 sec)\n", 726 | "INFO:tensorflow:global_step/sec: 474.491\n", 727 | "INFO:tensorflow:loss = 0.803496, step = 1101 (0.210 sec)\n", 728 | "INFO:tensorflow:global_step/sec: 438.589\n", 729 | "INFO:tensorflow:loss = 0.287445, step = 1201 (0.229 sec)\n", 730 | "INFO:tensorflow:global_step/sec: 348.355\n", 731 | "INFO:tensorflow:loss = 0.958928, step = 1301 (0.287 sec)\n", 732 | "INFO:tensorflow:global_step/sec: 416.379\n", 733 | "INFO:tensorflow:loss = 0.403331, step = 1401 (0.240 sec)\n", 734 | "INFO:tensorflow:global_step/sec: 541.293\n", 735 | "INFO:tensorflow:loss = 0.897942, step = 1501 (0.184 sec)\n", 736 | "INFO:tensorflow:global_step/sec: 451.571\n", 737 | "INFO:tensorflow:loss = 0.98193, step = 1601 (0.222 sec)\n", 738 | "INFO:tensorflow:global_step/sec: 351.221\n", 739 | "INFO:tensorflow:loss = 0.359593, step = 1701 (0.286 sec)\n", 740 | "INFO:tensorflow:global_step/sec: 411.729\n", 741 | "INFO:tensorflow:loss = 0.668092, step = 1801 (0.241 sec)\n", 742 | "INFO:tensorflow:global_step/sec: 405.147\n", 743 | "INFO:tensorflow:loss = 0.321452, step = 1901 (0.247 sec)\n", 744 | "INFO:tensorflow:global_step/sec: 342.992\n", 745 | "INFO:tensorflow:loss = 0.971123, step = 2001 (0.291 sec)\n", 746 | "INFO:tensorflow:global_step/sec: 279.385\n", 747 | "INFO:tensorflow:loss = 0.562374, step = 2101 (0.359 sec)\n", 748 | "INFO:tensorflow:global_step/sec: 452.982\n", 749 | "INFO:tensorflow:loss = 0.5424, step = 2201 (0.220 sec)\n", 750 | "INFO:tensorflow:global_step/sec: 511.352\n", 751 | "INFO:tensorflow:loss = 0.105311, step = 2301 (0.195 sec)\n", 752 | "INFO:tensorflow:global_step/sec: 480.748\n", 753 | "INFO:tensorflow:loss = 0.702961, step = 2401 (0.208 sec)\n", 754 | "INFO:tensorflow:global_step/sec: 503.087\n", 755 | "INFO:tensorflow:loss = 0.514465, step = 2501 (0.199 sec)\n", 756 | "INFO:tensorflow:global_step/sec: 501.726\n", 757 | "INFO:tensorflow:loss = 0.291336, step = 2601 (0.199 sec)\n", 758 | "INFO:tensorflow:global_step/sec: 509.24\n", 759 | "INFO:tensorflow:loss = 0.542693, step = 2701 (0.197 sec)\n", 760 | "INFO:tensorflow:global_step/sec: 620.079\n", 761 | "INFO:tensorflow:loss = 0.405823, step = 2801 (0.161 sec)\n", 762 | "INFO:tensorflow:global_step/sec: 530.332\n", 763 | "INFO:tensorflow:loss = 0.604721, step = 2901 (0.189 sec)\n", 764 | "INFO:tensorflow:global_step/sec: 444.083\n", 765 | "INFO:tensorflow:loss = 0.498699, step = 3001 (0.225 sec)\n", 766 | "INFO:tensorflow:global_step/sec: 520.744\n", 767 | "INFO:tensorflow:loss = 0.803228, step = 3101 (0.192 sec)\n", 768 | "INFO:tensorflow:global_step/sec: 457.189\n", 769 | "INFO:tensorflow:loss = 0.479367, step = 3201 (0.219 sec)\n", 770 | "INFO:tensorflow:global_step/sec: 646.334\n", 771 | "INFO:tensorflow:loss = 0.619944, step = 3301 (0.155 sec)\n", 772 | "INFO:tensorflow:global_step/sec: 475.798\n", 773 | "INFO:tensorflow:loss = 0.421335, step = 3401 (0.211 sec)\n", 774 | "INFO:tensorflow:global_step/sec: 434.894\n", 775 | "INFO:tensorflow:loss = 0.423114, step = 3501 (0.229 sec)\n", 776 | "INFO:tensorflow:global_step/sec: 481.139\n", 777 | "INFO:tensorflow:loss = 0.709508, step = 3601 (0.208 sec)\n", 778 | "INFO:tensorflow:global_step/sec: 471.667\n", 779 | "INFO:tensorflow:loss = 0.850081, step = 3701 (0.212 sec)\n", 780 | "INFO:tensorflow:global_step/sec: 483.125\n", 781 | "INFO:tensorflow:loss = 0.319084, step = 3801 (0.207 sec)\n", 782 | "INFO:tensorflow:global_step/sec: 521.013\n", 783 | "INFO:tensorflow:loss = 0.914938, step = 3901 (0.192 sec)\n", 784 | "INFO:tensorflow:global_step/sec: 445.153\n", 785 | "INFO:tensorflow:loss = 0.602582, step = 4001 (0.225 sec)\n", 786 | "INFO:tensorflow:global_step/sec: 410.941\n", 787 | "INFO:tensorflow:loss = 0.79635, step = 4101 (0.243 sec)\n", 788 | "INFO:tensorflow:global_step/sec: 107.382\n", 789 | "INFO:tensorflow:loss = 0.734579, step = 4201 (0.931 sec)\n", 790 | "INFO:tensorflow:global_step/sec: 460.026\n", 791 | "INFO:tensorflow:loss = 0.259844, step = 4301 (0.217 sec)\n", 792 | "INFO:tensorflow:global_step/sec: 234.523\n", 793 | "INFO:tensorflow:loss = 0.327356, step = 4401 (0.428 sec)\n", 794 | "INFO:tensorflow:global_step/sec: 279.594\n", 795 | "INFO:tensorflow:loss = 0.128928, step = 4501 (0.356 sec)\n", 796 | "INFO:tensorflow:global_step/sec: 392.408\n", 797 | "INFO:tensorflow:loss = 0.2664, step = 4601 (0.255 sec)\n", 798 | "INFO:tensorflow:global_step/sec: 494.142\n", 799 | "INFO:tensorflow:loss = 0.398943, step = 4701 (0.202 sec)\n", 800 | "INFO:tensorflow:global_step/sec: 426.43\n", 801 | "INFO:tensorflow:loss = 0.478484, step = 4801 (0.235 sec)\n", 802 | "INFO:tensorflow:global_step/sec: 443.276\n", 803 | "INFO:tensorflow:loss = 0.54423, step = 4901 (0.226 sec)\n", 804 | "INFO:tensorflow:Saving checkpoints for 5000 into /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/model.ckpt.\n", 805 | "INFO:tensorflow:Loss for final step: 0.541655.\n", 806 | "WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:625: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 807 | "Instructions for updating:\n", 808 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 809 | "INFO:tensorflow:Starting evaluation at 2017-10-24-10:51:18\n", 810 | "INFO:tensorflow:Restoring parameters from /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/model.ckpt-5000\n", 811 | "INFO:tensorflow:Finished evaluation at 2017-10-24-10:51:18\n", 812 | "INFO:tensorflow:Saving dict for global step 5000: accuracy = 0.722309, global_step = 5000, loss = 0.57407\n", 813 | "INFO:tensorflow:Restoring parameters from /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/model.ckpt-5000\n", 814 | "INFO:tensorflow:Assets added to graph.\n", 815 | "INFO:tensorflow:No assets to write.\n", 816 | "INFO:tensorflow:SavedModel written to: /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/export/intermediate_prediction_models/1508842279/saved_model.pb\n", 817 | "INFO:tensorflow:Restoring parameters from /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/model.ckpt-5000\n", 818 | "INFO:tensorflow:Assets added to graph.\n", 819 | "INFO:tensorflow:No assets to write.\n", 820 | "INFO:tensorflow:SavedModel written to: /content/datalab/notebooks/sapaper-helpdesk/priority_classification/train/train/export/intermediate_evaluation_models/1508842280/saved_model.pb\n" 821 | ] 822 | } 823 | ], 824 | "source": [ 825 | "%%ml train --output train --analysis analysis\n", 826 | "training_data:\n", 827 | " transformed: './transform/train-*'\n", 828 | "evaluation_data:\n", 829 | " transformed: './transform/eval-*'\n", 830 | "model_args:\n", 831 | " model: dnn_classification\n", 832 | " max-steps: 5000\n", 833 | " save-checkpoints-secs: 300\n", 834 | " hidden-layer-size1: 256\n", 835 | " hidden-layer-size2: 128\n", 836 | " train-batch-size: 8\n", 837 | " eval-batch-size: 100\n", 838 | " learning-rate: 0.001" 839 | ] 840 | }, 841 | { 842 | "cell_type": "code", 843 | "execution_count": 18, 844 | "metadata": {}, 845 | "outputs": [ 846 | { 847 | "data": { 848 | "text/html": [ 849 | "

TensorBoard was started successfully with pid 2387. Click here to access it.

" 850 | ] 851 | }, 852 | "metadata": {}, 853 | "output_type": "display_data" 854 | } 855 | ], 856 | "source": [ 857 | "tensorboard_pid = ml.TensorBoard.start('./train')" 858 | ] 859 | }, 860 | { 861 | "cell_type": "code", 862 | "execution_count": 19, 863 | "metadata": { 864 | "collapsed": true 865 | }, 866 | "outputs": [], 867 | "source": [ 868 | "ml.TensorBoard.stop(tensorboard_pid)" 869 | ] 870 | }, 871 | { 872 | "cell_type": "markdown", 873 | "metadata": {}, 874 | "source": [ 875 | "# 6 of 7\n", 876 | "In this section, we will test our model to see how well it performs. For demo purposes, we are reusing the evaluation dataset. Consider using a 3rd separated dataset for production cases." 877 | ] 878 | }, 879 | { 880 | "cell_type": "code", 881 | "execution_count": 20, 882 | "metadata": { 883 | "collapsed": true 884 | }, 885 | "outputs": [], 886 | "source": [ 887 | "!rm -rf evalme" 888 | ] 889 | }, 890 | { 891 | "cell_type": "code", 892 | "execution_count": 21, 893 | "metadata": {}, 894 | "outputs": [ 895 | { 896 | "name": "stdout", 897 | "output_type": "stream", 898 | "text": [ 899 | "local prediction...\n", 900 | "INFO:tensorflow:Restoring parameters from train/evaluation_model/variables/variables\n", 901 | "done.\n" 902 | ] 903 | } 904 | ], 905 | "source": [ 906 | "%%ml batch_predict --model train/evaluation_model --output evalme\n", 907 | "format: csv\n", 908 | "prediction_data:\n", 909 | " csv: './eval.csv'" 910 | ] 911 | }, 912 | { 913 | "cell_type": "code", 914 | "execution_count": 22, 915 | "metadata": {}, 916 | "outputs": [ 917 | { 918 | "name": "stdout", 919 | "output_type": "stream", 920 | "text": [ 921 | "P2,0.651408,P2,t9\r\n", 922 | "P4,0.584897,P3,t15\r\n" 923 | ] 924 | } 925 | ], 926 | "source": [ 927 | "!head -n 2 evalme/predict_results_eval.csv" 928 | ] 929 | }, 930 | { 931 | "cell_type": "code", 932 | "execution_count": 23, 933 | "metadata": {}, 934 | "outputs": [ 935 | { 936 | "data": { 937 | "image/png": "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\n", 938 | "text/plain": [ 939 | "" 940 | ] 941 | }, 942 | "metadata": {}, 943 | "output_type": "display_data" 944 | } 945 | ], 946 | "source": [ 947 | "ml.ConfusionMatrix.from_csv(\n", 948 | " input_csv='./evalme/predict_results_eval.csv',\n", 949 | " schema_file='./evalme/predict_results_schema.json'\n", 950 | ").plot()" 951 | ] 952 | }, 953 | { 954 | "cell_type": "markdown", 955 | "metadata": {}, 956 | "source": [ 957 | "We see on this confusion matrix that the predictions are not too bad, the good ones being on the diagonale. Results might improve by doing extra prepration steps such as:\n", 958 | "- Feature crossing\n", 959 | "- Correlation analysis\n", 960 | "- Normalization" 961 | ] 962 | }, 963 | { 964 | "cell_type": "markdown", 965 | "metadata": {}, 966 | "source": [ 967 | "# 7 of 7 - Deploy the Model" 968 | ] 969 | }, 970 | { 971 | "cell_type": "code", 972 | "execution_count": null, 973 | "metadata": {}, 974 | "outputs": [], 975 | "source": [ 976 | "model_name = 'mdl_helpdesk_resolution_time'\n", 977 | "model_version = 'v1'\n", 978 | "\n", 979 | "storage_bucket = 'gs://' + google.datalab.Context.default().project_id + '-datalab-workspace/'\n", 980 | "storage_region = 'us-central1'" 981 | ] 982 | }, 983 | { 984 | "cell_type": "code", 985 | "execution_count": null, 986 | "metadata": { 987 | "collapsed": true 988 | }, 989 | "outputs": [], 990 | "source": [ 991 | "# Check that we have the model files created by the training\n", 992 | "!ls -R train/model" 993 | ] 994 | }, 995 | { 996 | "cell_type": "code", 997 | "execution_count": null, 998 | "metadata": { 999 | "collapsed": true 1000 | }, 1001 | "outputs": [], 1002 | "source": [ 1003 | "# Create a model\n", 1004 | "!gcloud ml-engine models create {model_name} --regions {storage_region}" 1005 | ] 1006 | }, 1007 | { 1008 | "cell_type": "code", 1009 | "execution_count": null, 1010 | "metadata": {}, 1011 | "outputs": [], 1012 | "source": [ 1013 | "# Create a staging bucket required to write staging files\n", 1014 | "# When creating a model from local files\n", 1015 | "staging_bucket = 'gs://' + google.datalab.Context.default().project_id + '-dtlb-staging-resolution'\n", 1016 | "!gsutil mb -c regional -l {storage_region} {staging_bucket}" 1017 | ] 1018 | }, 1019 | { 1020 | "cell_type": "code", 1021 | "execution_count": null, 1022 | "metadata": { 1023 | "collapsed": true 1024 | }, 1025 | "outputs": [], 1026 | "source": [ 1027 | "# Create our version of the model.\n", 1028 | "!gcloud ml-engine versions create {model_version} --model {model_name} --origin train/model --staging-bucket {staging_bucket}" 1029 | ] 1030 | }, 1031 | { 1032 | "cell_type": "markdown", 1033 | "metadata": {}, 1034 | "source": [ 1035 | "That version that is deployed is the one that you will be able to update automatically in a production environment if you need to update your model daily/weekly/monthly for example" 1036 | ] 1037 | } 1038 | ], 1039 | "metadata": { 1040 | "kernelspec": { 1041 | "display_name": "Python 2", 1042 | "language": "python", 1043 | "name": "python2" 1044 | }, 1045 | "language_info": { 1046 | "codemirror_mode": { 1047 | "name": "ipython", 1048 | "version": 2 1049 | }, 1050 | "file_extension": ".py", 1051 | "mimetype": "text/x-python", 1052 | "name": "python", 1053 | "nbconvert_exporter": "python", 1054 | "pygments_lexer": "ipython2", 1055 | "version": "2.7.13" 1056 | } 1057 | }, 1058 | "nbformat": 4, 1059 | "nbformat_minor": 2 1060 | } 1061 | -------------------------------------------------------------------------------- /ml/example_fields_analytics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 4, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "data": { 12 | "text/html": [ 13 | "\n", 14 | " \n", 15 | " \n", 22 | " " 23 | ], 24 | "text/plain": [ 25 | "" 26 | ] 27 | }, 28 | "metadata": {}, 29 | "output_type": "display_data" 30 | } 31 | ], 32 | "source": [ 33 | "import google.datalab.bigquery as bq" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 15, 39 | "metadata": { 40 | "collapsed": false 41 | }, 42 | "outputs": [ 43 | { 44 | "data": { 45 | "text/html": [ 46 | "\n", 47 | " \n", 48 | " \n", 55 | " " 56 | ], 57 | "text/plain": [ 58 | "" 59 | ] 60 | }, 61 | "metadata": {}, 62 | "output_type": "display_data" 63 | } 64 | ], 65 | "source": [ 66 | "def get_distinct_values(column_name):\n", 67 | " sql = \"\"\"\n", 68 | "SELECT\n", 69 | " {0},\n", 70 | " COUNT(1) AS num_tickets,\n", 71 | " AVG(resolutiontime) AS avg_rt\n", 72 | "FROM\n", 73 | " `mam-cloud.helpdesk.tickets`\n", 74 | "GROUP BY\n", 75 | " {0}\n", 76 | "ORDER BY {0}\n", 77 | " \"\"\".format(column_name)\n", 78 | " return bq.Query(sql).execute().result().to_dataframe()" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 18, 84 | "metadata": { 85 | "collapsed": false 86 | }, 87 | "outputs": [ 88 | { 89 | "data": { 90 | "text/html": [ 91 | "\n", 92 | " \n", 93 | " \n", 100 | " " 101 | ], 102 | "text/plain": [ 103 | "" 104 | ] 105 | }, 106 | "metadata": {}, 107 | "output_type": "display_data" 108 | }, 109 | { 110 | "data": { 111 | "text/plain": [ 112 | "" 113 | ] 114 | }, 115 | "execution_count": 18, 116 | "metadata": {}, 117 | "output_type": "execute_result" 118 | }, 119 | { 120 | "data": { 121 | "image/png": "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\nIk0AAAAASUVORK5CYII=\n", 122 | "text/plain": [ 123 | "" 124 | ] 125 | }, 126 | "metadata": {}, 127 | "output_type": "display_data" 128 | }, 129 | { 130 | "data": { 131 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdoAAAGiCAYAAABAlQcDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XtUVXXC//HPAbyAoIHC4/2GFj6J1cpsysQyzSskXlKz\ni9lTY2XaaKNpU01aZjZTK6enMbtN+FSmopmaZo+JWI26ysqatDGVTFwIBSiXFDjs3x8+8vOEeo7B\nl73P5v1aq7U6+5yz+ZzvEj5n377bY1mWJQAAYESI3QEAAHAzihYAAIMoWgAADKJoAQAwiKIFAMAg\nihYAAIPC/L3gwIED+sMf/iCPxyPLsvTjjz9q6tSpuu222+oiHwAAQc1zPtfRVlZWKikpScuXL1er\nVq1M5gIAwBXOa9fxp59+qvbt21OyAAAE6LyK9v3339fQoUNNZQEAwHUCLtry8nJ99NFHGjx4sN/X\nVlR4axQKAAC38Hsy1CmZmZm6+OKLFRMT4/e1BQWlNQplQmxslPLyiuyO4XiMU+AYq8AwToFhnALn\nxLGKjY0663MBb9GuW7dOw4YNq5VAAADUFwEV7fHjx/Xpp59qwIABpvMAAOAqAe06bty4sbZt22Y6\nCwAArsPMUAAAGETRAgBgEEULAIBBFC0AAAYFfB0tAACS5PV6lZW1v1bX2bFjZ4WGhtbqOp2CogUA\nnJesrP2a+sx7imgWVyvrKz2aq+f/mKL4+K61sr6a2ro1Q+3bd1SHDh1rZX0ULQDgvEU0i1NkdBu7\nY9Q6r9errVu36OqrvRQtAKB+mTXrQeXl5aqyskLDh49WZaVXhw8f1r33TpEkrV+/Vt99t0cPPPCg\n/vGPV7Rx43pFR8coNjZOCQndNHbsLWdc7/33/15du16or7/+Sn36XKuPP87Ul1/uVFraa3riiQVq\n3bpmXygoWgBAUJg9+zFFRUWpadOGGj48Vc8/v0j33DOxqmg3bdqo22+/U3v27FZm5ma98cZSlZeX\na+LEW5SQ0O2c666oqNDLL6dJkg4d+lG9e/dR3779aiU3RQsACArLlr2lrVu3KCwsRLm5uTp8OFut\nW7fVt99+o7Zt2+nHHw8qMfESLVv2tq65pq8aNGigBg0aqHfvPn7Xff31NxjLTdECABzviy8+186d\nn2nx4n+oTZvmGjv2ZpWXl+n66wdo06YP1aFDRyUlXfd/r7bOe/2NG4fXbuDTULQAgPNWejS3TtdV\nUlKsqKgoNWzYUPv27dO//vWNJCkp6Tq98car+v771rrnnvslST16XKpnnnlKt9wyQRUVFfr00626\n8cYRAeeJiIhQSUnJb/swZ0DRAgDOS8eOnfX8H1NqfZ3ncuWVV+vdd9N1yy03qWvXeHXvnihJioqK\nUseOnXXwYJYSEv5TkpSQ8J+65pokTZgwTjExzRUf31WRkZFnXbfH4/F5fP31N+jpp5/UihXv6Ikn\nnq7xyVAey7LOfxvbD6fdkFdy5o2CnYhxChxjFRjGKTCMU+ACGatffvlF4eHhOnHiuO67727NnPmw\nuna9yGims2GLFgDgOgsWPKmsrP0qLy/X4MHDjJasPxQtAMB1HnvsiWrLnn32aX399VfyeDyyLEse\nj0ejR4/T4MHDjGahaAEA9cK0aTNt+bncvQcAAIMoWgAADKJoAQAwiKIFAMAgihYAAIMoWgAADKJo\nAQAwiKIFAMAgihYAAIMoWgAADKJoAQAwiKIFAMAgihYAAIMoWgAADKJoAQAwiKIFAMAgihYAAIMo\nWgAADAqoaIuKijRlyhQNHjxYQ4cO1VdffWU6FwAArhAWyIuefPJJ9e3bVwsXLlRFRYWOHz9uOpe8\nXq+ysvbX2voKCiKVn19c4/V07NhZoaGhtZAIAFAf+C3a4uJiffbZZ5o/f/7JN4SFKTIy0niwrKz9\nmvrMe4poFmf8ZwWq9Giunv9jiuLju9odBQAQJPwW7aFDhxQdHa1Zs2Zpz5496t69ux5++GE1btzY\neLiIZnGKjG5j/OcAAGCK36KtqKjQt99+q0cffVSJiYl68skntXjxYk2ZMuWs74mOjlBYWM12rxYU\nmN9q/i1iYiIVGxtldwyj3P75ahNjFRjGKTCMU+CCaaz8Fm3Lli3VsmVLJSYmSpIGDhyoV1555Zzv\nKSgorXGw2jieakJ+frHy8orsjmFMbGyUqz9fbWKsAsM4BYZxCpwTx+pcxe/3rOMWLVqoVatWOnDg\ngCRp27Ztio+Pr710AAC4WEBnHf/pT3/Sgw8+qIqKCrVr105PPfWU6VwAALhCQEWbkJCg9PR001kA\nAHAdZoYCAMAgihYAAIMoWgAADKJoAQAwiKIFAMAgihYAAIMoWgAADKJoAQAwiKIFAMAgihYAAIMo\nWgAADKJoAQAwiKIFAMAgihYAAIMoWgAADKJoAQAwiKIFAMAgihYAAIMoWgAADKJoAQAwiKIFAMAg\nihYAAIMoWgAADKJoAQAwiKIFAMCgMLsDoOa8Xq+ysvbXyroKCiKVn19cK+vq2LGzQkNDa2VdABCs\nKFoXyMrar6nPvKeIZnF2R6lSejRXz/8xRfHxXe2OAgC2omhdIqJZnCKj29gdAwDwKxyjBQDAIIoW\nAACDKFoAAAyiaAEAMIiiBQDAIIoWAACDKFoAAAwK6Drafv36KTIyUiEhIQoLC9OKFStM5wIAwBUC\nKlqPx6MlS5aoWbNmpvMAAOAqAe06tixLlZWVprMAAOA6ARWtx+PRnXfeqZEjR2rZsmWmMwEA4BoB\n7TpeunSpYmNjlZ+frzvuuEOdO3dWz549z/r66OgIhYXV7K4tBQWRNXq/KTExkYqNjbI7hg/Gyj5u\n/3y1hXEKDOMUuGAaq4CKNjY2VpIUExOjAQMG6Ouvvz5n0RYUlNY4WG3dqq225ecXKy+vyO4YPhgr\ne8TGRrn689UWxikwjFPgnDhW5yp+v7uOf/nlF5WUlEiSSktL9fHHH6trV259BgBAIPxu0f7000+a\nPHmyPB6PvF6vkpOTdc0119RFNgAAgp7fom3Xrp1Wr15dF1kAAHAdZoYCAMAgihYAAIMoWgAADKJo\nAQAwiKIFAMAgihYAAIMoWgAADKJoAQAwKKC5jgE38Hq9ysraX2vrKyiIrJV5pjt27KzQ0JrdhAOA\nc1G0qDeysvZr6jPvKaJZnN1RqpQezdXzf0xRfDzzhwNuRdGiXoloFqfI6DZ2xwBQj3CMFgAAgyha\nAAAMomgBADCIogUAwCCKFgAAgyhaAAAMomgBADCIogUAwCCKFgAAgyhaAAAMomgBADCIogUAwCCK\nFgAAgyhaAAAMomgBADCIogUAwCCKFgAAgyhaAAAMomgBADCIogUAwCCKFgAAgyhaAAAMomgBADCI\nogUAwKCAi7ayslKpqamaNGmSyTwAALhKwEWblpam+Ph4k1kAAHCdgIo2JydHW7Zs0ejRo03nAQDA\nVQIq2nnz5mnGjBnyeDym8wAA4Cph/l6QkZGhFi1aqFu3btq+fXtAK42OjlBYWGiNghUURNbo/abE\nxEQqNjbK7hg+GKvAME72cfvnqy2MU+CCaaz8Fu3OnTv10UcfacuWLTpx4oRKSko0Y8YMLViw4Kzv\nKSgorXGw/PziGq/DhPz8YuXlFdkdwwdjFRjGyR6xsVGu/ny1hXEKnBPH6lzF77dop02bpmnTpkmS\nduzYoddee+2cJQsguHm9XmVl7a+19RUURNbal5yOHTsrNLRme8uAuua3aAHUL1lZ+zX1mfcU0SzO\n7ig+So/m6vk/pig+vqvdUarU5pcSvpC413kVba9evdSrVy9TWQA4RESzOEVGt7E7huM58UuJE7+Q\n1Hds0QJADfClBP4wBSMAAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0A\nAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEUL\nAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETR\nAgBgEEULAIBBFC0AAAZRtAAAGBTm7wVlZWUaP368ysvL5fV6NXDgQE2ePLkusgEAEPT8Fm3Dhg2V\nlpam8PBweb1ejRs3TklJSerRo0dd5AMAIKgFtOs4PDxc0smt24qKCqOBAABwk4CKtrKyUsOHD1fv\n3r3Vu3dvtmYBAAiQ313HkhQSEqJ3331XxcXFuvfee/X999+rS5cuZ319dHSEwsJCaxSsoCCyRu83\nJSYmUrGxUXbH8MFYBYZxCoxTx0lirALltHEyIZg+X0BFe0pkZKR69eqlrVu3nrNoCwpKaxwsP7+4\nxuswIT+/WHl5RXbH8MFYBYZxCoxTx0lirALltHGqbbGxUY77fOcqfr+7jvPz81VUdPIDHT9+XP/8\n5z/VuXPn2ksHAICL+d2izcvL00MPPaTKykpVVlZqyJAh6tu3b11kAwAg6Pkt2osuukirVq2qiywA\nALgOM0MBAGAQRQsAgEEULQAABlG0AAAYRNECAGAQRQsAgEEULQAABlG0AAAYRNECAGAQRQsAgEEU\nLQAABlG0AAAYRNECAGAQRQsAgEEULQAABlG0AAAYRNECAGAQRQsAgEEULQAABlG0AAAYRNECAGAQ\nRQsAgEEULQAABlG0AAAYRNECAGAQRQsAgEEULQAABlG0AAAYRNECAGAQRQsAgEEULQAABlG0AAAY\nRNECAGAQRQsAgEFh/l6Qk5OjGTNm6KefflJoaKhGjx6t2267rS6yAQAQ9PwWbWhoqGbNmqVu3bqp\npKREI0aMUO/evRUfH18X+QAACGp+dx3HxsaqW7dukqQmTZooPj5eubm5xoMBAOAG53WM9tChQ9qz\nZ4969OhhKg8AAK4ScNGWlJRoypQpmj17tpo0aWIyEwAAruH3GK0kVVRUaMqUKbrxxhvVv39/v6+P\njo5QWFhojYIVFETW6P2mxMREKjY2yu4YPhirwDBOgXHqOEmMVaCcNk4mBNPnC6hoZ8+erS5duuj2\n228PaKUFBaU1CiVJ+fnFNV6HCfn5xcrLK7I7hg/GKjCMU2CcOk4SYxUop41TbYuNjXLc5ztX8fvd\ndfz5559rzZo12rZtm4YPH67U1FRlZmbWakAAANzK7xbt5Zdfrt27d9dFFgAAXIeZoQAAMIiiBQDA\nIIoWAACDKFoAAAyiaAEAMIiiBQDAIIoWAACDKFoAAAyiaAEAMIiiBQDAIIoWAACDKFoAAAyiaAEA\nMIiiBQDAIIoWAACDKFoAAAyiaAEAMIiiBQDAIIoWAACDKFoAAAyiaAEAMIiiBQDAIIoWAACDKFoA\nAAyiaAEAMIiiBQDAIIoWAACDKFoAAAyiaAEAMIiiBQDAIIoWAACDKFoAAAyiaAEAMIiiBQDAIIoW\nAACD/Bbt7NmzdfXVVys5Obku8gAA4Cp+i3bEiBF69dVX6yILAACu47doe/bsqaZNm9ZFFgAAXIdj\ntAAAGBRmYqXR0REKCwut0ToKCiJrKU3tiomJVGxslN0xfDBWgWGcAuPUcZIYq0A5bZxMCKbPZ6Ro\nCwpKa7yO/PziWkhS+/Lzi5WXV2R3DB+MVWAYp8A4dZwkxipQThun2hYbG+W4z3eu4g9o17FlWbUW\nBgCA+sRv0U6fPl1jx47VgQMHdO211yo9Pb0ucgEA4Ap+dx3/9a9/rYscAAC4EmcdAwBgEEULAIBB\nFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBg\nEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAA\nGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0AAAZRtAAAGETRAgBgEEULAIBBFC0A\nAAYFVLSZmZkaNGiQBg4cqMWLF5vOBACAa/gt2srKSs2dO1evvvqq1q5dq3Xr1mnfvn11kQ0AgKDn\nt2h37dqlDh06qE2bNmrQoIGGDh2qTZs21UU2AACCXpi/Fxw5ckStWrWqevwf//Ef+vrrr42GOqX0\naG6d/JxAOS3P6ZyWzWl5TnFaLqflOcWJuZyYSXJeLqflOWXfvr21tq6Cgkjl5xfXeD3x8V1rIY1/\nHsuyrHO9YMOGDfrkk080d+5cSdLq1av19ddf609/+lOdBAQAIJj53XXcsmVLHT58uOrxkSNHFBcX\nZzQUAABu4bdoExMTdfDgQWVnZ6usrEzr1q3T9ddfXxfZAAAIen6P0YaGhuqRRx7RxIkTZVmWRo0a\npfj4+LrIBgBA0PN7jBYAAPx2zAwFAIBBFC0AAAZRtAAAGETRAgBgEEULAHC8srKygJY5EWcdA35s\n3LjxnM/fcMMNdZQkOHi9Xv3lL3/RzJkz7Y7iWIWFhed8/oILLqijJMEjNTVVq1at8rvMifxeRxts\n5s6dK4/Hc9bnmTrSV2VlpYYMGaINGzbYHcWxNm/efM7nKVpfoaGh+vzzz+2O4WgjRoyQx+PRmbZz\nPB4PN245TV5eno4cOaLjx4/r22+/rRqz4uJi/fLLLzanC4zrirZ79+6SpJ07d+r777/XkCFDJJ2c\ns5mJNqoLCQlRp06ddPjwYbVu3druOI701FNP2R0h6HTr1k2TJk3SoEGDFBERUbWcLyUnffTRR3ZH\nCBoff/yxVq5cqZycHM2fP7+qaCMjIzVt2jSb0wXGtbuOb7rpJr311lsKCzv5XaK8vFzjx4/XsmXL\nbE7mPOPHj9e3336rHj16KDw8vGr5okWLbEzlTBkZGdq7d69OnDhRtWzy5Mk2JnKmWbNmnXE5X1qq\nO3r0qH744Qeff1NXXHGFjYmc6YMPPtDAgQPtjvGbuG6L9pSjR4+quLi46lhHaWmpjh49anMqZ5o6\ndardEYLCo48+quPHj2v79u0aPXq0PvjgAyUmJtody5Eo1MAsX75caWlpysnJUUJCgr766itdeuml\nSktLszua4/zrX//SVVddpaZNm0o6+Tf+tdde0x/+8Aebk/nn2rOO7777bqWmpuqhhx7SQw89pNTU\nVE2aNMnuWI7Uq1cvde7cWSUlJSopKVF8fLx69epldyzH+eKLL7RgwQI1bdpUkydP1tKlS5WVlWV3\nLEfKycnRfffdp6uuukpXX3217r//fuXk5Ngdy3HS0tK0YsUKtW7dWkuWLNGqVauqigS+MjMzfcam\nWbNmyszMtDFR4FxbtCNHjtSyZcvUv39/9e/fX++8845SU1PtjuVI77//vkaPHq0NGzZo/fr1Vf8P\nX40bN5YkhYeH68iRI2rQoIHy8vJsTuVMs2bNUr9+/bR161ZlZmbquuuuO+vu5PqsYcOGatSokaST\nl6rEx8frwIEDNqdyJq/X63M5z/Hjx4Pm8h7X7jq2LEuffvqpfvzxR02ePFmHDx/Wrl271KNHD7uj\nOc6iRYu0YsUKNW/eXJKUn5+vCRMmaNCgQTYnc5Zrr71Wx44d05133ll11uioUaPsjuVI+fn5Gjly\nZNXjESNG6I033rAxkTO1bNlSx44dU//+/XXHHXeoadOmnJR4FikpKbr99turfvfS09M1fPhwu2MF\nxLUnQz322GMKCQnRtm3btH79eh09elQTJ05Uenq63dEcJzk5WWvWrKl6XFlZqRtvvNFnGXyVlZXp\nxIkTioqKsjuKI02YMEGpqakaNmyYJGnt2rVauXIlZXsOO3bsUFFRkfr06aOGDRvaHceRtmzZom3b\ntsmyLPXu3Vt9+vSxO1JAXLtFu2vXLq1atarqG0+zZs1UXl5ucypnuuaaa3TnnXdq6NChkk7uSk5K\nSrI5lfN4vV5lZGQoOztbXq+3avkdd9xhYypnmjdvnubMmaOnnnpKHo9Hl112mebNm2d3LMf58ssv\n1aVLF0VGRqpXr14qLi7W7t27dckll9gdzZH69u2rvn372h3jvLm2aMPCwuT1eqsmr8jPz1dIiGsP\nSdfIzJkz9cEHH2jnzp2yLEtjxozRgAED7I7lOJMmTVKjRo104YUX8m/Jj9atW3N5WAD+/Oc/+8xs\nFBERUW0ZTvryyy81d+5c7d+/X+Xl5fJ6vQoPD9fOnTvtjuaXa4v21ltv1X333aeff/5Zzz33nDZs\n2KAHHnjA7liONXDgwKC9Rq2u5OTksDvdj5dffll33XXXWWdou+CCC5SSkqL27dvbkM55LMvyGaeQ\nkBBVVFTYmMi55syZo+eee05Tp05Venq63n333aA569+1RZuSkqKLL764an/+iy++yMxQZ7Fx40b9\n5S9/0c8//yzLsqp++YPhm2JdSkpK0scff6xrrrnG7iiOdep37NQMbb9WWFioyZMn67333qvLWI7V\nrl07paWlady4cZKkt956S+3atbM5lXN16NBBXq9XoaGhGjlypIYPH67p06fbHcsv1xVtcXGxIiMj\nVVhYqObNm1cdd5RO/pIzWXd1zzzzjBYtWsQXET8uvfRSTZ48WZWVlQoLC+MLyRn069dPks55Kd3p\ns4/Vd48//rieeOIJ/f3vf5fH49FVV12luXPn2h3LkcLDw1VWVqZu3bppwYIFiouLU2Vlpd2xAuK6\ns45///vf66WXXlK/fv18dsmc+qPIZN3VjR07VkuXLrU7huP169dPL774oi666KJz3rgCJ8+JePnl\nl/X999/7TC3IjEf4rbKzs9WiRQuVl5frH//4h4qKinTzzTerQ4cOdkfzy3VbtHfddZckaf369VUX\nguPcunfvrgceeED9+/f3uayACeB9tWrVShdeeCElG4AHH3xQgwcPVkZGhh5//HGtWrVKMTExdsdy\nDH/HsrnLWHVt2rRRWVmZDh06pAEDBqhTp05BcxmU64p23rx5WrlypcaOHcuZewEqKSlReHi4Pvnk\nE5/lFK2vdu3a6dZbb1VSUpLPLziX91RXWFio0aNHKy0tTb169VKvXr18JrCo7/wdy0Z1GRkZeuyx\nx9S+fXtZlqVDhw7p8ccfD4rLfVxXtGFhYXrkkUeUk5OjJ554otrzfFOsjgngA9O2bVu1bdtW5eXl\nXJPtx6m7ZsXFxSkjI0NxcXHc1OM0p45lN27cWIMHD/Z5bv369XZEcrz58+crLS2talfxwYMHdffd\nd1O0dli0aJH++c9/6uOPP9bFF19sdxxHY/dV4Lxer0pKSjRz5ky7owSFe+65R0VFRZo5c6bmzp2r\nkpIS5jo+g8WLF1cr2jMtg9SkSROf47Ht2rVTkyZNbEwUONcVbUxMjIYOHar4+HglJCTYHcfR2H0V\nuNDQUM4uPg/XXXedJCkqKkpLliyxOY3zbNmyRZmZmTpy5IjPnrfi4mKFhobamMy5unfvrrvuukuD\nBw+Wx+PRhg0blJiYqI0bN0py9qEu1511zFYaTHnsscd05MgRDRo0SBEREVXLnfwLXtfOdLjmdPz+\nnbRnzx7t3r1bCxcu1JQpU6qWN2nSRFdeeaWaNWtmYzpn8rdHxMmHwFy3RctWWuD83Z+XKfR8lZWV\nKTo6Wtu3b/dZTtH+f0uXLlXXrl01ePBgxcXFyWXf42tNQkKCEhISlJycXHU8G+fm5CL1x3VbtGdS\nWVmp0tJSRUZG2h3FUX73u9+pVatWGjp0qC655JJqfxS5+TvOV0FBgTZs2KD3339fYWFhGjJkiG64\n4Qa20H5l6tSpev7555WcnHzG55nqs7off/xR//M//6Ps7GyfaSqDYYPAtUU7ffp0Pf744woJCdGo\n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\nTigxMVFz5sxRWNjJS+dfeuklrV27ViEhIYqIiNBbb72l5ORkZWdnq2PHjmrfvr2ef/55O4cEcA8L\ngO0SEhKs0tJSy7Is67rrrrMWLFhgWZZl7dq1y7r00kutt956y7Isy3r//fetcePGWZZlWYcOHbIu\nuugia/Xq1ZZlWdaOHTuspKQkq6yszLIsyyosLKxa/4wZM6ylS5dalmVZK1eutMaMGWOVlJT4vG77\n9u3WyJEjTX9UoN5x3cxQQDCyfrVjaciQIZKkiy++WMePH9fgwYMlSd27d9fBgwerXtewYUOlpKRI\nkq644go1btxYBw4cUJcuXfTKK69o69at8nq9KioqUnh4uCQpIyND48aNU0REhCSpWbNmxj8fUJ9R\ntIADNWrUSJIUEhJS7bHX6z3r+yzLksfj0Zo1a/TFF1/o7bffVnh4uF566SVlZWVVvQZA3eFkKMDh\nfl2Mpz8uKyvTmjVrJEmfffaZysrK1KlTJxUVFSk6Olrh4eEqKirS2rVrq97Tr18/vf322yopKZEk\nFRYWSpIiIyNVVFRk+uMA9Q5btIADnH5nqV/fZepcjy+44ALt3r1bL7/8siTp2WefVVhYmIYPH65N\nmzZpyJAhat68uXr27Knjx49LkoYPH67c3FyNGTNGoaGhioyM1JtvvqmLLrpInTp1UnJysjp37szJ\nUEAt4axjIEidOut427ZtdkcBcA7sOgaCGPdYBpyPLVoAAAxiixYAAIMoWgAADKJoAQAwiKIFAMAg\nihYAAIP+H/4pn5Lxv807AAAAAElFTkSuQmCC\n", 132 | "text/plain": [ 133 | "" 134 | ] 135 | }, 136 | "metadata": {}, 137 | "output_type": "display_data" 138 | } 139 | ], 140 | "source": [ 141 | "df = get_distinct_values('impact')\n", 142 | "df.plot(x='impact', y='num_tickets', logy=True, kind='bar')\n", 143 | "df.plot(x='impact', y='avg_rt', kind='bar')" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "metadata": { 150 | "collapsed": true 151 | }, 152 | "outputs": [], 153 | "source": [] 154 | } 155 | ], 156 | "metadata": { 157 | "kernelspec": { 158 | "display_name": "Python 2", 159 | "language": "python", 160 | "name": "python2" 161 | }, 162 | "language_info": { 163 | "codemirror_mode": { 164 | "name": "ipython", 165 | "version": 2 166 | }, 167 | "file_extension": ".py", 168 | "mimetype": "text/x-python", 169 | "name": "python", 170 | "nbconvert_exporter": "python", 171 | "pygments_lexer": "ipython2", 172 | "version": "2.7.12" 173 | } 174 | }, 175 | "nbformat": 4, 176 | "nbformat_minor": 2 177 | } 178 | -------------------------------------------------------------------------------- /ml/priority_classification.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "data": { 12 | "text/html": [ 13 | "\n", 14 | " \n", 15 | " \n", 22 | " " 23 | ], 24 | "text/plain": [ 25 | "" 26 | ] 27 | }, 28 | "metadata": {}, 29 | "output_type": "display_data" 30 | } 31 | ], 32 | "source": [ 33 | "# Only to use if Jupyter Notebook\n", 34 | "# %load_ext google.datalab.kernel" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 3, 40 | "metadata": { 41 | "collapsed": false 42 | }, 43 | "outputs": [ 44 | { 45 | "data": { 46 | "text/html": [ 47 | "\n", 48 | " \n", 49 | " \n", 56 | " " 57 | ], 58 | "text/plain": [ 59 | "" 60 | ] 61 | }, 62 | "metadata": {}, 63 | "output_type": "display_data" 64 | } 65 | ], 66 | "source": [ 67 | "from __future__ import division\n", 68 | "import os, hashlib, math\n", 69 | "import numpy as np\n", 70 | "import pandas as pd\n", 71 | "\n", 72 | "import sklearn.metrics as metrics\n", 73 | "import seaborn as sns\n", 74 | "import matplotlib.pyplot as plt\n", 75 | "\n", 76 | "import google.datalab.contrib.mlworkbench.commands\n", 77 | "import google.datalab.ml as ml" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "This Notebook shows you how to perform the basic steps in order to build, train and deploy a model on Google Cloud Platform using ML Toolbox\n", 85 | "\n", 86 | "1. Collect data\n", 87 | "2. Organize data\n", 88 | "3. Design the model\n", 89 | "4. Train and generate the model\n", 90 | "5. Deploy the model\n", 91 | "\n", 92 | "Note that we will build, train and deploy our model on this machine only as our dataset is small enough and a model created locally can still be deployed on Google ML Engine." 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 4, 98 | "metadata": { 99 | "collapsed": false 100 | }, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/html": [ 105 | "\n", 106 | " \n", 107 | " \n", 114 | " " 115 | ], 116 | "text/plain": [ 117 | "" 118 | ] 119 | }, 120 | "metadata": {}, 121 | "output_type": "display_data" 122 | } 123 | ], 124 | "source": [ 125 | "WORKING_FOLDER = 'priority'\n", 126 | "CSV_FILE = \"issues.csv\"" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 5, 132 | "metadata": { 133 | "collapsed": false 134 | }, 135 | "outputs": [ 136 | { 137 | "data": { 138 | "text/html": [ 139 | "\n", 140 | " \n", 141 | " \n", 148 | " " 149 | ], 150 | "text/plain": [ 151 | "" 152 | ] 153 | }, 154 | "metadata": {}, 155 | "output_type": "display_data" 156 | } 157 | ], 158 | "source": [ 159 | "!rm -rf $WORKING_FOLDER\n", 160 | "!mkdir $WORKING_FOLDER" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "# 1 of 7 - Collect data\n", 168 | "In a use case like this, data can be collected in different ways usually through dump of your database or export from your CRM into CSV. \n", 169 | "\n", 170 | "The data that we have collected is available on Google Cloud Storage as gs://solutions-public-assets/smartenup-helpdesk/ml/data.csv (public dataset).\n", 171 | "\n", 172 | "Note that in some cases, data might be saved in BigQuery (which is both a storage and queryuing engine) which is perfectly fine and would actually facilitate the filtering of the data specially with big datasets." 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 6, 178 | "metadata": { 179 | "collapsed": false 180 | }, 181 | "outputs": [ 182 | { 183 | "data": { 184 | "text/html": [ 185 | "\n", 186 | " \n", 187 | " \n", 194 | " " 195 | ], 196 | "text/plain": [ 197 | "" 198 | ] 199 | }, 200 | "metadata": {}, 201 | "output_type": "display_data" 202 | }, 203 | { 204 | "name": "stdout", 205 | "output_type": "stream", 206 | "text": [ 207 | "Copying gs://solutions-public-assets/smartenup-helpdesk/ml/issues.csv...\n", 208 | "/ [1 files][ 6.5 MiB/ 6.5 MiB] \n", 209 | "Operation completed over 1 objects/6.5 MiB. \n" 210 | ] 211 | } 212 | ], 213 | "source": [ 214 | "# Copy data from the cloud to this instance\n", 215 | "!gsutil cp gs://solutions-public-assets/smartenup-helpdesk/ml/issues.csv $CSV_FILE" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 7, 221 | "metadata": { 222 | "collapsed": false 223 | }, 224 | "outputs": [ 225 | { 226 | "data": { 227 | "text/html": [ 228 | "\n", 229 | " \n", 230 | " \n", 237 | " " 238 | ], 239 | "text/plain": [ 240 | "" 241 | ] 242 | }, 243 | "metadata": {}, 244 | "output_type": "display_data" 245 | }, 246 | { 247 | "name": "stdout", 248 | "output_type": "stream", 249 | "text": [ 250 | "100000 rows\n" 251 | ] 252 | }, 253 | { 254 | "data": { 255 | "text/html": [ 256 | "
\n", 257 | "\n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | "
ticketidcontactidseniorityexperiencecategorytypeimpactpriorityresolutiontime
0t0Patrick Blevins123-AdvancedPerformanceRequest4-CriticalP15
1t1Kaitlyn Ruiz32-ExperiencedTechnicalIssue1-MinorP46
2t2Chelsea Martin112-ExperiencedTechnicalRequest4-CriticalP12
3t3Richard Arnold82-ExperiencedPerformanceRequest1-MinorP35
4t4Kelly Jackson74-TrainerBillingRequest0-UnclassifiedP43
\n", 335 | "
" 336 | ], 337 | "text/plain": [ 338 | " ticketid contactid seniority experience category type \\\n", 339 | "0 t0 Patrick Blevins 12 3-Advanced Performance Request \n", 340 | "1 t1 Kaitlyn Ruiz 3 2-Experienced Technical Issue \n", 341 | "2 t2 Chelsea Martin 11 2-Experienced Technical Request \n", 342 | "3 t3 Richard Arnold 8 2-Experienced Performance Request \n", 343 | "4 t4 Kelly Jackson 7 4-Trainer Billing Request \n", 344 | "\n", 345 | " impact priority resolutiontime \n", 346 | "0 4-Critical P1 5 \n", 347 | "1 1-Minor P4 6 \n", 348 | "2 4-Critical P1 2 \n", 349 | "3 1-Minor P3 5 \n", 350 | "4 0-Unclassified P4 3 " 351 | ] 352 | }, 353 | "execution_count": 7, 354 | "metadata": {}, 355 | "output_type": "execute_result" 356 | } 357 | ], 358 | "source": [ 359 | "# Read data from csv into a Panda dataframe\n", 360 | "df_data = pd.read_csv(CSV_FILE, dtype=str)\n", 361 | "print '%d rows' % len(df_data)\n", 362 | "df_data.head()" 363 | ] 364 | }, 365 | { 366 | "cell_type": "markdown", 367 | "metadata": {}, 368 | "source": [ 369 | "We need to make sure that we do not have any duplicate. If that was the case some identical rows could be found in the training set and validation set for example which would have an impact on the model training (remember, validation and training set can't overlap)" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": 8, 375 | "metadata": { 376 | "collapsed": false 377 | }, 378 | "outputs": [ 379 | { 380 | "data": { 381 | "text/html": [ 382 | "\n", 383 | " \n", 384 | " \n", 391 | " " 392 | ], 393 | "text/plain": [ 394 | "" 395 | ] 396 | }, 397 | "metadata": {}, 398 | "output_type": "display_data" 399 | }, 400 | { 401 | "name": "stdout", 402 | "output_type": "stream", 403 | "text": [ 404 | "99993 rows\n" 405 | ] 406 | } 407 | ], 408 | "source": [ 409 | "df_data = df_data.drop_duplicates(df_data.columns.difference(['ticketid']))\n", 410 | "print '%d rows' % len(df_data)" 411 | ] 412 | }, 413 | { 414 | "cell_type": "markdown", 415 | "metadata": {}, 416 | "source": [ 417 | "# 2 of 7 - Organize data" 418 | ] 419 | }, 420 | { 421 | "cell_type": "markdown", 422 | "metadata": {}, 423 | "source": [ 424 | "## Filter data\n", 425 | "\n", 426 | "We keep only the columns that we are interested and discard some others:\n", 427 | "- ownerid because we won't know its value when doing a prediction\n", 428 | "- priority as it is a value that we will predict in another Notebook\n", 429 | "- statisfaction as it is not a value that we know when doing a prediction. It might also be a value we woud like to predict later" 430 | ] 431 | }, 432 | { 433 | "cell_type": "code", 434 | "execution_count": 9, 435 | "metadata": { 436 | "collapsed": false 437 | }, 438 | "outputs": [ 439 | { 440 | "data": { 441 | "text/html": [ 442 | "\n", 443 | " \n", 444 | " \n", 451 | " " 452 | ], 453 | "text/plain": [ 454 | "" 455 | ] 456 | }, 457 | "metadata": {}, 458 | "output_type": "display_data" 459 | }, 460 | { 461 | "name": "stdout", 462 | "output_type": "stream", 463 | "text": [ 464 | "99993 rows\n" 465 | ] 466 | }, 467 | { 468 | "data": { 469 | "text/html": [ 470 | "
\n", 471 | "\n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | "
ticketidseniorityexperiencecategorytypeimpactpriority
0t0123-AdvancedPerformanceRequest4-CriticalP1
1t132-ExperiencedTechnicalIssue1-MinorP4
2t2112-ExperiencedTechnicalRequest4-CriticalP1
3t382-ExperiencedPerformanceRequest1-MinorP3
4t474-TrainerBillingRequest0-UnclassifiedP4
\n", 537 | "
" 538 | ], 539 | "text/plain": [ 540 | " ticketid seniority experience category type impact \\\n", 541 | "0 t0 12 3-Advanced Performance Request 4-Critical \n", 542 | "1 t1 3 2-Experienced Technical Issue 1-Minor \n", 543 | "2 t2 11 2-Experienced Technical Request 4-Critical \n", 544 | "3 t3 8 2-Experienced Performance Request 1-Minor \n", 545 | "4 t4 7 4-Trainer Billing Request 0-Unclassified \n", 546 | "\n", 547 | " priority \n", 548 | "0 P1 \n", 549 | "1 P4 \n", 550 | "2 P1 \n", 551 | "3 P3 \n", 552 | "4 P4 " 553 | ] 554 | }, 555 | "execution_count": 9, 556 | "metadata": {}, 557 | "output_type": "execute_result" 558 | } 559 | ], 560 | "source": [ 561 | "def transform_data(df):\n", 562 | " # Lists the column names that we want to keep from our dataframe. \n", 563 | " interesting_columns = ['ticketid', 'seniority', 'experience', 'category', 'type', 'impact', 'priority']\n", 564 | " \n", 565 | " # Filters the dataframe to keep only the relevant data and return the dataframe.\n", 566 | " df = df[interesting_columns]\n", 567 | " return df\n", 568 | " \n", 569 | "df_data = transform_data(df_data)\n", 570 | "# Displays the new dataframe.\n", 571 | "print '%d rows' % len(df_data)\n", 572 | "df_data.head()" 573 | ] 574 | }, 575 | { 576 | "cell_type": "markdown", 577 | "metadata": {}, 578 | "source": [ 579 | "## Create datasets\n", 580 | "Here we create training and test datasets on a 80/20 basis. To keep consistency for every load of data we use a column that follows these two requirements:\n", 581 | "- Is a unique identifer for each row\n", 582 | "- Will not be used as a training input\n", 583 | "\n", 584 | "ticketid is a good candidate" 585 | ] 586 | }, 587 | { 588 | "cell_type": "code", 589 | "execution_count": 10, 590 | "metadata": { 591 | "collapsed": false 592 | }, 593 | "outputs": [ 594 | { 595 | "data": { 596 | "text/html": [ 597 | "\n", 598 | " \n", 599 | " \n", 606 | " " 607 | ], 608 | "text/plain": [ 609 | "" 610 | ] 611 | }, 612 | "metadata": {}, 613 | "output_type": "display_data" 614 | } 615 | ], 616 | "source": [ 617 | "def is_test_set(identifier, test_ratio, hash):\n", 618 | " h = int(hash(identifier.encode('ascii')).hexdigest()[-7:], 16)\n", 619 | " return (h/0xFFFFFFF) < test_ratio\n", 620 | " \n", 621 | "def create_datasets(df, id_column, test_ratio=0.2, hash=hashlib.md5):\n", 622 | " ids = df[id_column]\n", 623 | " ids_test_set = ids.apply(lambda x: is_test_set(x, test_ratio, hash))\n", 624 | " return df.loc[~ids_test_set], df.loc[ids_test_set]\n", 625 | "\n", 626 | "df_train, df_eval = create_datasets(df_data, 'ticketid')" 627 | ] 628 | }, 629 | { 630 | "cell_type": "code", 631 | "execution_count": 11, 632 | "metadata": { 633 | "collapsed": false 634 | }, 635 | "outputs": [ 636 | { 637 | "data": { 638 | "text/html": [ 639 | "\n", 640 | " \n", 641 | " \n", 648 | " " 649 | ], 650 | "text/plain": [ 651 | "" 652 | ] 653 | }, 654 | "metadata": {}, 655 | "output_type": "display_data" 656 | } 657 | ], 658 | "source": [ 659 | "# Set paths for CSV datasets\n", 660 | "training_data_path = './{}/train.csv'.format(WORKING_FOLDER)\n", 661 | "test_data_path = './{}/eval.csv'.format(WORKING_FOLDER)\n", 662 | "\n", 663 | "# Write Panda Dataframes to CSV files\n", 664 | "df_train.to_csv(training_data_path, header=False, index=False)\n", 665 | "df_eval.to_csv(test_data_path, header=False, index=False)" 666 | ] 667 | }, 668 | { 669 | "cell_type": "markdown", 670 | "metadata": {}, 671 | "source": [ 672 | "## Explore\n", 673 | "One of the most important part of Machine Learning is to explore the data before building a model. A few things will help improving your model quality such as\n", 674 | "- Normalization\n", 675 | "- Look at feature correlation\n", 676 | "- Feature crossing\n", 677 | "- ...\n", 678 | "\n", 679 | "Because the main goal of this Notebook and solution is to show how to build and deploy a model for serverless enrichment, we won't spend too much time here (also because the provided dataset is fake) but keep in mind that this is not a part that should be ignored in a real world example." 680 | ] 681 | }, 682 | { 683 | "cell_type": "markdown", 684 | "metadata": {}, 685 | "source": [ 686 | "One important thing in Classification though is to have a balanced set of Labels. " 687 | ] 688 | }, 689 | { 690 | "cell_type": "code", 691 | "execution_count": 12, 692 | "metadata": { 693 | "collapsed": false 694 | }, 695 | "outputs": [ 696 | { 697 | "data": { 698 | "text/html": [ 699 | "\n", 700 | " \n", 701 | " \n", 708 | " " 709 | ], 710 | "text/plain": [ 711 | "" 712 | ] 713 | }, 714 | "metadata": {}, 715 | "output_type": "display_data" 716 | }, 717 | { 718 | "name": "stdout", 719 | "output_type": "stream", 720 | "text": [ 721 | "Training set:\n", 722 | "P1 20099\n", 723 | "P3 20060\n", 724 | "P4 20019\n", 725 | "P2 19962\n", 726 | "Name: priority, dtype: int64\n" 727 | ] 728 | } 729 | ], 730 | "source": [ 731 | "print \"Training set:\\n{}\".format(df_train.priority.value_counts())" 732 | ] 733 | }, 734 | { 735 | "cell_type": "code", 736 | "execution_count": 13, 737 | "metadata": { 738 | "collapsed": false 739 | }, 740 | "outputs": [ 741 | { 742 | "data": { 743 | "text/html": [ 744 | "\n", 745 | " \n", 746 | " \n", 753 | " " 754 | ], 755 | "text/plain": [ 756 | "" 757 | ] 758 | }, 759 | "metadata": {}, 760 | "output_type": "display_data" 761 | }, 762 | { 763 | "name": "stdout", 764 | "output_type": "stream", 765 | "text": [ 766 | "Eval set:\n", 767 | "P2 5037\n", 768 | "P4 4979\n", 769 | "P3 4939\n", 770 | "P1 4898\n", 771 | "Name: priority, dtype: int64\n" 772 | ] 773 | } 774 | ], 775 | "source": [ 776 | "print \"Eval set:\\n{}\".format(df_eval.priority.value_counts())" 777 | ] 778 | }, 779 | { 780 | "cell_type": "markdown", 781 | "metadata": {}, 782 | "source": [ 783 | "# 3 of 7: Analyse\n", 784 | "ML Workbench comes with a pre-buit function that analyzes training data and generate stats, such as min/max/mean for numeric values, vocabulary for text columns. Note that if cloud is set to True, the function leverages BigQuery making the switch from small dataset to big data seamless." 785 | ] 786 | }, 787 | { 788 | "cell_type": "code", 789 | "execution_count": 21, 790 | "metadata": { 791 | "collapsed": false 792 | }, 793 | "outputs": [ 794 | { 795 | "data": { 796 | "text/html": [ 797 | "\n", 798 | " \n", 799 | " \n", 806 | " " 807 | ], 808 | "text/plain": [ 809 | "" 810 | ] 811 | }, 812 | "metadata": {}, 813 | "output_type": "display_data" 814 | } 815 | ], 816 | "source": [ 817 | "!rm -rf ./priority/analysis" 818 | ] 819 | }, 820 | { 821 | "cell_type": "code", 822 | "execution_count": 22, 823 | "metadata": { 824 | "collapsed": false 825 | }, 826 | "outputs": [ 827 | { 828 | "data": { 829 | "text/html": [ 830 | "\n", 831 | " \n", 832 | " \n", 839 | " " 840 | ], 841 | "text/plain": [ 842 | "" 843 | ] 844 | }, 845 | "metadata": {}, 846 | "output_type": "display_data" 847 | } 848 | ], 849 | "source": [ 850 | "%%ml dataset create\n", 851 | "format: csv\n", 852 | "train: ./priority/train.csv\n", 853 | "eval: ./priority/eval.csv\n", 854 | "name: issues_data_priority\n", 855 | "schema:\n", 856 | " - name: ticketid\n", 857 | " type: STRING\n", 858 | " - name: seniority\n", 859 | " type: FLOAT\n", 860 | " - name: experience\n", 861 | " type: STRING\n", 862 | " - name: category\n", 863 | " type: STRING\n", 864 | " - name: type\n", 865 | " type: STRING\n", 866 | " - name: impact\n", 867 | " type: STRING\n", 868 | " - name: priority\n", 869 | " type: STRING" 870 | ] 871 | }, 872 | { 873 | "cell_type": "code", 874 | "execution_count": 23, 875 | "metadata": { 876 | "collapsed": false 877 | }, 878 | "outputs": [ 879 | { 880 | "data": { 881 | "text/html": [ 882 | "\n", 883 | " \n", 884 | " \n", 891 | " " 892 | ], 893 | "text/plain": [ 894 | "" 895 | ] 896 | }, 897 | "metadata": {}, 898 | "output_type": "display_data" 899 | }, 900 | { 901 | "name": "stdout", 902 | "output_type": "stream", 903 | "text": [ 904 | "Expanding any file patterns...\n", 905 | "file list computed.\n", 906 | "Analyzing file /content/datalab/notebooks/priority/train.csv...\n", 907 | "file /content/datalab/notebooks/priority/train.csv analyzed.\n" 908 | ] 909 | } 910 | ], 911 | "source": [ 912 | "%%ml analyze\n", 913 | "output: ./priority/analysis\n", 914 | "data: $issues_data_priority\n", 915 | "features:\n", 916 | " ticketid: \n", 917 | " transform: key\n", 918 | " seniority:\n", 919 | " transform: identity\n", 920 | " experience:\n", 921 | " transform: one_hot\n", 922 | " category:\n", 923 | " transform: one_hot\n", 924 | " type:\n", 925 | " transform: one_hot\n", 926 | " impact:\n", 927 | " transform: one_hot\n", 928 | " priority:\n", 929 | " transform: target" 930 | ] 931 | }, 932 | { 933 | "cell_type": "markdown", 934 | "metadata": {}, 935 | "source": [ 936 | "# 4 of 7: Transform\n", 937 | "This section is optional but can be an important step when dealing with big data. While the Analysis phase provides enough details for the training step to append, the transform phase creates tfRecord files which is required for Tensorflow processing. Doing it now make sure that the training step can start from the preprocessed data and does not have to do this for each row for every pass of the data which is not recommended when handling text or image data." 938 | ] 939 | }, 940 | { 941 | "cell_type": "code", 942 | "execution_count": 24, 943 | "metadata": { 944 | "collapsed": false 945 | }, 946 | "outputs": [ 947 | { 948 | "data": { 949 | "text/html": [ 950 | "\n", 951 | " \n", 952 | " \n", 959 | " " 960 | ], 961 | "text/plain": [ 962 | "" 963 | ] 964 | }, 965 | "metadata": {}, 966 | "output_type": "display_data" 967 | } 968 | ], 969 | "source": [ 970 | "!rm -rf ./priority/transform" 971 | ] 972 | }, 973 | { 974 | "cell_type": "code", 975 | "execution_count": 25, 976 | "metadata": { 977 | "collapsed": false 978 | }, 979 | "outputs": [ 980 | { 981 | "data": { 982 | "text/html": [ 983 | "\n", 984 | " \n", 985 | " \n", 992 | " " 993 | ], 994 | "text/plain": [ 995 | "" 996 | ] 997 | }, 998 | "metadata": {}, 999 | "output_type": "display_data" 1000 | }, 1001 | { 1002 | "name": "stdout", 1003 | "output_type": "stream", 1004 | "text": [ 1005 | "WARNING:root:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.\n", 1006 | "WARNING:root:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.\n" 1007 | ] 1008 | } 1009 | ], 1010 | "source": [ 1011 | "%%ml transform\n", 1012 | "output: ./priority/transform\n", 1013 | "analysis: ./priority/analysis\n", 1014 | "data: $issues_data_priority" 1015 | ] 1016 | }, 1017 | { 1018 | "cell_type": "code", 1019 | "execution_count": 26, 1020 | "metadata": { 1021 | "collapsed": false 1022 | }, 1023 | "outputs": [ 1024 | { 1025 | "data": { 1026 | "text/html": [ 1027 | "\n", 1028 | " \n", 1029 | " \n", 1036 | " " 1037 | ], 1038 | "text/plain": [ 1039 | "" 1040 | ] 1041 | }, 1042 | "metadata": {}, 1043 | "output_type": "display_data" 1044 | } 1045 | ], 1046 | "source": [ 1047 | "%%ml dataset create\n", 1048 | "format: transformed\n", 1049 | "name: issues_data_priority_transformed\n", 1050 | "train: ./priority/transform/train-*\n", 1051 | "eval: ./priority/transform/eval-*" 1052 | ] 1053 | }, 1054 | { 1055 | "cell_type": "markdown", 1056 | "metadata": {}, 1057 | "source": [ 1058 | "# 5 of 7 Train\n", 1059 | "This steps leverages Tensorflow canned models in the background without you having to write any code." 1060 | ] 1061 | }, 1062 | { 1063 | "cell_type": "code", 1064 | "execution_count": 35, 1065 | "metadata": { 1066 | "collapsed": false 1067 | }, 1068 | "outputs": [ 1069 | { 1070 | "data": { 1071 | "text/html": [ 1072 | "\n", 1073 | " \n", 1074 | " \n", 1081 | " " 1082 | ], 1083 | "text/plain": [ 1084 | "" 1085 | ] 1086 | }, 1087 | "metadata": {}, 1088 | "output_type": "display_data" 1089 | } 1090 | ], 1091 | "source": [ 1092 | "!rm -rf ./priority/train" 1093 | ] 1094 | }, 1095 | { 1096 | "cell_type": "code", 1097 | "execution_count": 37, 1098 | "metadata": { 1099 | "collapsed": false 1100 | }, 1101 | "outputs": [ 1102 | { 1103 | "data": { 1104 | "text/html": [ 1105 | "\n", 1106 | " \n", 1107 | " \n", 1114 | " " 1115 | ], 1116 | "text/plain": [ 1117 | "" 1118 | ] 1119 | }, 1120 | "metadata": {}, 1121 | "output_type": "display_data" 1122 | }, 1123 | { 1124 | "data": { 1125 | "text/html": [ 1126 | "

TensorBoard was started successfully with pid 10914. Click here to access it.

" 1127 | ] 1128 | }, 1129 | "metadata": {}, 1130 | "output_type": "display_data" 1131 | } 1132 | ], 1133 | "source": [ 1134 | "%%ml train\n", 1135 | "output: ./priority/train\n", 1136 | "analysis: ./priority/analysis\n", 1137 | "data: $issues_data_priority_transformed\n", 1138 | "model_args:\n", 1139 | "model_args:\n", 1140 | " model: dnn_classification\n", 1141 | " max-steps: 5000\n", 1142 | " hidden-layer-size1: 256\n", 1143 | " hidden-layer-size2: 128\n", 1144 | " train-batch-size: 8\n", 1145 | " eval-batch-size: 100\n", 1146 | " learning-rate: 0.001" 1147 | ] 1148 | }, 1149 | { 1150 | "cell_type": "code", 1151 | "execution_count": null, 1152 | "metadata": { 1153 | "collapsed": false 1154 | }, 1155 | "outputs": [], 1156 | "source": [ 1157 | "tensorboard_pid = ml.TensorBoard.start('./{}/train'.format(WORKING_FOLDER))" 1158 | ] 1159 | }, 1160 | { 1161 | "cell_type": "code", 1162 | "execution_count": 30, 1163 | "metadata": { 1164 | "collapsed": false 1165 | }, 1166 | "outputs": [ 1167 | { 1168 | "data": { 1169 | "text/html": [ 1170 | "\n", 1171 | " \n", 1172 | " \n", 1179 | " " 1180 | ], 1181 | "text/plain": [ 1182 | "" 1183 | ] 1184 | }, 1185 | "metadata": {}, 1186 | "output_type": "display_data" 1187 | } 1188 | ], 1189 | "source": [ 1190 | "ml.TensorBoard.stop(tensorboard_pid)" 1191 | ] 1192 | }, 1193 | { 1194 | "cell_type": "markdown", 1195 | "metadata": {}, 1196 | "source": [ 1197 | "# 6 of 7\n", 1198 | "In this section, we will test our model to see how well it performs. For demo purposes, we are reusing the evaluation dataset. Consider using a 3rd separated dataset for production cases." 1199 | ] 1200 | }, 1201 | { 1202 | "cell_type": "code", 1203 | "execution_count": 38, 1204 | "metadata": { 1205 | "collapsed": false 1206 | }, 1207 | "outputs": [ 1208 | { 1209 | "data": { 1210 | "text/html": [ 1211 | "\n", 1212 | " \n", 1213 | " \n", 1220 | " " 1221 | ], 1222 | "text/plain": [ 1223 | "" 1224 | ] 1225 | }, 1226 | "metadata": {}, 1227 | "output_type": "display_data" 1228 | } 1229 | ], 1230 | "source": [ 1231 | "!rm -rf ./priority/evalme" 1232 | ] 1233 | }, 1234 | { 1235 | "cell_type": "code", 1236 | "execution_count": 40, 1237 | "metadata": { 1238 | "collapsed": false 1239 | }, 1240 | "outputs": [ 1241 | { 1242 | "data": { 1243 | "text/html": [ 1244 | "\n", 1245 | " \n", 1246 | " \n", 1253 | " " 1254 | ], 1255 | "text/plain": [ 1256 | "" 1257 | ] 1258 | }, 1259 | "metadata": {}, 1260 | "output_type": "display_data" 1261 | }, 1262 | { 1263 | "name": "stdout", 1264 | "output_type": "stream", 1265 | "text": [ 1266 | "local prediction...\n", 1267 | "INFO:tensorflow:Restoring parameters from ./priority/train/evaluation_model/variables/variables\n", 1268 | "done.\n" 1269 | ] 1270 | } 1271 | ], 1272 | "source": [ 1273 | "%%ml batch_predict\n", 1274 | "model: ./priority/train/evaluation_model/\n", 1275 | "output: ./priority/evalme\n", 1276 | "format: csv\n", 1277 | "data:\n", 1278 | " csv: ./priority/eval.csv" 1279 | ] 1280 | }, 1281 | { 1282 | "cell_type": "code", 1283 | "execution_count": 42, 1284 | "metadata": { 1285 | "collapsed": false 1286 | }, 1287 | "outputs": [ 1288 | { 1289 | "data": { 1290 | "text/html": [ 1291 | "\n", 1292 | " \n", 1293 | " \n", 1300 | " " 1301 | ], 1302 | "text/plain": [ 1303 | "" 1304 | ] 1305 | }, 1306 | "metadata": {}, 1307 | "output_type": "display_data" 1308 | }, 1309 | { 1310 | "name": "stdout", 1311 | "output_type": "stream", 1312 | "text": [ 1313 | "P2,0.85453,P2,t9\r\n", 1314 | "P4,0.858997,P3,t15\r\n" 1315 | ] 1316 | } 1317 | ], 1318 | "source": [ 1319 | "!head -n 2 ./priority/evalme/predict_results_eval.csv" 1320 | ] 1321 | }, 1322 | { 1323 | "cell_type": "code", 1324 | "execution_count": 43, 1325 | "metadata": { 1326 | "collapsed": false 1327 | }, 1328 | "outputs": [ 1329 | { 1330 | "data": { 1331 | "text/html": [ 1332 | "\n", 1333 | " \n", 1334 | " \n", 1341 | " " 1342 | ], 1343 | "text/plain": [ 1344 | "" 1345 | ] 1346 | }, 1347 | "metadata": {}, 1348 | "output_type": "display_data" 1349 | }, 1350 | { 1351 | "data": { 1352 | "image/png": "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\nlfm1du3a2fY3btyY1NRUABITE22Vj8DAQDIyMkhPT2fLli0EBwfbbu0ODg5m/fr1RERE3LDf4r3k\nVkRERP5gyud2C6xWK1FRUQQHBxMcHEyTJk1s+ywWC//9739tCUpaWhp+fn88H8vPzw+z2YzZbMbf\n39/W7uvri9lsvmm/SkhEREQcRGFP2QC4uLjYpmt27NjBvn37bPsmTJhAixYtCAq68jbzv64HNQwD\nk8l03XWiefWvhERERMRBFEVCclWZMmW47777WL9+PQBxcXGcPn2asWPH2o7x9fW1Td8ApKam4uPj\ng5+fH8eOHbum/WaUkIiIiAgAp06dsi0+vXTpEps2baJ27dp88cUXbNiwgbfeeivX8WFhYSQkJACw\nfft2vLy88Pb2JiQkhOTkZDIyMjh79izJycmEhITctO8iunNaRERE8quwb/s9ceIEY8aMwWq1YrVa\niYiIIDQ0lLvvvpuqVavSp08fTCYTnTp1YtiwYYSGhrJu3To6deqEp6cnr732GgDlypVj2LBh9OzZ\nE5PJRExMTJ6vjFFCIiIi4iAKOyFp0KAB8fHx17T/8ssvNzxn3Lhx122Pjo4mOjr6lvtWQiIiIuIo\nnOvBvbkoIREREXEQzvxGay1qFREREbtThURERMRBOHOFRAmJiIiIg1BCIiIiInbnzAmJ1pCIiIiI\n3alCIiIi4iict0CihERERMRROPOUjRISERERB+HMCYnWkIiIiIjdqUIiIiLiIJy5QqKERERExFE4\nbz6ihERERMRRqEIiIiIidufMCYkWtYqIiIjdqUIiIiLiIJy5QqKERERExEEoIRERERH7c958RGtI\nRERExP5UIREREXEQmrIRERERu1NCIiIiInanhERERETsz3nzES1qFREREftThURERMRBaMpGRERE\n7M6ZExJN2YiIiDgIk8mUry0vqampDBgwgIiICCIjI5kzZw4Au3fvpm/fvkRFRdGrVy927txpO2fi\nxIl07tyZ7t27s2vXLlt7fHw84eHhhIeHk5CQkGffqpCIiIgIAK6urowdO5aAgAAyMzPp2bMnwcHB\nTJ48meHDhxMSEsK6deuYPHkyn376KevWrSMlJYWVK1eyY8cOxo8fz8KFCzl79izTp08nPj4ewzCI\njo4mLCyMsmXL3rBvVUhEREQcRGFXSCpXrkxAQAAApUuXpnbt2qSlpWEymcjIyAAgIyMDX19fABIT\nE4mKigIgMDCQjIwM0tPT2bBhA8HBwZQtWxYvLy+Cg4NZv379Tft2mArJjje72zuEQuFs46oV+oy9\nQyhwcT3imPnydHuHUaAeuucVe4dQ4ILrVWD7oTP2DqPABdYoZ+8QCpabC5Ycq72jKHhuRfT7fREu\nITly5Ai7d++mSZMmjB07ln/+85+8/vrrGIbBggULAEhLS8PPz892jp+fH2azGbPZjL+/v63d19cX\ns9l80/5UIREREXEQhV0huSozM5MRI0YQGxtL6dKlmT9/Pv/617/49ttvGTt2LLGxsQAYhpHrPMMw\nMJlM17Rfjf1mlJCIiIiIjcViYcSIEXTv3p2OHTsCkJCQYPu6S5cu/PTTT8CVykdqaqrt3NTUVHx8\nfPDz8+PYsWPXtN+MEhIREREHURQVktjYWOrWrcvAgQNtbb6+vnz33XcAbNq0iZo1awIQFhZmu4Nm\n+/bteHl54e3tTUhICMnJyWRkZHD27FmSk5MJCQm5ab8Os4ZERETkTlfYjyH54YcfWLp0KfXr1ycq\nKgqTycQzzzzDK6+8wsSJE7FarZQoUYJXXrmyDi00NJR169bRqVMnPD09ee211wAoV64cw4YNo2fP\nnphMJmJiYvDy8rpp30pIREREHERhPxitWbNmuZ4l8meLFy++bvu4ceOu2x4dHU10dPQt962ERERE\nxEE48YNatYZERERE7E8VEhEREQfhzO+yUUIiIiLiIJw4H1FCIiIi4ihcXJw3I9EaEhEREbE7VUhE\nREQchKZsRERExO60qFVERETszonzEa0hEREREftThURERMRBaMpGRERE7E4JiYiIiNidE+cjSkhE\nREQchTNXSLSoVUREROxOFRIREREH4cQFEiUkIiIijsKZp2yUkIiIiDgIJ85HtIZERERE7E8VEhER\nEQehKRsRERGxOyfOR5SQiIiIOApnrpBoDYmIiIjYnSokIiIiDsKJCyRKSERERByFM0/ZKCERERFx\nEE6cj2gNiYiIiKMwmUz52vKSmprKgAEDiIiIIDIykjlz5uTaP3PmTBo2bMiZM2dsbRMnTqRz5850\n796dXbt22drj4+MJDw8nPDychISEPPtWhUREREQAcHV1ZezYsQQEBJCZmUl0dDTBwcHUqVOH1NRU\nkpOTqVKliu34devWkZKSwsqVK9mxYwfjx49n4cKFnD17lunTpxMfH49hGERHRxMWFkbZsmVv2Lcq\n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\niY8//pj3338fDw8PW3uHDh1Yvnw52dnZHD58mJSUFJo0aULjxo1JSUnh6NGjZGdns2zZMsLCwvLs\nVxUSERERAWDUqFFs2bKFM2fO0L59e4YPH86MGTO4fPkygwcPBiAwMJCXXnqJunXr0rVrV7p164ab\nmxvjx4/HZDLh6urKiy++yODBgzEMg169elGnTp08+1ZCIiIi4iAKe8pmypQp17T17NnzhscPGTKE\nIUOGXNPerl072rVr97f6VkIiIiLiIExO/KxWJSQiIiIOwonfradFrSIiImJ/qpCIiIg4iFu9U8YR\nKSERERFxEM48ZaOERERExEEoIRERERG7c+a7bLSoVUREROxOFRIREREH4eK8BRIlJCIiIo7Cmads\nlJCIiIg4CGde1Ko1JCIiImJ3qpCIiIg4CE3ZiIiIiN1pUauIiIjYnTNXSLSGREREROxOFRIREREH\n4cx32SghERERcRBOnI8oIREREXEULk5cIlFCIgWqRiVPe4dQKJxtXB9M7G3vEAqFM46r46Ap9g6h\nQF3cMNHpxgRXxlUUnDcd0aJWERERKQZUIREREXEUTlwiUUIiIiLiIJz5OSRKSERERByEE69p1RoS\nERERsT9VSERERByEExdIVCERERFxGKZ8brdg9uzZ3H///URGRjJq1Ciys7M5cuQIffr0ITw8nGef\nfRaLxQJAdnY2zzzzDJ07d6Zv374cO3bstoemhERERMRBmPL5X17MZjOffvopixcvZunSpeTk5LBs\n2TLefPNNBg0axIoVKyhbtiyLFi0CYNGiRZQrV46VK1cycOBAJk+efNtjU0IiIiLiIEym/G23wmq1\ncvHiRSwWC5cuXcLHx4ctW7YQHh4OQI8ePVi9ejUAiYmJ9OjRA4Dw8HA2bdp022NTQiIiIiIA+Pr6\nMmjQINq3b0+7du0oW7YsjRo1wsvLCxeXKymDn58fZrMZgLS0NPz8/ABwdXXFy8uLM2fO3FbfSkhE\nREQcRGEvITl37hyJiYmsXbuW9evXc/HiRZKSkq6N43/lFsMwcrUbhmHb93cpIREREXEUhZyRJCcn\nU716dcqXL4+rqysdO3bkxx9/5Ny5c1itVgBSU1Px8fEBrlRUUlNTAcjJyeH8+fOUK1futoamhERE\nRMRBFPai1ipVqrBjxw6ysrIwDIPNmzdTr149WrZsyTfffANAfHw8YWFhAHTo0IH4+HgAvvnmG1q1\n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\n3TbdB1tERMRLlMWt46+8EjY7O5sqVaq4vzqjVq1anD5d8r1CZ86coVatWgDY7XYqV67M+fPnf1bb\nlJCIiIh4CU+XkGzevJnq1asTGxvrrgM1xlxVE3opWblyuTHmZ5dr6LtsREREvISnSzPT0tLYuHEj\nW7ZscV8JO23aNC5evIjL5cLPz4/MzExq1qwJQFRUFJmZmURFRVFcXExOTg5Vrvh6lJulERIREREB\nrn0l7KxZs7jzzjtJTk4GYMWKFXTt2hWA+Ph4Vqwo+dbs5ORkOnbs+LOPrYRERETEa1jzZTbjx4/n\n3XffJSEhgQsXLtC/f38AHnroIbKzs+nevTvvv/8+48ePv8GefpymbERERLxEWd5No0OHDnTo0AGA\nevXqsXTp0qvWCQwM5M9//vMtOZ4SEhERES/hy3f30pSNiIiIWE4jJCIiIl7Cl2+AroRERETES9zs\n3Va9kRISERERb+G7+YhqSERERMR6GiERERHxEj48QKKERERExFuoqFVEREQsp6JWERERsZ7v5iMq\nahURERHraYRERETES/jwAIkSEhEREW+holYRERGxnC8XtaqGRERERCynERIREREv4ctTNhohERER\nEctphERERMRL+PIIiRISERERL6GiVhEREREP0giJiIiIl9CUjYiIiFjOh/MRJSQiIiJew4czEtWQ\niIiIiOU0QiIiIuIlfPkqGyUkIiIiXsKXi1o1ZSMiIuIlbL/wcTNSU1Pp0aMHCQkJzJ8//9Y24DqU\nkIiIiAgALpeLKVOm8M4777B69WrWrFnDsWPHyuTYSkhERES8hYeHSPbv30+DBg2oU6cOAQEB9OrV\ni5SUFA805GpKSERERLyE7Rf+3Mjp06e57bbb3L9HRUVx5swZTzbJTUWtIiIiXsLTRa3GGM8e4Dq8\nJiEJ8ppIfxrfa5dvloAHB/hWu4a2r291CB7hi+0aunWq1SHccvk+2Kay4un3jFq1anHq1Cn376dP\nn6ZmzZqePei/acpGREREAGjdujXHjx/n5MmTFBUVsWbNGrp27Vomx/a5z+ciIiLy89jtdv74xz8y\nfPhwjDH079+fJk2alMmxbcbKCSMRERERNGUjIiIi5YASEhEREbGcEhIRERGxnBISERERsZwSEhER\nKVO6lkKuRQmJRXRCeoeioiKrQ5CbkJuba3UIchMyMzMBsNlseg2Uq+g+JGVsz549+Pv7c/vtt2OM\nwebp+wDLz5aamkpKSgo2m41BgwbRrFkz/PyUw5c3W7ZsYdWqVURERNCvXz9atGhhdUhyDWlpaTzz\nzDMMHjyYYcOGuZMSvQbKJXp1LUNbt25lxIgRjB07lu3bt+tTQjmWmprKtGnTiI+Px+l08t5771Fc\nXGx1WHKFzZs3M3v2bPr27Ut+fj5/+9vfrA5JfoS/vz81atRg7969vP766wBKRqQU+6RJkyZZHYSv\nM8bgdDpZtmwZiYmJ9OzZkxkzZtCwYUPq16+Py+XSiVlOGGPIyclh7ty5PPzww9x///3Ex8ezYMEC\n/Pz8iI2NtTpEAVwuF/n5+cycOZPHH3+ce+65hwYNGpCSkkJWVhZ2u53Q0FACAwOtDrVCuzQCYozB\n4XBw8OBBHn74YVJTUzl37hzVqlXD6XQSHBxsdahSDighKQNOp5OAgADat29PvXr1iImJITQ0lNde\ne4369evToEED93qaErDWpRfHli1b0qZNG1wuF3a7naNHjxIUFESbNm0ANNRsMYfDQVBQEAkJCURH\nR5OVlcWgQYPo1KkT+fn5HDx4EKfTSXR0tNWhVmjFxcX4+flhs9moUqUKX3zxBa1ataJTp0689dZb\n/PWvf6Vr165Ur15d55SohsTTtm3bxvLly2nWrBmxsbF06dIFgMTERIwxTJ8+nRo1anDhwgUuXLhA\nt27ddFJa5FJfxcbG0rBhQ3eiCHDbbbe5Cyc3b95M5cqVadeunVWhVmiX+ql58+Y0bdqU+Ph4IiMj\neeWVV2jfvj0Af/nLX9izZw89evSwONqK61I/xcTE0LhxY7p164bdbqegoABjDOnp6TRo0ICtW7fS\nvHlzve6Jakg8KTU1lVdeeYW2bdtSUFBAcnIyX375JVDyCbtPnz5MmDCBgQMHMmrUKKKjo3VSWuTy\nvsrNzWXjxo3s27fP/XxxcTHGGNavX8+f/vSnMvs6bint8n7Kz89n/fr17NmzB4C4uDh3TVZUVBQO\nhwOHw2FluBXW5f2Ul5fHhg0bSE9Pp2fPnrz99ts89dRTPP/880ycOJGjR4/y/fffWx2ylAdGPCI7\nO9s0b97cpKSkGGOMOXXqlBkzZoxZv359qfVWrlxpOnfubI4cOWJFmGJurq/WrVtnunTpYoYMGWIO\nHz5sVagV2s2eU0uWLDGJiYnqJ4tcr5/OnDljnn32WfPZZ58ZY4wpLCw0eXl5VoYr5YhGSDwkIiKC\nefPmMXv2bHJycrjtttvw9/fn3LlzQElRXlFREf/85z955513NNdtoRv1FZR84rbZbLzwwgs0bdrU\nwmgrrhv1U1FREV9//TXr1q1jxowZ6ieLXKuf7HY7WVlZ1KhRg9///vd07twZYwyBgYEqaBU31ZB4\n0L333ovNZqNv37507tyZgoICkpKSgJLL3QIDA5kwYQJ2u93iSOV6fWWMITY2lo8++ojKlStbHGnF\ndr1+CgwMpGnTpsydO5fQ0FCLI63YruynwsJCevfuDUB4eDigS37lajZjdCMMT9u+fTvDhw9n27Zt\nREZGUlhYSKVKlawOS67hyr4qKCggKCjI6rDkCuon76DXPvkpNGVTBjp16sSbb77JI488QlZWlk7I\ncuzKvtKbXPmkfvIOeu2Tn0JTNmWkS5cuOBwOHnvsMZYvX47NZtOQZTmlvvIO6ifvoH6Sm6UpmzKW\nm5ur+W0vob7yDuon76B+khtRQiIiIiKWUw2JiIiIWE4JiYiIiFhOCYmIiIhYTgmJiIiIWE4Jicgt\nFh8fzwMPPEBiYiK//vWv+eSTT27Zfo8ePQrAk08+yXfffXfd9Tds2MCBAwd+1rFWrFjB6NGjbxjH\n9cTExJCfn/+Tjnvy5Ek6duz4k7YREd+g+5CIeMDcuXNp0qQJX331FYMGDaJTp05ERESUWsflcuHn\nd/OfCS6/d8Obb755w/VTUlJo1aoVrVu3vvnAf+R4Zbm97lEhUjEpIRHxgEtX08fGxhIaGsqJEyfY\ntGkTq1atIjQ0lIyMDGbOnElkZCRTpkwhMzOTgoICHnzwQZ544gkAdu/ezeTJk7HZbLRv357Lr9CP\nj49n/vz5REdHc/r0aV566SXS09Ox2Wz06tWLFi1asHHjRnbs2MGyZcsYNmwYiYmJrFy5kkWLFlFc\nXEx4eDgTJ06kUaNGOBwOpkyZwueff07VqlWJjY29qXa+++67fPLJJxQXFxMYGMikSZOIiYlx/w3e\nfvttUlJSKCwsZOzYsXTv3h2A/fv3M2vWLHJzcwEYPXo0Xbp0uWV/fxHxPkpIRDxo586dFBUV0bBh\nQ44cOcK+fftYtWoVdevWBWD48OE8/fTTxMXF4XA4GDZsGK1bt+aOO+5g3LhxzJkzh7i4ONauXcui\nRYuueYzf/e533Hfffbz22msAnD9/noiICOLj42nVqhWDBw8GShKctWvXsnDhQgICAkhNTeW5555j\n8eLFLFmyhJMnT7J27VqKiooYPHiwO8br6dOnD48++igAO3bsYOLEifz97393P+/v78/KlSv59ttv\nGTRoEHFxcQQEBDBx4kTeeustqlevztmzZ+nfvz9r1qz5RX9rEfFuSkhEPGD06NFUqlSJsLAw5s6d\nS1hYGAB33HGH+40+Pz+fXbt2kZ2d7R79yMvL49ixY1SrVo3g4GDi4uIA6NmzJy+88MJVx8nLy2Pv\n3r28//777mVXTg1dsmnTJg4dOsSAAQMwxmCM4eLFiwDs2rWLpKQk/Pz8CAoKonfv3qSlpd2wnQcO\nHGD+/PlcuHABm81GRkZGqef79+8PQKNGjWjVqhX79u3Dz8+PEydO8Pjjj7vbbbfbycjI+NHYRcT3\nKSER8YBLNSRXCgkJcf/b5XJhs9lYvnz5VbUkX3/99U0fy2azYYy5Ye2FMYZ+/foxatSoaz73Uzkc\nDsaMGcPixYuJiYnhzJkzV027XL7fS+2FkoLXBQsWXLXPkydP/uQ4RMQ36CobEQ+4mTf40NBQ4uLi\nmDdvnntZZmYmWVlZNG7cmMLCQnbv3g1AcnKyezTjciEhIbRt25b33nvPvSw7O9u9/5ycHPfy+Ph4\nVq5cyenTp4GSBOHgwYMA3HXXXXz00UcUFxdTUFDA6tWrbxh/YWEhLpeLqKgoABYuXHjVOh9++CEA\n6enpfP3117Rp04a2bduSnp7O559/7l7v8quB9G0WIhWTRkhEbrGfcpXIrFmzmDZtGr1798YYQ1hY\nGNOmTSMyMpLZs2czadIk/Pz8aN++PbVr177mMWbMmMGLL77IihUrsNvtPPjggzz22GMkJiby7LPP\nkpyc7C5qHTt2LE899RQulwuHw0GPHj1o2bIlAwYM4NChQzzwwANUrVqVNm3acO7cueu2LywsjNGj\nR9OvXz+qVq1KQkLCVes5nU6SkpIoKChgypQpVKtWDYA33niD6dOn8/LLL1NUVET9+vXdiZmushGp\nmPTleiIiImI5TdmIiIiI5ZSQiIiIiOWUkIiIiIjllJCIiIiI5ZSQiIiIiOWUkIiIiIjllJCIiIiI\n5ZSQiIiIiOX+D/d1LqpGJxqMAAAAAElFTkSuQmCC\n", 1353 | "text/plain": [ 1354 | "" 1355 | ] 1356 | }, 1357 | "metadata": {}, 1358 | "output_type": "display_data" 1359 | } 1360 | ], 1361 | "source": [ 1362 | "ml.ConfusionMatrix.from_csv(\n", 1363 | " input_csv='./{}/evalme/predict_results_eval.csv'.format(WORKING_FOLDER),\n", 1364 | " schema_file='./{}/evalme/predict_results_schema.json'.format(WORKING_FOLDER)\n", 1365 | ").plot()" 1366 | ] 1367 | }, 1368 | { 1369 | "cell_type": "markdown", 1370 | "metadata": {}, 1371 | "source": [ 1372 | "We see on this confusion matrix that the predictions are not too bad, the good ones being on the diagonale. Results might improve by doing extra prepration steps such as:\n", 1373 | "- Feature crossing\n", 1374 | "- Correlation analysis\n", 1375 | "- Normalization" 1376 | ] 1377 | }, 1378 | { 1379 | "cell_type": "markdown", 1380 | "metadata": {}, 1381 | "source": [ 1382 | "# 7 of 7 - Deploy the Model" 1383 | ] 1384 | }, 1385 | { 1386 | "cell_type": "code", 1387 | "execution_count": null, 1388 | "metadata": { 1389 | "collapsed": true 1390 | }, 1391 | "outputs": [], 1392 | "source": [ 1393 | "model_name = 'mdl_helpdesk_priority'\n", 1394 | "model_version = 'v1'\n", 1395 | "\n", 1396 | "storage_bucket = 'gs://' + google.datalab.Context.default().project_id + '-datalab-workspace/'\n", 1397 | "storage_region = 'us-central1'" 1398 | ] 1399 | }, 1400 | { 1401 | "cell_type": "code", 1402 | "execution_count": null, 1403 | "metadata": { 1404 | "collapsed": true 1405 | }, 1406 | "outputs": [], 1407 | "source": [ 1408 | "# Check that we have the model files created by the training\n", 1409 | "!ls -R train/model" 1410 | ] 1411 | }, 1412 | { 1413 | "cell_type": "code", 1414 | "execution_count": null, 1415 | "metadata": { 1416 | "collapsed": true 1417 | }, 1418 | "outputs": [], 1419 | "source": [ 1420 | "# Create a model\n", 1421 | "!gcloud ml-engine models create {model_name} --regions {storage_region}" 1422 | ] 1423 | }, 1424 | { 1425 | "cell_type": "code", 1426 | "execution_count": null, 1427 | "metadata": { 1428 | "collapsed": true 1429 | }, 1430 | "outputs": [], 1431 | "source": [ 1432 | "# Create a staging bucket required to write staging files\n", 1433 | "# When creating a model from local files\n", 1434 | "staging_bucket = 'gs://' + google.datalab.Context.default().project_id + '-dtlb-staging-resolution'\n", 1435 | "!gsutil mb -c regional -l {storage_region} {staging_bucket}" 1436 | ] 1437 | }, 1438 | { 1439 | "cell_type": "code", 1440 | "execution_count": null, 1441 | "metadata": { 1442 | "collapsed": true 1443 | }, 1444 | "outputs": [], 1445 | "source": [ 1446 | "# Create our version of the model.\n", 1447 | "!gcloud ml-engine versions create {model_version} --model {model_name} --origin train/model --staging-bucket {staging_bucket}" 1448 | ] 1449 | }, 1450 | { 1451 | "cell_type": "markdown", 1452 | "metadata": {}, 1453 | "source": [ 1454 | "That version that is deployed is the one that you will be able to update automatically in a production environment if you need to update your model daily/weekly/monthly for example" 1455 | ] 1456 | } 1457 | ], 1458 | "metadata": { 1459 | "kernelspec": { 1460 | "display_name": "Python 2", 1461 | "language": "python", 1462 | "name": "python2" 1463 | }, 1464 | "language_info": { 1465 | "codemirror_mode": { 1466 | "name": "ipython", 1467 | "version": 2 1468 | }, 1469 | "file_extension": ".py", 1470 | "mimetype": "text/x-python", 1471 | "name": "python", 1472 | "nbconvert_exporter": "python", 1473 | "pygments_lexer": "ipython2", 1474 | "version": "2.7.12" 1475 | } 1476 | }, 1477 | "nbformat": 4, 1478 | "nbformat_minor": 2 1479 | } 1480 | --------------------------------------------------------------------------------