From 01d8cbdd0756bc11254f360d918b9dbd583d1d24 Mon Sep 17 00:00:00 2001 From: Mehrin Kiani Date: Fri, 26 Jan 2024 14:46:55 -0500 Subject: [PATCH] Update notebooks with JSON Reporting (#92) --- README.md | 3 + notebooks/keras_fashion_mnist.ipynb | 110 ++++++++++---- notebooks/pytorch_sentiment_analysis.ipynb | 100 ++++++++----- notebooks/tensorflow_fashion_mnist.ipynb | 134 +++++++++++++----- .../xgboost_diabetes_classification.ipynb | 77 ++++++++-- 5 files changed, 322 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 824955b..57c856c 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,9 @@ ModelScan supports the following arguments via the CLI: | ```modelscan -p /path/to/model_file``` | -p or --path | Scan a locally stored model | | ```modelscan -p /path/to/model_file --settings-file ./modelscan-settings.toml``` | --settings-file | Scan a locally stored model using custom configurations | | ```modelscan create-settings-file``` | -l or --location | Create a configurable settings file | +| ```modelscan -r``` | -r or --reporting-format | Format of the output. Options are console, json, or custom (to be defined in settings-file). Default is console | +| ```modelscan -r reporting-format -o file-name``` | -o or --output-file | Optional file name for output report | +| ```modelscan --show-skipped``` | --show-skipped | Print a list of files that were skipped during the scan | Remember models are just like any other form of digital media, you should scan content from any untrusted source before use. diff --git a/notebooks/keras_fashion_mnist.ipynb b/notebooks/keras_fashion_mnist.ipynb index af136a7..a1e2b36 100644 --- a/notebooks/keras_fashion_mnist.ipynb +++ b/notebooks/keras_fashion_mnist.ipynb @@ -11,9 +11,18 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "modelscan, version 0.5.0\n" + ] + } + ], "source": [ - "!pip install -q modelscan" + "!pip install -q modelscan\n", + "!modelscan -v" ] }, { @@ -58,35 +67,35 @@ "output_type": "stream", "text": [ "Epoch 1/10\n", - "1875/1875 [==============================] - 2s 773us/step - loss: 0.5034 - accuracy: 0.8228\n", + "1875/1875 [==============================] - 2s 1ms/step - loss: 0.4986 - accuracy: 0.8258\n", "Epoch 2/10\n", - "1875/1875 [==============================] - 1s 738us/step - loss: 0.3762 - accuracy: 0.8640\n", + "1875/1875 [==============================] - 2s 800us/step - loss: 0.3741 - accuracy: 0.8649\n", "Epoch 3/10\n", - "1875/1875 [==============================] - 1s 760us/step - loss: 0.3358 - accuracy: 0.8769\n", + "1875/1875 [==============================] - 1s 772us/step - loss: 0.3391 - accuracy: 0.8766\n", "Epoch 4/10\n", - "1875/1875 [==============================] - 1s 733us/step - loss: 0.3114 - accuracy: 0.8854\n", + "1875/1875 [==============================] - 1s 755us/step - loss: 0.3125 - accuracy: 0.8852\n", "Epoch 5/10\n", - "1875/1875 [==============================] - 1s 743us/step - loss: 0.2949 - accuracy: 0.8909\n", + "1875/1875 [==============================] - 2s 965us/step - loss: 0.2955 - accuracy: 0.8908\n", "Epoch 6/10\n", - "1875/1875 [==============================] - 1s 731us/step - loss: 0.2816 - accuracy: 0.8962\n", + "1875/1875 [==============================] - 2s 905us/step - loss: 0.2808 - accuracy: 0.8963\n", "Epoch 7/10\n", - "1875/1875 [==============================] - 1s 743us/step - loss: 0.2687 - accuracy: 0.9000\n", + "1875/1875 [==============================] - 2s 1ms/step - loss: 0.2696 - accuracy: 0.9004\n", "Epoch 8/10\n", - "1875/1875 [==============================] - 1s 762us/step - loss: 0.2567 - accuracy: 0.9044\n", + "1875/1875 [==============================] - 2s 895us/step - loss: 0.2587 - accuracy: 0.9039\n", "Epoch 9/10\n", - "1875/1875 [==============================] - 1s 766us/step - loss: 0.2464 - accuracy: 0.9075\n", + "1875/1875 [==============================] - 2s 925us/step - loss: 0.2479 - accuracy: 0.9068\n", "Epoch 10/10\n", - "1875/1875 [==============================] - 1s 737us/step - loss: 0.2372 - accuracy: 0.9113\n", - "313/313 [==============================] - 0s 490us/step - loss: 0.3440 - accuracy: 0.8827\n", + "1875/1875 [==============================] - 2s 893us/step - loss: 0.2406 - accuracy: 0.9112\n", + "313/313 [==============================] - 0s 555us/step - loss: 0.3496 - accuracy: 0.8790\n", "\n", - "Model trained with test accuracy: 0.8827000260353088\n" + "Model trained with test accuracy: 0.8790000081062317\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "/Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages/keras/src/engine/training.py:3000: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`.\n", + "/Users/mehrinkiani/mambaforge/envs/py310/lib/python3.10/site-packages/keras/src/engine/training.py:3000: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`.\n", " saving_api.save_model(\n" ] } @@ -117,9 +126,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "1/1 [==============================] - 0s 35ms/step\n", + "1/1 [==============================] - 0s 42ms/step\n", "\n", - "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 98.059 99.993996 100. ]\n", + "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 98.948 99.926 100. ]\n", "\n", "The true labels are ['Ankle boot', 'Pullover', 'Trouser']\n" ] @@ -159,7 +168,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/safe_model.h5 using hdf5 model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/safe_model.h5 using modelscan.scanners.H5LambdaDetectScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", @@ -244,9 +255,9 @@ "aws_secret_access_key=\n", "aws_access_key_id=\n", "aws_secret_access_key=\n", - "1/1 [==============================] - 0s 29ms/step\n", + "1/1 [==============================] - 0s 35ms/step\n", "\n", - "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 98.059 99.993996 100. ]\n", + "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 98.948 99.926 100. ]\n", "\n", "The true labels are ['Ankle boot', 'Pullover', 'Trouser']\n" ] @@ -289,7 +300,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/unsafe_model.h5 using hdf5 model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/unsafe_model.h5 using modelscan.scanners.H5LambdaDetectScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", @@ -317,6 +330,55 @@ "!modelscan -p KerasModels/unsafe_model.h5" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reporting Format\n", + "ModelScan can report scan results in console (default), JSON, or custom report (to be defined by user in settings-file). For mode details, please see: ` modelscan -h` " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## JSON Report\n", + "\n", + "For JSON reporting: `modelscan -p ./path-to/file -r json -o output-file-name.json` \n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/unsafe_model.h5 using modelscan.scanners.H5LambdaDetectScan model scan\n", + "\u001b[1m{\u001b[0m\u001b[32m\"modelscan_version\"\u001b[0m: \u001b[32m\"0.5.0\"\u001b[0m, \u001b[32m\"timestamp\"\u001b[0m: \u001b[32m\"2024-01-25T17:56:36.776966\"\u001b[0m, \n", + "\u001b[32m\"input_path\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/unsafe_model.h5\"\u001b[0m, \n", + "\u001b[32m\"total_issues\"\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m\"summary\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"total_issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"LOW\"\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m\"MEDIUM\"\u001b[0m: \n", + "\u001b[1;36m1\u001b[0m, \u001b[32m\"HIGH\"\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m\"CRITICAL\"\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m\"issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"MEDIUM\"\u001b[0m: \u001b[1m[\u001b[0m\u001b[1m{\u001b[0m\u001b[32m\"description\"\u001b[0m:\n", + "\u001b[32m\"Use of unsafe operator 'Lambda' from module 'Keras'\"\u001b[0m, \u001b[32m\"operator\"\u001b[0m: \u001b[32m\"Lambda\"\u001b[0m, \n", + "\u001b[32m\"module\"\u001b[0m: \u001b[32m\"Keras\"\u001b[0m, \u001b[32m\"source\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/unsafe_model.h5\"\u001b[0m, \n", + "\u001b[32m\"scanner\"\u001b[0m: \u001b[32m\"modelscan.scanners.H5LambdaDetectScan\"\u001b[0m\u001b[1m}\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m\"errors\"\u001b[0m: \u001b[1m[\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m\"scanned\"\u001b[0m: \n", + "\u001b[1m{\u001b[0m\u001b[32m\"total_scanned\"\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m\"scanned_files\"\u001b[0m: \n", + "\u001b[1m[\u001b[0m\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/KerasModels/unsafe_model.h5\"\u001b[0m\u001b[1m]\u001b[0m\n", + "\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m\n" + ] + } + ], + "source": [ + "# This will save the scan results in file: keras-model-scan-results.json\n", + "!modelscan --path KerasModels/unsafe_model.h5 -r json -o keras-model-scan-results.json" + ] + }, { "cell_type": "code", "execution_count": null, @@ -327,7 +389,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.9.16 ('notebooks-xgb-2')", + "display_name": "Python 3.10.13 ('py310')", "language": "python", "name": "python3" }, @@ -341,12 +403,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.10.13" }, "orig_nbformat": 4, "vscode": { "interpreter": { - "hash": "8792b9cba3843e3778a70fb92fd4a6e11f0e29f4d97b60d08b903ca0b490cce6" + "hash": "bd638e2064d9001d4ca93bc8e56e039dad230900dd235e8a6196f1614960903a" } } }, diff --git a/notebooks/pytorch_sentiment_analysis.ipynb b/notebooks/pytorch_sentiment_analysis.ipynb index b9401a2..282439b 100644 --- a/notebooks/pytorch_sentiment_analysis.ipynb +++ b/notebooks/pytorch_sentiment_analysis.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Installing modelscan" + "## Installing ModelScan" ] }, { @@ -23,34 +23,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Collecting modelscan\n", - " Obtaining dependency information for modelscan from https://files.pythonhosted.org/packages/bf/6e/dda8f775b53c307bd59042a7d20143b3f0d068e9b9c6776cf07a28ce208d/modelscan-0.1.1-py3-none-any.whl.metadata\n", - " Using cached modelscan-0.1.1-py3-none-any.whl.metadata (5.1 kB)\n", - "Collecting click<9.0.0,>=8.1.3 (from modelscan)\n", - " Obtaining dependency information for click<9.0.0,>=8.1.3 from https://files.pythonhosted.org/packages/1a/70/e63223f8116931d365993d4a6b7ef653a4d920b41d03de7c59499962821f/click-8.1.6-py3-none-any.whl.metadata\n", - " Using cached click-8.1.6-py3-none-any.whl.metadata (3.0 kB)\n", - "Collecting numpy==1.24.0 (from modelscan)\n", - " Using cached numpy-1.24.0-cp39-cp39-macosx_11_0_arm64.whl (13.9 MB)\n", - "Collecting rich<14.0.0,>=13.4.2 (from modelscan)\n", - " Obtaining dependency information for rich<14.0.0,>=13.4.2 from https://files.pythonhosted.org/packages/8d/5f/21a93b2ec205f4b79853ff6e838e3c99064d5dbe85ec6b05967506f14af0/rich-13.5.2-py3-none-any.whl.metadata\n", - " Using cached rich-13.5.2-py3-none-any.whl.metadata (18 kB)\n", - "Collecting markdown-it-py>=2.2.0 (from rich<14.0.0,>=13.4.2->modelscan)\n", - " Obtaining dependency information for markdown-it-py>=2.2.0 from https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl.metadata\n", - " Using cached markdown_it_py-3.0.0-py3-none-any.whl.metadata (6.9 kB)\n", - "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /Users/mehrinkiani/mambaforge/envs/notebooks-pt/lib/python3.9/site-packages (from rich<14.0.0,>=13.4.2->modelscan) (2.15.1)\n", - "Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich<14.0.0,>=13.4.2->modelscan)\n", - " Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)\n", - "Using cached modelscan-0.1.1-py3-none-any.whl (22 kB)\n", - "Using cached click-8.1.6-py3-none-any.whl (97 kB)\n", - "Using cached rich-13.5.2-py3-none-any.whl (239 kB)\n", - "Using cached markdown_it_py-3.0.0-py3-none-any.whl (87 kB)\n", - "Installing collected packages: numpy, mdurl, click, markdown-it-py, rich, modelscan\n", - "Successfully installed click-8.1.6 markdown-it-py-3.0.0 mdurl-0.1.2 modelscan-0.1.1 numpy-1.24.0 rich-13.5.2\n" + "modelscan, version 0.5.0\n" ] } ], "source": [ - "!pip install modelscan" + "!pip install -q modelscan\n", + "!modelscan -v" ] }, { @@ -73,7 +52,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/mehrinkiani/mambaforge/envs/notebooks-pt/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + "/Users/mehrinkiani/mambaforge/envs/py310/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] }, @@ -147,7 +126,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Scan the Safe Model\n", + "# Scan Safe Model\n", "\n", "The scan results include information on the files scanned, and any issues if found. For the safe model scanned, modelscan finds no model serialization attacks, as expected." ] @@ -161,7 +140,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/safe_model.pt:safe_model/data.pkl using pickle model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/safe_model.pt:safe_model/data.pkl using modelscan.scanners.PickleUnsafeOpScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", @@ -237,7 +218,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Scanning Unsafe Model\n", + "# Scan Unsafe Model\n", "\n", "The scan results include information on the files scanned, and any issues if found. In this case, a critical severity level issue is found in the unsafe model scanned. \n", "\n", @@ -253,7 +234,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/unsafe_model.pt:unsafe_model/data.pkl using pickle model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/unsafe_model.pt:unsafe_model/data.pkl using modelscan.scanners.PickleUnsafeOpScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", @@ -278,7 +261,56 @@ } ], "source": [ - "!modelscan --path ./PyTorchModels/unsafe_model.pt" + "!modelscan --path ./PyTorchModels/unsafe_model.pt " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reporting Format\n", + "ModelScan can report scan results in console (default), json, or custom report (to be defined by user in settings-file). For mode details, please see: ` modelscan -h` " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## JSON Report\n", + "\n", + "For JSON reporting: `modelscan -p ./path-to/file -r json -o output-file-name.json` " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/unsafe_model.pt:unsafe_model/data.pkl using modelscan.scanners.PickleUnsafeOpScan model scan\n", + "\u001b[1m{\u001b[0m\u001b[32m\"modelscan_version\"\u001b[0m: \u001b[32m\"0.5.0\"\u001b[0m, \u001b[32m\"timestamp\"\u001b[0m: \u001b[32m\"2024-01-25T17:10:54.306065\"\u001b[0m, \n", + "\u001b[32m\"input_path\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/unsafe_model.pt\"\u001b[0m\n", + ", \u001b[32m\"total_issues\"\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m\"summary\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"total_issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"LOW\"\u001b[0m: \u001b[1;36m0\u001b[0m, \n", + "\u001b[32m\"MEDIUM\"\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m\"HIGH\"\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m\"CRITICAL\"\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m\"issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"CRITICAL\"\u001b[0m: \n", + "\u001b[1m[\u001b[0m\u001b[1m{\u001b[0m\u001b[32m\"description\"\u001b[0m: \u001b[32m\"Use of unsafe operator 'system' from module 'posix'\"\u001b[0m, \n", + "\u001b[32m\"operator\"\u001b[0m: \u001b[32m\"system\"\u001b[0m, \u001b[32m\"module\"\u001b[0m: \u001b[32m\"posix\"\u001b[0m, \u001b[32m\"source\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/unsafe_model.pt:\u001b[0m\n", + "\u001b[32munsafe_model/data.pkl\"\u001b[0m, \u001b[32m\"scanner\"\u001b[0m: \u001b[32m\"modelscan.scanners.PickleUnsafeOpScan\"\u001b[0m\u001b[1m}\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m, \n", + "\u001b[32m\"errors\"\u001b[0m: \u001b[1m[\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m\"scanned\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"total_scanned\"\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m\"scanned_files\"\u001b[0m: \n", + "\u001b[1m[\u001b[0m\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/PyTorchModels/unsafe_model.pt\u001b[0m\n", + "\u001b[32m:unsafe_model/data.pkl\"\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m\n" + ] + } + ], + "source": [ + "# This will save the scan results in file: pytorch-model-scan-results.json\n", + "!modelscan --path ./PyTorchModels/unsafe_model.pt -r json -o pytorch-model-scan-results.json" ] }, { @@ -291,7 +323,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.9.16 ('notebooks-pt')", + "display_name": "Python 3.10.13 ('py310')", "language": "python", "name": "python3" }, @@ -305,11 +337,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.10.13" }, "vscode": { "interpreter": { - "hash": "95ff33cfa0d19b4e14b9fe5f5453ae67cda9106db59c9cf5735c1bf946b2200c" + "hash": "bd638e2064d9001d4ca93bc8e56e039dad230900dd235e8a6196f1614960903a" } } }, diff --git a/notebooks/tensorflow_fashion_mnist.ipynb b/notebooks/tensorflow_fashion_mnist.ipynb index d420324..bdd48e6 100644 --- a/notebooks/tensorflow_fashion_mnist.ipynb +++ b/notebooks/tensorflow_fashion_mnist.ipynb @@ -16,18 +16,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Requirement already satisfied: modelscan in /Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages (0.1.1)\n", - "Requirement already satisfied: click<9.0.0,>=8.1.3 in /Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages (from modelscan) (8.1.6)\n", - "Requirement already satisfied: numpy==1.24.0 in /Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages (from modelscan) (1.24.0)\n", - "Requirement already satisfied: rich<14.0.0,>=13.4.2 in /Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages (from modelscan) (13.5.2)\n", - "Requirement already satisfied: markdown-it-py>=2.2.0 in /Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages (from rich<14.0.0,>=13.4.2->modelscan) (3.0.0)\n", - "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages (from rich<14.0.0,>=13.4.2->modelscan) (2.15.1)\n", - "Requirement already satisfied: mdurl~=0.1 in /Users/mehrinkiani/mambaforge/envs/notebooks-xgb-2/lib/python3.9/site-packages (from markdown-it-py>=2.2.0->rich<14.0.0,>=13.4.2->modelscan) (0.1.2)\n" + "modelscan, version 0.5.0\n" ] } ], "source": [ - "!pip install modelscan" + "!pip install -q modelscan\n", + "!modelscan -v" ] }, { @@ -73,28 +68,28 @@ "output_type": "stream", "text": [ "Epoch 1/10\n", - "1875/1875 [==============================] - 2s 752us/step - loss: 0.5000 - accuracy: 0.8244\n", + "1875/1875 [==============================] - 2s 1ms/step - loss: 0.4993 - accuracy: 0.8256\n", "Epoch 2/10\n", - "1875/1875 [==============================] - 1s 726us/step - loss: 0.3774 - accuracy: 0.8637\n", + "1875/1875 [==============================] - 2s 915us/step - loss: 0.3759 - accuracy: 0.8643\n", "Epoch 3/10\n", - "1875/1875 [==============================] - 1s 734us/step - loss: 0.3386 - accuracy: 0.8767\n", + "1875/1875 [==============================] - 2s 925us/step - loss: 0.3380 - accuracy: 0.8761\n", "Epoch 4/10\n", - "1875/1875 [==============================] - 1s 730us/step - loss: 0.3131 - accuracy: 0.8858\n", + "1875/1875 [==============================] - 2s 919us/step - loss: 0.3125 - accuracy: 0.8852\n", "Epoch 5/10\n", - "1875/1875 [==============================] - 1s 750us/step - loss: 0.2943 - accuracy: 0.8921\n", + "1875/1875 [==============================] - 2s 947us/step - loss: 0.2955 - accuracy: 0.8910\n", "Epoch 6/10\n", - "1875/1875 [==============================] - 1s 735us/step - loss: 0.2821 - accuracy: 0.8953\n", + "1875/1875 [==============================] - 2s 839us/step - loss: 0.2819 - accuracy: 0.8951\n", "Epoch 7/10\n", - "1875/1875 [==============================] - 1s 731us/step - loss: 0.2686 - accuracy: 0.9007\n", + "1875/1875 [==============================] - 2s 1ms/step - loss: 0.2708 - accuracy: 0.9000\n", "Epoch 8/10\n", - "1875/1875 [==============================] - 1s 733us/step - loss: 0.2586 - accuracy: 0.9029\n", + "1875/1875 [==============================] - 2s 983us/step - loss: 0.2577 - accuracy: 0.9047\n", "Epoch 9/10\n", - "1875/1875 [==============================] - 1s 753us/step - loss: 0.2493 - accuracy: 0.9068\n", + "1875/1875 [==============================] - 2s 983us/step - loss: 0.2496 - accuracy: 0.9065\n", "Epoch 10/10\n", - "1875/1875 [==============================] - 1s 731us/step - loss: 0.2390 - accuracy: 0.9104\n", - "313/313 [==============================] - 0s 482us/step - loss: 0.3422 - accuracy: 0.8820\n", + "1875/1875 [==============================] - 2s 818us/step - loss: 0.2387 - accuracy: 0.9109\n", + "313/313 [==============================] - 0s 564us/step - loss: 0.4170 - accuracy: 0.8603\n", "\n", - "Model trained with test accuracy: 0.8820000290870667\n" + "Model trained with test accuracy: 0.8603000044822693\n" ] } ], @@ -125,9 +120,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "1/1 [==============================] - 0s 35ms/step\n", + "1/1 [==============================] - 0s 36ms/step\n", "\n", - "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 95.854996 99.886 100. ]\n", + "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 94.768 65.806 100. ]\n", "\n", "The true labels are ['Ankle boot', 'Pullover', 'Trouser']\n" ] @@ -166,13 +161,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/safe_model/fingerprint.pb using saved_model model scan\n", - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/safe_model/keras_metadata.pb using saved_model model scan\n", - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/safe_model/saved_model.pb using saved_model model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/safe_model/fingerprint.pb using modelscan.scanners.SavedModelTensorflowOpScan model scan\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/safe_model/keras_metadata.pb using modelscan.scanners.SavedModelLambdaDetectScan model scan\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/safe_model/saved_model.pb using modelscan.scanners.SavedModelTensorflowOpScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", - "\u001b[32m No issues found! 🎉\u001b[0m\n" + "\u001b[32m No issues found! 🎉\u001b[0m\n", + "\n", + "\u001b[34m--- Skipped --- \u001b[0m\n", + "\n", + "Total skipped: \u001b[1;36m2\u001b[0m - run with --show-skipped to see the full list.\n" ] } ], @@ -228,7 +229,7 @@ "aws_secret_access_key=\n", "1/1 [==============================] - 0s 48ms/step\n", "\n", - "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 95.854996 99.886 100. ]\n", + "The model predicts: ['Ankle boot', 'Pullover', 'Trouser'] with probabilities: [ 94.768 65.806 100. ]\n", "\n", "The true labels are ['Ankle boot', 'Pullover', 'Trouser']\n" ] @@ -271,9 +272,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/fingerprint.pb using saved_model model scan\n", - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/keras_metadata.pb using saved_model model scan\n", - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/saved_model.pb using saved_model model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/fingerprint.pb using modelscan.scanners.SavedModelTensorflowOpScan model scan\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/keras_metadata.pb using modelscan.scanners.SavedModelLambdaDetectScan model scan\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/saved_model.pb using modelscan.scanners.SavedModelTensorflowOpScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", @@ -298,7 +301,11 @@ "Unsafe operator found:\n", " - Severity: HIGH\n", " - Description: Use of unsafe operator 'WriteFile' from module 'Tensorflow'\n", - " - Source: /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/saved_model.pb\n" + " - Source: /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/saved_model.pb\n", + "\n", + "\u001b[34m--- Skipped --- \u001b[0m\n", + "\n", + "Total skipped: \u001b[1;36m2\u001b[0m - run with --show-skipped to see the full list.\n" ] } ], @@ -306,6 +313,65 @@ "!modelscan -p TensorFlowModels/unsafe_model" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reporting Format\n", + "ModelScan can report scan results in console (default), JSON, or custom report (to be defined by user in settings-file). For mode details, please see: ` modelscan -h` " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## JSON Report\n", + "\n", + "For JSON reporting: `modelscan -p ./path-to/file -r json -o output-file-name.json` " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/fingerprint.pb using modelscan.scanners.SavedModelTensorflowOpScan model scan\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/keras_metadata.pb using modelscan.scanners.SavedModelLambdaDetectScan model scan\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/saved_model.pb using modelscan.scanners.SavedModelTensorflowOpScan model scan\n", + "\u001b[1m{\u001b[0m\u001b[32m\"modelscan_version\"\u001b[0m: \u001b[32m\"0.5.0\"\u001b[0m, \u001b[32m\"timestamp\"\u001b[0m: \u001b[32m\"2024-01-25T17:56:46.559473\"\u001b[0m, \n", + "\u001b[32m\"input_path\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model\"\u001b[0m\n", + ", \u001b[32m\"total_issues\"\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m\"summary\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"total_issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"LOW\"\u001b[0m: \u001b[1;36m0\u001b[0m, \n", + "\u001b[32m\"MEDIUM\"\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m\"HIGH\"\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m\"CRITICAL\"\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m\"issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"HIGH\"\u001b[0m: \n", + "\u001b[1m[\u001b[0m\u001b[1m{\u001b[0m\u001b[32m\"description\"\u001b[0m: \u001b[32m\"Use of unsafe operator 'ReadFile' from module 'Tensorflow'\"\u001b[0m, \n", + "\u001b[32m\"operator\"\u001b[0m: \u001b[32m\"ReadFile\"\u001b[0m, \u001b[32m\"module\"\u001b[0m: \u001b[32m\"Tensorflow\"\u001b[0m, \u001b[32m\"source\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/\u001b[0m\n", + "\u001b[32msaved_model.pb\"\u001b[0m, \u001b[32m\"scanner\"\u001b[0m: \u001b[32m\"modelscan.scanners.SavedModelTensorflowOpScan\"\u001b[0m\u001b[1m}\u001b[0m, \n", + "\u001b[1m{\u001b[0m\u001b[32m\"description\"\u001b[0m: \u001b[32m\"Use of unsafe operator 'WriteFile' from module 'Tensorflow'\"\u001b[0m, \n", + "\u001b[32m\"operator\"\u001b[0m: \u001b[32m\"WriteFile\"\u001b[0m, \u001b[32m\"module\"\u001b[0m: \u001b[32m\"Tensorflow\"\u001b[0m, \u001b[32m\"source\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/\u001b[0m\n", + "\u001b[32msaved_model.pb\"\u001b[0m, \u001b[32m\"scanner\"\u001b[0m: \u001b[32m\"modelscan.scanners.SavedModelTensorflowOpScan\"\u001b[0m\u001b[1m}\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m, \n", + "\u001b[32m\"errors\"\u001b[0m: \u001b[1m[\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m\"scanned\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"total_scanned\"\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m\"scanned_files\"\u001b[0m: \n", + "\u001b[1m[\u001b[0m\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model\u001b[0m\n", + "\u001b[32m/fingerprint.pb\"\u001b[0m, \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/\u001b[0m\n", + "\u001b[32mkeras_metadata.pb\"\u001b[0m, \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/TensorFlowModels/unsafe_model/\u001b[0m\n", + "\u001b[32msaved_model.pb\"\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m\n" + ] + } + ], + "source": [ + "# This will save the scan results in file: tensorflow-model-scan-results.json\n", + "!modelscan --path TensorFlowModels/unsafe_model -r json -o tensorflow-model-scan-results.json" + ] + }, { "cell_type": "code", "execution_count": null, @@ -316,7 +382,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.9.16 ('notebooks-xgb-2')", + "display_name": "Python 3.10.13 ('py310')", "language": "python", "name": "python3" }, @@ -330,12 +396,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.10.13" }, "orig_nbformat": 4, "vscode": { "interpreter": { - "hash": "8792b9cba3843e3778a70fb92fd4a6e11f0e29f4d97b60d08b903ca0b490cce6" + "hash": "bd638e2064d9001d4ca93bc8e56e039dad230900dd235e8a6196f1614960903a" } } }, diff --git a/notebooks/xgboost_diabetes_classification.ipynb b/notebooks/xgboost_diabetes_classification.ipynb index 1ead323..4638d19 100644 --- a/notebooks/xgboost_diabetes_classification.ipynb +++ b/notebooks/xgboost_diabetes_classification.ipynb @@ -13,9 +13,18 @@ "execution_count": 1, "id": "00052a84", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "modelscan, version 0.5.0\n" + ] + } + ], "source": [ - "!pip install -q modelscan" + "!pip install -q modelscan\n", + "!modelscan -v" ] }, { @@ -119,7 +128,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/safe_model.pkl using pickle model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/safe_model.pkl using modelscan.scanners.PickleUnsafeOpScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", @@ -226,7 +237,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl using pickle model scan\n", + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl using modelscan.scanners.PickleUnsafeOpScan model scan\n", "\n", "\u001b[34m--- Summary ---\u001b[0m\n", "\n", @@ -254,18 +267,62 @@ "!modelscan -p XGBoostModels/unsafe_model.pkl" ] }, + { + "cell_type": "markdown", + "id": "9a908243", + "metadata": {}, + "source": [ + "# Reporting Format\n", + "ModelScan can report scan results in console (default), JSON, or custom report (to be defined by user in settings-file). For mode details, please see: ` modelscan -h` " + ] + }, + { + "cell_type": "markdown", + "id": "7ff858af", + "metadata": {}, + "source": [ + "## JSON Report\n", + "\n", + "For JSON reporting: `modelscan -p ./path-to/file -r json -o output-file-name.json` " + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "6df55b3e", "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No settings file detected at /Users/mehrinkiani/Documents/modelscan/notebooks/modelscan-settings.toml. Using defaults. \n", + "\n", + "Scanning /Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl using modelscan.scanners.PickleUnsafeOpScan model scan\n", + "\u001b[1m{\u001b[0m\u001b[32m\"modelscan_version\"\u001b[0m: \u001b[32m\"0.5.0\"\u001b[0m, \u001b[32m\"timestamp\"\u001b[0m: \u001b[32m\"2024-01-25T17:56:00.855056\"\u001b[0m, \n", + "\u001b[32m\"input_path\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl\u001b[0m\n", + "\u001b[32m\"\u001b[0m, \u001b[32m\"total_issues\"\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m\"summary\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"total_issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"LOW\"\u001b[0m: \u001b[1;36m0\u001b[0m, \n", + "\u001b[32m\"MEDIUM\"\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m\"HIGH\"\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m\"CRITICAL\"\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m\"issues_by_severity\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"CRITICAL\"\u001b[0m: \n", + "\u001b[1m[\u001b[0m\u001b[1m{\u001b[0m\u001b[32m\"description\"\u001b[0m: \u001b[32m\"Use of unsafe operator 'system' from module 'posix'\"\u001b[0m, \n", + "\u001b[32m\"operator\"\u001b[0m: \u001b[32m\"system\"\u001b[0m, \u001b[32m\"module\"\u001b[0m: \u001b[32m\"posix\"\u001b[0m, \u001b[32m\"source\"\u001b[0m: \n", + "\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl\u001b[0m\n", + "\u001b[32m\"\u001b[0m, \u001b[32m\"scanner\"\u001b[0m: \u001b[32m\"modelscan.scanners.PickleUnsafeOpScan\"\u001b[0m\u001b[1m}\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m\"errors\"\u001b[0m: \u001b[1m[\u001b[0m\u001b[1m]\u001b[0m, \n", + "\u001b[32m\"scanned\"\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m\"total_scanned\"\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m\"scanned_files\"\u001b[0m: \n", + "\u001b[1m[\u001b[0m\u001b[32m\"/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pk\u001b[0m\n", + "\u001b[32ml\"\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m\n" + ] + } + ], + "source": [ + "# This will save the scan results in file: xgboost-model-scan-results.json\n", + "!modelscan --path XGBoostModels/unsafe_model.pkl -r json -o xgboost-model-scan-results.json" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3.9.16 ('notebooks-xgb-2')", + "display_name": "Python 3.10.13 ('py310')", "language": "python", "name": "python3" }, @@ -279,11 +336,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.10.13" }, "vscode": { "interpreter": { - "hash": "8792b9cba3843e3778a70fb92fd4a6e11f0e29f4d97b60d08b903ca0b490cce6" + "hash": "bd638e2064d9001d4ca93bc8e56e039dad230900dd235e8a6196f1614960903a" } } },