|
11 | 11 | "cell_type": "markdown", |
12 | 12 | "metadata": {}, |
13 | 13 | "source": [ |
14 | | - "In this example we develop a small fraud detection model for credit card transactions based on XGBoost, export it to TorchScript using Hummingbird (https://github.com/microsoft/hummingbird) and run Shapley Value Sampling explanations (see https://captum.ai/api/shapley_value_sampling.html for reference) on it, also exported to TorchScript.\n", |
| 14 | + "In this example we develop a small fraud detection model for credit card transactions based on XGBoost, export it to TorchScript using Hummingbird (https://github.com/microsoft/hummingbird) and run Shapley Value Sampling explanations (see https://captum.ai/api/shapley_value_sampling.html for reference) on it, via torch script.\n", |
15 | 15 | "\n", |
16 | | - "We load both the original model and the explainability model in RedisAI and trigger them in a DAG." |
| 16 | + "We load both the original model and the explainability script in RedisAI and trigger them in a DAG." |
17 | 17 | ] |
18 | 18 | }, |
19 | 19 | { |
|
39 | 39 | }, |
40 | 40 | { |
41 | 41 | "cell_type": "code", |
42 | | - "execution_count": 45, |
| 42 | + "execution_count": 1, |
43 | 43 | "metadata": {}, |
44 | 44 | "outputs": [], |
45 | 45 | "source": [ |
|
102 | 102 | "name": "stderr", |
103 | 103 | "output_type": "stream", |
104 | 104 | "text": [ |
105 | | - "/home/dvirdukhan/.local/lib/python3.8/site-packages/xgboost/sklearn.py:1146: UserWarning: The use of label encoder in XGBClassifier is deprecated and will be removed in a future release. To remove this warning, do the following: 1) Pass option use_label_encoder=False when constructing XGBClassifier object; and 2) Encode your labels (y) as integers starting with 0, i.e. 0, 1, 2, ..., [num_class - 1].\n", |
| 105 | + "/home/dvirdukhan/Code/redisai-examples/venv/lib/python3.8/site-packages/xgboost/sklearn.py:1146: UserWarning: The use of label encoder in XGBClassifier is deprecated and will be removed in a future release. To remove this warning, do the following: 1) Pass option use_label_encoder=False when constructing XGBClassifier object; and 2) Encode your labels (y) as integers starting with 0, i.e. 0, 1, 2, ..., [num_class - 1].\n", |
106 | 106 | " warnings.warn(label_encoder_deprecation_msg, UserWarning)\n" |
107 | 107 | ] |
108 | 108 | }, |
109 | 109 | { |
110 | 110 | "name": "stdout", |
111 | 111 | "output_type": "stream", |
112 | 112 | "text": [ |
113 | | - "[10:32:38] WARNING: ../src/learner.cc:573: \n", |
| 113 | + "[05:49:05] WARNING: ../src/learner.cc:573: \n", |
114 | 114 | "Parameters: { \"label_encoder\" } might not be used.\n", |
115 | 115 | "\n", |
116 | 116 | " This may not be accurate due to some parameters are only used in language bindings but\n", |
117 | 117 | " passed down to XGBoost core. Or some parameters are not used but slip through this\n", |
118 | 118 | " verification. Please open an issue if you find above cases.\n", |
119 | 119 | "\n", |
120 | 120 | "\n", |
121 | | - "[10:32:38] WARNING: ../src/learner.cc:1095: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.\n" |
| 121 | + "[05:49:05] WARNING: ../src/learner.cc:1095: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.\n" |
122 | 122 | ] |
123 | 123 | }, |
124 | 124 | { |
|
223 | 223 | "cell_type": "markdown", |
224 | 224 | "metadata": {}, |
225 | 225 | "source": [ |
226 | | - "We are interested to explore are casesof fraud, so we extract them from the test set." |
| 226 | + "We are interested to explore are cases of fraud, so we extract them from the test set." |
227 | 227 | ] |
228 | 228 | }, |
229 | 229 | { |
|
302 | 302 | }, |
303 | 303 | { |
304 | 304 | "cell_type": "code", |
305 | | - "execution_count": 12, |
| 305 | + "execution_count": 10, |
306 | 306 | "metadata": {}, |
307 | 307 | "outputs": [], |
308 | 308 | "source": [ |
|
311 | 311 | }, |
312 | 312 | { |
313 | 313 | "cell_type": "code", |
314 | | - "execution_count": 13, |
| 314 | + "execution_count": 11, |
315 | 315 | "metadata": {}, |
316 | 316 | "outputs": [], |
317 | 317 | "source": [ |
|
331 | 331 | }, |
332 | 332 | { |
333 | 333 | "cell_type": "code", |
334 | | - "execution_count": 14, |
| 334 | + "execution_count": 12, |
335 | 335 | "metadata": {}, |
336 | 336 | "outputs": [], |
337 | 337 | "source": [ |
|
349 | 349 | }, |
350 | 350 | { |
351 | 351 | "cell_type": "code", |
352 | | - "execution_count": 15, |
| 352 | + "execution_count": 13, |
353 | 353 | "metadata": {}, |
354 | 354 | "outputs": [], |
355 | 355 | "source": [ |
|
369 | 369 | }, |
370 | 370 | { |
371 | 371 | "cell_type": "code", |
372 | | - "execution_count": 16, |
| 372 | + "execution_count": 14, |
373 | 373 | "metadata": {}, |
374 | 374 | "outputs": [ |
375 | 375 | { |
|
378 | 378 | "True" |
379 | 379 | ] |
380 | 380 | }, |
381 | | - "execution_count": 16, |
| 381 | + "execution_count": 14, |
382 | 382 | "metadata": {}, |
383 | 383 | "output_type": "execute_result" |
384 | 384 | } |
|
389 | 389 | "torch.equal(loaded_output_classes, xgboost_output_classes)" |
390 | 390 | ] |
391 | 391 | }, |
| 392 | + { |
| 393 | + "cell_type": "markdown", |
| 394 | + "metadata": {}, |
| 395 | + "source": [ |
| 396 | + "## Explainer Script" |
| 397 | + ] |
| 398 | + }, |
| 399 | + { |
| 400 | + "cell_type": "markdown", |
| 401 | + "metadata": {}, |
| 402 | + "source": [ |
| 403 | + "The script `torch_shapely.py` is a torch script degined specificly running on RedisAI, and utilizes RedisAI extension for torch script, that allows to run any model stored in RedisAI from within the script. Let's go over the details:\n", |
| 404 | + "\n", |
| 405 | + "In RedisAI, each entry point (function in script) should have the signature:\n", |
| 406 | + "`function_name(tensors: List[Tensor], keys: List[str], args: List[str]):`\n", |
| 407 | + "In our case our entry point is `shapely_sample(tensors: List[Tensor], keys: List[str], args: List[str]):` and the parameters are:\n", |
| 408 | + "```\n", |
| 409 | + "Tensors:\n", |
| 410 | + " tensors[0] - x : Input tensor to the model\n", |
| 411 | + " tensors[1] - baselines : Optional - reference values which replace each feature when\n", |
| 412 | + " ablated; if no baselines are provided, baselines are set\n", |
| 413 | + " to all zeros\n", |
| 414 | + "\n", |
| 415 | + "Keys:\n", |
| 416 | + " keys[0] - model_key: Redis key name where the model is stored as RedisAI model.\n", |
| 417 | + " \n", |
| 418 | + "Args:\n", |
| 419 | + " args[0] - n_samples: number of random feature permutations performed\n", |
| 420 | + " args[1] - number_of_outputs - number of model outputs\n", |
| 421 | + " args[2] - output_tensor_index - index of the tested output tensor\n", |
| 422 | + " args[3] - Optional - target: output indices for which Shapley Value Sampling is\n", |
| 423 | + " computed; if model returns a single scalar, target can be\n", |
| 424 | + " None\n", |
| 425 | + "```\n", |
| 426 | + "\n", |
| 427 | + "The script will create `n_samples` amount of permutations of the input features. For each permutation it will check for each feature what was its contribution to the result by running the model repeatedly on a new subset of input features.\n" |
| 428 | + ] |
| 429 | + }, |
392 | 430 | { |
393 | 431 | "cell_type": "markdown", |
394 | 432 | "metadata": {}, |
|
400 | 438 | "cell_type": "markdown", |
401 | 439 | "metadata": {}, |
402 | 440 | "source": [ |
403 | | - "At this point we can load the models we exported into RedisAI and serve them from there. After making sure RedisAI is running, we initialize the client." |
| 441 | + "At this point we can load the model we exported into RedisAI and serve it from there. We will also load the `torch_shapely.py` script, that allows calculating the Shapely value of a model, from within RedisAI. After making sure RedisAI is running, we initialize the client." |
404 | 442 | ] |
405 | 443 | }, |
406 | 444 | { |
407 | 445 | "cell_type": "code", |
408 | | - "execution_count": 34, |
| 446 | + "execution_count": 15, |
409 | 447 | "metadata": {}, |
410 | 448 | "outputs": [], |
411 | 449 | "source": [ |
|
418 | 456 | "cell_type": "markdown", |
419 | 457 | "metadata": {}, |
420 | 458 | "source": [ |
421 | | - "We read the model and the explainer from the saved TorchScript." |
| 459 | + "We read the model and the script." |
422 | 460 | ] |
423 | 461 | }, |
424 | 462 | { |
425 | 463 | "cell_type": "code", |
426 | | - "execution_count": 64, |
| 464 | + "execution_count": 16, |
427 | 465 | "metadata": {}, |
428 | 466 | "outputs": [], |
429 | 467 | "source": [ |
|
438 | 476 | "cell_type": "markdown", |
439 | 477 | "metadata": {}, |
440 | 478 | "source": [ |
441 | | - "We load both models into RedisAI." |
| 479 | + "We load both movel and script into RedisAI." |
442 | 480 | ] |
443 | 481 | }, |
444 | 482 | { |
445 | 483 | "cell_type": "code", |
446 | | - "execution_count": 65, |
| 484 | + "execution_count": 17, |
447 | 485 | "metadata": {}, |
448 | 486 | "outputs": [ |
449 | 487 | { |
|
452 | 490 | "'OK'" |
453 | 491 | ] |
454 | 492 | }, |
455 | | - "execution_count": 65, |
| 493 | + "execution_count": 17, |
456 | 494 | "metadata": {}, |
457 | 495 | "output_type": "execute_result" |
458 | 496 | } |
|
466 | 504 | "cell_type": "markdown", |
467 | 505 | "metadata": {}, |
468 | 506 | "source": [ |
469 | | - "All, set, it's now test time. We reuse our `X_test_fraud` NumPy array we created previously. We set it, run both models, and get predictions and explanations as arrays." |
| 507 | + "All set, it's now test time. We reuse our `X_test_fraud` NumPy array we created previously. We set it, and run the Shapley script and get explanations as arrays." |
470 | 508 | ] |
471 | 509 | }, |
472 | 510 | { |
473 | 511 | "cell_type": "code", |
474 | | - "execution_count": 66, |
475 | | - "metadata": {}, |
476 | | - "outputs": [], |
477 | | - "source": [ |
478 | | - "rai.tensorset(\"fraud_input\", X_test_fraud, dtype=\"float\")\n", |
479 | | - "\n", |
480 | | - "rai.scriptexecute(\"shapely_script\", \"shapely_sample\", inputs = [\"fraud_input\"], keys = [\"fraud_detection_model\"], args = [\"20\", \"2\", \"0\"], outputs=[\"fraud_explanations\"])\n", |
481 | | - "\n", |
482 | | - "rai_expl = rai.tensorget(\"fraud_explanations\")" |
483 | | - ] |
484 | | - }, |
485 | | - { |
486 | | - "cell_type": "markdown", |
487 | | - "metadata": {}, |
488 | | - "source": [ |
489 | | - "We check whether the winning feature is consistent to what we found earlier." |
490 | | - ] |
491 | | - }, |
492 | | - { |
493 | | - "cell_type": "code", |
494 | | - "execution_count": 67, |
| 512 | + "execution_count": 18, |
495 | 513 | "metadata": {}, |
496 | 514 | "outputs": [ |
497 | 515 | { |
|
503 | 521 | } |
504 | 522 | ], |
505 | 523 | "source": [ |
| 524 | + "rai.tensorset(\"fraud_input\", X_test_fraud, dtype=\"float\")\n", |
| 525 | + "\n", |
| 526 | + "rai.scriptexecute(\"shapely_script\", \"shapely_sample\", inputs = [\"fraud_input\"], keys = [\"fraud_detection_model\"], args = [\"20\", \"2\", \"0\"], outputs=[\"fraud_explanations\"])\n", |
| 527 | + "\n", |
| 528 | + "rai_expl = rai.tensorget(\"fraud_explanations\")\n", |
| 529 | + "\n", |
506 | 530 | "winning_feature_redisai = np.argmax(rai_expl[0], axis=0)\n", |
507 | 531 | "\n", |
508 | | - "print(\"Winning feature: %d\" % winning_feature_redisai)\n", |
509 | | - "\n" |
510 | | - ] |
511 | | - }, |
512 | | - { |
513 | | - "cell_type": "code", |
514 | | - "execution_count": 71, |
515 | | - "metadata": {}, |
516 | | - "outputs": [ |
517 | | - { |
518 | | - "data": { |
519 | | - "text/plain": [ |
520 | | - "array([ 0. , -0.05, 0. , 0.05, 0.2 , 0. , 0.1 , 0. , 0. ,\n", |
521 | | - " -0.05, 0.15, 0. , 0.1 , 0. , 0.5 , 0. , 0.05, -0.1 ,\n", |
522 | | - " 0. , 0. , 0.05, 0. , 0. , 0. , 0. , 0. , 0. ,\n", |
523 | | - " 0. , 0. , 0. ])" |
524 | | - ] |
525 | | - }, |
526 | | - "execution_count": 71, |
527 | | - "metadata": {}, |
528 | | - "output_type": "execute_result" |
529 | | - } |
530 | | - ], |
531 | | - "source": [ |
532 | | - "rai_expl[0]" |
| 532 | + "print(\"Winning feature: %d\" % winning_feature_redisai)" |
533 | 533 | ] |
534 | 534 | }, |
535 | 535 | { |
|
541 | 541 | }, |
542 | 542 | { |
543 | 543 | "cell_type": "code", |
544 | | - "execution_count": 74, |
| 544 | + "execution_count": 19, |
545 | 545 | "metadata": {}, |
546 | 546 | "outputs": [ |
547 | 547 | { |
548 | 548 | "data": { |
549 | 549 | "text/plain": [ |
550 | | - "<redisai.dag.Dag at 0x7fb118524d30>" |
| 550 | + "<redisai.dag.Dag at 0x7f8a941a52e0>" |
551 | 551 | ] |
552 | 552 | }, |
553 | | - "execution_count": 74, |
| 553 | + "execution_count": 19, |
554 | 554 | "metadata": {}, |
555 | 555 | "output_type": "execute_result" |
556 | 556 | } |
|
573 | 573 | }, |
574 | 574 | { |
575 | 575 | "cell_type": "code", |
576 | | - "execution_count": 75, |
| 576 | + "execution_count": 20, |
577 | 577 | "metadata": {}, |
578 | 578 | "outputs": [], |
579 | 579 | "source": [ |
|
584 | 584 | }, |
585 | 585 | { |
586 | 586 | "cell_type": "code", |
587 | | - "execution_count": 76, |
| 587 | + "execution_count": 21, |
588 | 588 | "metadata": {}, |
589 | 589 | "outputs": [ |
590 | 590 | { |
|
600 | 600 | " 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1])" |
601 | 601 | ] |
602 | 602 | }, |
603 | | - "execution_count": 76, |
| 603 | + "execution_count": 21, |
604 | 604 | "metadata": {}, |
605 | 605 | "output_type": "execute_result" |
606 | 606 | } |
|
618 | 618 | }, |
619 | 619 | { |
620 | 620 | "cell_type": "code", |
621 | | - "execution_count": 77, |
| 621 | + "execution_count": 22, |
622 | 622 | "metadata": {}, |
623 | 623 | "outputs": [ |
624 | 624 | { |
|
638 | 638 | ], |
639 | 639 | "metadata": { |
640 | 640 | "kernelspec": { |
641 | | - "display_name": "Python 3", |
| 641 | + "display_name": "Python 3 (ipykernel)", |
642 | 642 | "language": "python", |
643 | 643 | "name": "python3" |
644 | 644 | }, |
|
652 | 652 | "name": "python", |
653 | 653 | "nbconvert_exporter": "python", |
654 | 654 | "pygments_lexer": "ipython3", |
655 | | - "version": "3.8.5" |
| 655 | + "version": "3.8.10" |
656 | 656 | } |
657 | 657 | }, |
658 | 658 | "nbformat": 4, |
|
0 commit comments