|
53 | 53 | "cell_type": "code",
|
54 | 54 | "execution_count": null,
|
55 | 55 | "metadata": {
|
| 56 | + "collapsed": true, |
56 | 57 | "isConfigCell": true
|
57 | 58 | },
|
58 | 59 | "outputs": [],
|
|
78 | 79 | {
|
79 | 80 | "cell_type": "code",
|
80 | 81 | "execution_count": null,
|
81 |
| - "metadata": {}, |
| 82 | + "metadata": { |
| 83 | + "collapsed": true |
| 84 | + }, |
82 | 85 | "outputs": [],
|
83 | 86 | "source": [
|
84 | 87 | "import numpy as np # For matrix operations and numerical processing\n",
|
85 | 88 | "import pandas as pd # For munging tabular data\n",
|
86 | 89 | "import matplotlib.pyplot as plt # For charts and visualizations\n",
|
87 | 90 | "from IPython.display import Image # For displaying images in the notebook\n",
|
88 | 91 | "from IPython.display import display # For displaying outputs in the notebook\n",
|
89 |
| - "from sklearn.datasets import dump_svmlight_file # For outputting data to libsvm format for xgboost\n", |
90 | 92 | "from time import gmtime, strftime # For labeling SageMaker models, endpoints, etc.\n",
|
91 | 93 | "import sys # For writing outputs to notebook\n",
|
92 | 94 | "import math # For ceiling function\n",
|
|
298 | 300 | {
|
299 | 301 | "cell_type": "code",
|
300 | 302 | "execution_count": null,
|
301 |
| - "metadata": {}, |
| 303 | + "metadata": { |
| 304 | + "collapsed": true |
| 305 | + }, |
302 | 306 | "outputs": [],
|
303 | 307 | "source": [
|
304 | 308 | "data['no_previous_contact'] = np.where(data['pdays'] == 999, 1, 0) # Indicator variable to capture when pdays takes a value of 999\n",
|
|
320 | 324 | {
|
321 | 325 | "cell_type": "code",
|
322 | 326 | "execution_count": null,
|
323 |
| - "metadata": {}, |
| 327 | + "metadata": { |
| 328 | + "collapsed": true |
| 329 | + }, |
324 | 330 | "outputs": [],
|
325 | 331 | "source": [
|
326 | 332 | "model_data = model_data.drop(['duration', 'emp.var.rate', 'cons.price.idx', 'cons.conf.idx', 'euribor3m', 'nr.employed'], axis=1)"
|
|
338 | 344 | {
|
339 | 345 | "cell_type": "code",
|
340 | 346 | "execution_count": null,
|
341 |
| - "metadata": {}, |
| 347 | + "metadata": { |
| 348 | + "collapsed": true |
| 349 | + }, |
342 | 350 | "outputs": [],
|
343 | 351 | "source": [
|
344 | 352 | "train_data, validation_data, test_data = np.split(model_data.sample(frac=1, random_state=1729), [int(0.7 * len(model_data)), int(0.9 * len(model_data))]) # Randomly sort the data then split out first 70%, second 20%, and last 10%"
|
|
348 | 356 | "cell_type": "markdown",
|
349 | 357 | "metadata": {},
|
350 | 358 | "source": [
|
351 |
| - "Amazon SageMaker's XGBoost container expects data in the libSVM data format. This expects features and the target variable to be provided as separate arguments. Let's split these apart. Notice that although repetitive it's easiest to do this after the train|validation|test split rather than before. This avoids any misalignment issues due to random reordering." |
| 359 | + "Amazon SageMaker's XGBoost container expects data in the libSVM or CSV data format. For this example, we'll stick to CSV. Note that the first column must be the target variable and the CSV should not include headers. Also, notice that although repetitive it's easiest to do this after the train|validation|test split rather than before. This avoids any misalignment issues due to random reordering." |
352 | 360 | ]
|
353 | 361 | },
|
354 | 362 | {
|
355 | 363 | "cell_type": "code",
|
356 | 364 | "execution_count": null,
|
357 |
| - "metadata": {}, |
| 365 | + "metadata": { |
| 366 | + "collapsed": true |
| 367 | + }, |
358 | 368 | "outputs": [],
|
359 | 369 | "source": [
|
360 |
| - "dump_svmlight_file(X=train_data.drop(['y_no', 'y_yes'], axis=1), y=train_data['y_yes'], f='train.libsvm')\n", |
361 |
| - "dump_svmlight_file(X=validation_data.drop(['y_no', 'y_yes'], axis=1), y=validation_data['y_yes'], f='validation.libsvm')\n", |
362 |
| - "dump_svmlight_file(X=test_data.drop(['y_no', 'y_yes'], axis=1), y=test_data['y_yes'], f='test.libsvm')" |
| 370 | + "pd.concat([train_data['y_yes'], train_data.drop(['y_no', 'y_yes'], axis=1)], axis=1).to_csv('train.csv', index=False, header=False)\n", |
| 371 | + "pd.concat([validation_data['y_yes'], validation_data.drop(['y_no', 'y_yes'], axis=1)], axis=1).to_csv('validation.csv', index=False, header=False)" |
363 | 372 | ]
|
364 | 373 | },
|
365 | 374 | {
|
|
372 | 381 | {
|
373 | 382 | "cell_type": "code",
|
374 | 383 | "execution_count": null,
|
375 |
| - "metadata": {}, |
| 384 | + "metadata": { |
| 385 | + "collapsed": true |
| 386 | + }, |
376 | 387 | "outputs": [],
|
377 | 388 | "source": [
|
378 |
| - "boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train/train.libsvm')).upload_file('train.libsvm')\n", |
379 |
| - "boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'validation/validation.libsvm')).upload_file('validation.libsvm')" |
| 389 | + "boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train/train.csv')).upload_file('train.csv')\n", |
| 390 | + "boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'validation/validation.csv')).upload_file('validation.csv')" |
380 | 391 | ]
|
381 | 392 | },
|
382 | 393 | {
|
|
398 | 409 | {
|
399 | 410 | "cell_type": "code",
|
400 | 411 | "execution_count": null,
|
401 |
| - "metadata": {}, |
| 412 | + "metadata": { |
| 413 | + "collapsed": true |
| 414 | + }, |
402 | 415 | "outputs": [],
|
403 | 416 | "source": [
|
404 | 417 | "containers = {'us-west-2': '433757028032.dkr.ecr.us-west-2.amazonaws.com/xgboost:latest',\n",
|
|
411 | 424 | "cell_type": "markdown",
|
412 | 425 | "metadata": {},
|
413 | 426 | "source": [
|
414 |
| - "Then, because we're training with the libSVM file format, we'll create `s3_input`s that our training function can use as a pointer to the files in S3, which also specify that the content type is libSVM." |
| 427 | + "Then, because we're training with the CSV file format, we'll create `s3_input`s that our training function can use as a pointer to the files in S3, which also specify that the content type is CSV." |
415 | 428 | ]
|
416 | 429 | },
|
417 | 430 | {
|
418 | 431 | "cell_type": "code",
|
419 | 432 | "execution_count": null,
|
420 |
| - "metadata": {}, |
| 433 | + "metadata": { |
| 434 | + "collapsed": true |
| 435 | + }, |
421 | 436 | "outputs": [],
|
422 | 437 | "source": [
|
423 |
| - "s3_input_train = sagemaker.s3_input(s3_data='s3://{}/{}/train'.format(bucket, prefix), content_type='libsvm')\n", |
424 |
| - "s3_input_validation = sagemaker.s3_input(s3_data='s3://{}/{}/validation/'.format(bucket, prefix), content_type='libsvm')" |
| 438 | + "s3_input_train = sagemaker.s3_input(s3_data='s3://{}/{}/train'.format(bucket, prefix), content_type='csv')\n", |
| 439 | + "s3_input_validation = sagemaker.s3_input(s3_data='s3://{}/{}/validation/'.format(bucket, prefix), content_type='csv')" |
425 | 440 | ]
|
426 | 441 | },
|
427 | 442 | {
|
|
478 | 493 | {
|
479 | 494 | "cell_type": "code",
|
480 | 495 | "execution_count": null,
|
481 |
| - "metadata": {}, |
| 496 | + "metadata": { |
| 497 | + "collapsed": true |
| 498 | + }, |
482 | 499 | "outputs": [],
|
483 | 500 | "source": [
|
484 | 501 | "xgb_predictor = xgb.deploy(initial_instance_count=1,\n",
|
|
494 | 511 | "## Evaluation\n",
|
495 | 512 | "There are many ways to compare the performance of a machine learning model, but let's start by simply comparing actual to predicted values. In this case, we're simply predicting whether the customer subscribed to a term deposit (`1`) or not (`0`), which produces a simple confusion matrix.\n",
|
496 | 513 | "\n",
|
497 |
| - "First we'll need to determine how we pass data into and receive data from our endpoint. Our data is currently stored as NumPy arrays in memory of our notebook instance. To send it in an HTTP POST request, we'll serialize it as a CSV string and then decode the resulting CSV." |
| 514 | + "First we'll need to determine how we pass data into and receive data from our endpoint. Our data is currently stored as NumPy arrays in memory of our notebook instance. To send it in an HTTP POST request, we'll serialize it as a CSV string and then decode the resulting CSV.\n", |
| 515 | + "\n", |
| 516 | + "*Note: For inference with CSV format, SageMaker XGBoost requires that the data does NOT include the target variable.*" |
498 | 517 | ]
|
499 | 518 | },
|
500 | 519 | {
|
501 | 520 | "cell_type": "code",
|
502 | 521 | "execution_count": null,
|
503 |
| - "metadata": {}, |
| 522 | + "metadata": { |
| 523 | + "collapsed": true |
| 524 | + }, |
504 | 525 | "outputs": [],
|
505 | 526 | "source": [
|
506 | 527 | "xgb_predictor.content_type = 'text/csv'\n",
|
|
514 | 535 | "Now, we'll use a simple function to:\n",
|
515 | 536 | "1. Loop over our test dataset\n",
|
516 | 537 | "1. Split it into mini-batches of rows \n",
|
517 |
| - "1. Convert those mini-batchs to CSV string payloads\n", |
| 538 | + "1. Convert those mini-batches to CSV string payloads (notice, we drop the target variable from our dataset first)\n", |
518 | 539 | "1. Retrieve mini-batch predictions by invoking the XGBoost endpoint\n",
|
519 | 540 | "1. Collect predictions and convert from the CSV output our model provides into a NumPy array"
|
520 | 541 | ]
|
521 | 542 | },
|
522 | 543 | {
|
523 | 544 | "cell_type": "code",
|
524 | 545 | "execution_count": null,
|
525 |
| - "metadata": {}, |
| 546 | + "metadata": { |
| 547 | + "collapsed": true |
| 548 | + }, |
526 | 549 | "outputs": [],
|
527 | 550 | "source": [
|
528 | 551 | "def predict(data, rows=500):\n",
|
|
546 | 569 | {
|
547 | 570 | "cell_type": "code",
|
548 | 571 | "execution_count": null,
|
549 |
| - "metadata": {}, |
| 572 | + "metadata": { |
| 573 | + "collapsed": true |
| 574 | + }, |
550 | 575 | "outputs": [],
|
551 | 576 | "source": [
|
552 | 577 | "pd.crosstab(index=test_data['y_yes'], columns=np.round(predictions), rownames=['actuals'], colnames=['predictions'])"
|
|
556 | 581 | "cell_type": "markdown",
|
557 | 582 | "metadata": {},
|
558 | 583 | "source": [
|
559 |
| - "So, of the ~3700 potential customers we predicted would subscribe, 428 of them actually did. We also had 55 subscribers who subscribed that we did not predict would. This is less than desirable, but the model can (and should) be tuned to improve this. Most importantly, note that with minimal effort, our model produced accuracies similar to those published [here](http://media.salford-systems.com/video/tutorial/2015/targeted_marketing.pdf).\n", |
| 584 | + "So, of the ~4000 potential customers, we predicted 136 would subscribe and 94 of them actually did. We also had 389 subscribers who subscribed that we did not predict would. This is less than desirable, but the model can (and should) be tuned to improve this. Most importantly, note that with minimal effort, our model produced accuracies similar to those published [here](http://media.salford-systems.com/video/tutorial/2015/targeted_marketing.pdf).\n", |
560 | 585 | "\n",
|
561 | 586 | "_Note that because there is some element of randomness in the algorithm's subsample, your results may differ slightly from the text written above._"
|
562 | 587 | ]
|
|
569 | 594 | "\n",
|
570 | 595 | "## Extensions\n",
|
571 | 596 | "\n",
|
572 |
| - "This example analyzed a relatively small dataset, but utilized Amazon SageMaker features such as distributed, managed training and real-time model hosting, which could easily be applied to much larger problems. In order to improve predictive accuracy further, we could explore techniques like hyperparameter tuning, as well as spend more time engineering features by hand. In a real-worl scenario we may also look for additional datasets to include which contain customer information not available in our initial dataset." |
| 597 | + "This example analyzed a relatively small dataset, but utilized Amazon SageMaker features such as distributed, managed training and real-time model hosting, which could easily be applied to much larger problems. In order to improve predictive accuracy further, we could tweak value we threshold our predictions at to alter the mix of false-positives and false-negatives, or we could explore techniques like hyperparameter tuning. In a real-world scenario, we would also spend more time engineering features by hand and would likely look for additional datasets to include which contain customer information not available in our initial dataset." |
573 | 598 | ]
|
574 | 599 | },
|
575 | 600 | {
|
|
584 | 609 | {
|
585 | 610 | "cell_type": "code",
|
586 | 611 | "execution_count": null,
|
587 |
| - "metadata": {}, |
| 612 | + "metadata": { |
| 613 | + "collapsed": true |
| 614 | + }, |
588 | 615 | "outputs": [],
|
589 | 616 | "source": [
|
590 | 617 | "sagemaker.Session().delete_endpoint(xgb_predictor.endpoint)"
|
|
593 | 620 | ],
|
594 | 621 | "metadata": {
|
595 | 622 | "kernelspec": {
|
596 |
| - "display_name": "Environment (conda_python3)", |
| 623 | + "display_name": "conda_python3", |
597 | 624 | "language": "python",
|
598 | 625 | "name": "conda_python3"
|
599 | 626 | },
|
|
607 | 634 | "name": "python",
|
608 | 635 | "nbconvert_exporter": "python",
|
609 | 636 | "pygments_lexer": "ipython3",
|
610 |
| - "version": "3.6.3" |
| 637 | + "version": "3.6.2" |
611 | 638 | },
|
612 | 639 | "notice": "Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the \"License\"). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ or in the \"license\" file accompanying this file. This file is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License."
|
613 | 640 | },
|
|
0 commit comments