From 51a2831cf4941894f800de472dda34fafe3a77c5 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Mon, 30 Oct 2023 22:59:48 +0100 Subject: [PATCH 01/22] Remove scrapbook and papermill deps Signed-off-by: miguelgfierro --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 5aacc47a3..3ce8b5d4b 100644 --- a/setup.py +++ b/setup.py @@ -48,12 +48,10 @@ "retrying>=1.3.3", "pandera[strategies]>=0.6.5", # For generating fake datasets "scikit-surprise>=1.0.6", - "scrapbook>=0.5.0,<1.0.0", "hyperopt>=0.1.2,<1", "ipykernel>=4.6.1,<7", "jupyter>=1,<2", "locust>=1,<2", - "papermill>=2.1.2,<3", ] # shared dependencies From 964bca0582b491fa4fe73f385cfee9c4a610d1f6 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Mon, 30 Oct 2023 23:01:57 +0100 Subject: [PATCH 02/22] notebook utils programmatic execution Signed-off-by: miguelgfierro --- recommenders/utils/notebook_utils.py | 114 +++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/recommenders/utils/notebook_utils.py b/recommenders/utils/notebook_utils.py index c47154d60..723a38231 100644 --- a/recommenders/utils/notebook_utils.py +++ b/recommenders/utils/notebook_utils.py @@ -2,6 +2,10 @@ # Licensed under the MIT License. import os +import re +import nbformat +from nbconvert.preprocessors import ExecutePreprocessor +from IPython.display import display def is_jupyter(): @@ -35,3 +39,113 @@ def is_databricks(): return False except NameError: return False + + +def execute_notebook( + input_notebook, output_notebook, parameters, kernel_name="python3", timeout=600 +): + """Execute a notebook while passing parameters to it. + + .. note:: + + Ensure your Jupyter Notebook is set up with parameters that can be + modified and read. Use Markdown cells to specify parameters that need + modification and code cells to set parameters that need to be read. + + Args: + input_notebook (str): Path to the input notebook. + output_notebook (str): Path to the output notebook + parameters (dict): Dictionary of parameters to pass to the notebook. + kernel_name (str): Kernel name. + timeout (int): Timeout (in seconds) for each cell to execute. + """ + + # Load the Jupyter Notebook + with open(input_notebook, "r") as notebook_file: + notebook_content = nbformat.read(notebook_file, as_version=4) + + # Search for and replace parameter values in code cells + for cell in notebook_content.cells: + if ( + "tags" in cell.metadata + and "parameters" in cell.metadata["tags"] + and cell.cell_type == "code" + ): + cell_source = cell.source + modified_cell_source = ( + cell_source # Initialize a variable to hold the modified source + ) + for param, new_value in parameters.items(): + # Check if the new value is a string and surround it with quotes if necessary + if isinstance(new_value, str): + new_value = f'"{new_value}"' + # Define a regular expression pattern to match parameter assignments and ignore comments + pattern = re.compile( + rf"\b{param}\s*=\s*([^#\n]+)(?:#.*$)?", re.MULTILINE + ) + matches = re.findall(pattern, cell_source) + for match in matches: + old_assignment = match.strip() + modified_cell_source = modified_cell_source.replace( + old_assignment, f"{new_value}" + ) + # Update the cell's source within notebook_content + cell.source = modified_cell_source + + # Create an execution preprocessor + execute_preprocessor = ExecutePreprocessor(timeout=timeout, kernel_name=kernel_name) + + # Execute the notebook + executed_notebook, _ = execute_preprocessor.preprocess( + notebook_content, {"metadata": {"path": "./"}} + ) + + # Save the executed notebook + with open(output_notebook, "w", encoding="utf-8") as executed_notebook_file: + nbformat.write(executed_notebook, executed_notebook_file) + + +def store_metadata(name, value): + """Store data in the notebook's output source code. + This function is similar to snapbook.glue(). + + Args: + name (str): Name of the data. + value (int,float,str): Value of the data. + """ + + metadata = {"notebook_utils": {"name": name, "data": True, "display": False}} + data_json = { + "application/notebook_utils.json+json": { + "name": name, + "data": value, + "encoder": "json", + } + } + display(data_json, metadata=metadata, raw=True) + + +def read_notebook(path): + """Read the metadata stored in the notebook's output source code. + This function is similar to snapbook.read_notebook(). + + Args: + path (str): Path to the notebook. + + Returns: + dict: Dictionary of data stored in the notebook. + """ + # Load the Jupyter Notebook + with open(path, "r") as notebook_file: + notebook_content = nbformat.read(notebook_file, as_version=4) + + # Search for and replace parameter values in code cells + results = {} + for cell in notebook_content.cells: + if cell.cell_type == "code" and "outputs" in cell: + for outputs in cell.outputs: + if "metadata" in outputs and "notebook_utils" in outputs.metadata: + name = outputs.data["application/notebook_utils.json+json"]["name"] + data = outputs.data["application/notebook_utils.json+json"]["data"] + results[name] = data + return results From ab63e1c8160cb06a373c36fe498021339d333e63 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Tue, 31 Oct 2023 06:58:20 +0100 Subject: [PATCH 03/22] Test notebook programmatic Signed-off-by: miguelgfierro --- .../recommenders/utils/test_notebook_utils.py | 104 +++++++++++++++++- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/tests/unit/recommenders/utils/test_notebook_utils.py b/tests/unit/recommenders/utils/test_notebook_utils.py index 24223b703..755e09267 100644 --- a/tests/unit/recommenders/utils/test_notebook_utils.py +++ b/tests/unit/recommenders/utils/test_notebook_utils.py @@ -1,25 +1,43 @@ # Copyright (c) Recommenders contributors. # Licensed under the MIT License. - +import nbclient import pytest import papermill as pm import scrapbook as sb from pathlib import Path -from recommenders.utils.notebook_utils import is_jupyter, is_databricks +from recommenders.utils.notebook_utils import ( + is_jupyter, + is_databricks, + execute_notebook, + read_notebook, +) + + +@pytest.fixture(scope="function") +def notebook_types(): + return Path(__file__).absolute().parent.joinpath("test_notebook_utils.ipynb") + + +@pytest.fixture(scope="function") +def notebook_programmatic(): + return ( + Path(__file__) + .absolute() + .parent.joinpath("programmatic_notebook_execution.ipynb") + ) @pytest.mark.notebooks -def test_is_jupyter(output_notebook, kernel_name): +def test_is_jupyter(notebook_types, output_notebook, kernel_name): # Test on the terminal assert is_jupyter() is False assert is_databricks() is False # Test on Jupyter notebook - path = Path(__file__).absolute().parent.joinpath("test_notebook_utils.ipynb") pm.execute_notebook( - path, + notebook_types, output_notebook, kernel_name=kernel_name, ) @@ -36,3 +54,79 @@ def test_is_jupyter(output_notebook, kernel_name): @pytest.mark.skip(reason="TODO: Implement this") def test_is_databricks(): pass + + +def test_notebook_execution_int(notebook_programmatic, output_notebook, kernel_name): + execute_notebook( + notebook_programmatic, + output_notebook, + kernel_name=kernel_name, + parameters=dict(a=6), + ) + + results = read_notebook(output_notebook) + assert results["response1"] == 8 + + +def test_notebook_execution_float(notebook_programmatic, output_notebook, kernel_name): + execute_notebook( + notebook_programmatic, + output_notebook, + kernel_name=kernel_name, + parameters=dict(a=1.5), + ) + + results = read_notebook(output_notebook) + assert results["response1"] == 3.5 + + +def test_notebook_execution_letter(notebook_programmatic, output_notebook, kernel_name): + execute_notebook( + notebook_programmatic, + output_notebook, + kernel_name=kernel_name, + parameters=dict(b="M"), + ) + + results = read_notebook(output_notebook) + assert results["response2"] is True + + +def test_notebook_execution_other_letter( + notebook_programmatic, output_notebook, kernel_name +): + execute_notebook( + notebook_programmatic, + output_notebook, + kernel_name=kernel_name, + parameters=dict(b="A"), + ) + + results = read_notebook(output_notebook) + assert results["response2"] == "A" + + +def test_notebook_execution_value_error_fails( + notebook_programmatic, output_notebook, kernel_name +): + with pytest.raises(nbclient.exceptions.CellExecutionError): + execute_notebook( + notebook_programmatic, + output_notebook, + kernel_name=kernel_name, + parameters=dict(b=1), + ) + + +def test_notebook_execution_int_with_comment( + notebook_programmatic, output_notebook, kernel_name +): + execute_notebook( + notebook_programmatic, + output_notebook, + kernel_name=kernel_name, + parameters=dict(c=10), + ) + + results = read_notebook(output_notebook) + assert results["response3"] == 12 From 1ac30230b72841d45e197a00c71874ec7f4d18e1 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Tue, 31 Oct 2023 07:02:39 +0100 Subject: [PATCH 04/22] Added test notebook for utils Signed-off-by: miguelgfierro --- .../programmatic_notebook_execution.ipynb | 173 ++++++++++++++++++ .../utils/test_notebook_utils.ipynb | 40 ++-- 2 files changed, 196 insertions(+), 17 deletions(-) create mode 100644 tests/unit/recommenders/utils/programmatic_notebook_execution.ipynb diff --git a/tests/unit/recommenders/utils/programmatic_notebook_execution.ipynb b/tests/unit/recommenders/utils/programmatic_notebook_execution.ipynb new file mode 100644 index 000000000..739709f9d --- /dev/null +++ b/tests/unit/recommenders/utils/programmatic_notebook_execution.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7123b694", + "metadata": {}, + "source": [ + "Copyright (c) Recommenders contributors.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "id": "17b046f1", + "metadata": {}, + "source": [ + "# Programmatic execution of Jupyter notebooks\n", + "\n", + "This is a Jupyter notebook that can be paramtrized to be able to execute it externally. Also, we provide utilities to extract the outputs computed by the notebook.\n", + "\n", + "The main use case for this is to test the notebooks.\n", + "\n", + "**NOTE:**\n", + "Make sure you parametrize the cell where you want to inject parameters. For doing it, go to View, Cell toolbar, Tags. In the text box, add `parameters`" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "494d7493", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.8.13 (default, Mar 28 2022, 11:38:47) \n", + "[GCC 7.5.0]\n" + ] + } + ], + "source": [ + "import os\n", + "import sys\n", + "\n", + "from recommenders.utils.notebook_utils import store_metadata\n", + "\n", + "print(sys.version)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "298e0205", + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], + "source": [ + "# Parameters\n", + "a = 1\n", + "b = \"M\"\n", + "c = 5 # This is a comment" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5c802f7c", + "metadata": {}, + "outputs": [], + "source": [ + "def plus2(num):\n", + " return num + 2\n", + "\n", + "def is_letter(letter):\n", + " if letter == \"M\":\n", + " return True\n", + " elif letter != \"M\" and isinstance(letter, str):\n", + " return letter\n", + " else:\n", + " raise ValueError()\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7f4af7c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n" + ] + } + ], + "source": [ + "response1 = plus2(a)\n", + "print(response1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "004ba65c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "response2 = is_letter(b)\n", + "print(response2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ed0e55a", + "metadata": {}, + "outputs": [], + "source": [ + "response3 = plus2(c)\n", + "print(response3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0de1951d", + "metadata": {}, + "outputs": [], + "source": [ + "store_metadata(\"response1\", response1)\n", + "store_metadata(\"response2\", response2)\n", + "store_metadata(\"response3\", response3)" + ] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/unit/recommenders/utils/test_notebook_utils.ipynb b/tests/unit/recommenders/utils/test_notebook_utils.ipynb index 0d692267e..e9d0d72e4 100644 --- a/tests/unit/recommenders/utils/test_notebook_utils.ipynb +++ b/tests/unit/recommenders/utils/test_notebook_utils.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, + "source": [ + "Copyright (c) Recommenders contributors.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ "# This is a test notebook for recommenders.utils.notebook_utils module" ] @@ -17,7 +24,6 @@ "source": [ "# set the environment path to find Recommenders\n", "import sys\n", - "\n", "import scrapbook as sb\n", "from recommenders.utils.notebook_utils import is_jupyter, is_databricks" @@ -29,40 +35,40 @@ "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "application/scrapbook.scrap.json+json": { - "name": "is_jupyter", "data": true, "encoder": "json", + "name": "is_jupyter", "version": 1 } }, "metadata": { "scrapbook": { - "name": "is_jupyter", "data": true, - "display": false + "display": false, + "name": "is_jupyter" } - } + }, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/scrapbook.scrap.json+json": { - "name": "is_databricks", "data": false, "encoder": "json", + "name": "is_databricks", "version": 1 } }, "metadata": { "scrapbook": { - "name": "is_databricks", "data": true, - "display": false + "display": false, + "name": "is_databricks" } - } + }, + "output_type": "display_data" } ], "source": [ @@ -81,13 +87,13 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "name": "python3", "display_name": "Python 3.6.12 64-bit ('sb_full': conda)", "metadata": { "interpreter": { "hash": "f28711ae1fad89778b64817fc2d746effb845deda73edae96b2473c20b2d4f70" } - } + }, + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -104,4 +110,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} \ No newline at end of file +} From 6a03c423b8d5cfc7b35c735f6375f31d23430b88 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Tue, 31 Oct 2023 08:00:05 +0100 Subject: [PATCH 05/22] data notebooks Signed-off-by: miguelgfierro --- examples/01_prepare_data/mind_utils.ipynb | 53 +++++++++++++------ .../wikidata_knowledge_graph.ipynb | 9 ++-- tests/data_validation/examples/test_mind.py | 28 +++++----- .../data_validation/examples/test_wikidata.py | 16 +++--- 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/examples/01_prepare_data/mind_utils.ipynb b/examples/01_prepare_data/mind_utils.ipynb index 77fc03759..e03a3683d 100644 --- a/examples/01_prepare_data/mind_utils.ipynb +++ b/examples/01_prepare_data/mind_utils.ipynb @@ -13,19 +13,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Mind Utils Generation\n", + "# MIND Utils Generation\n", "\n", - "Many news recommendation methods ultilize word embeddings, news vertical embeddings, news subvertical embeddings and user id embedding. Therefore, it is necessary to generate a word dictionary, a vertical dictionary, a subvertical dictionary and a userid dictionary to convert words, news verticals, subvericals and user ids from strings to indexes. To use the pretrain word embedding, a embedding matrix is generated as the intial weight of the word embedding layer.\n", + "MIND dataset\\[1\\] is a large-scale English news dataset. It was collected from anonymized behavior logs of Microsoft News website. MIND contains 1,000,000 users, 161,013 news articles and 15,777,377 impression logs. Every news article contains rich textual content including title, abstract, body, category and entities. Each impression log contains the click events, non-clicked events and historical news click behaviors of this user before this impression.\n", "\n", + "Many news recommendation methods use word embeddings, news vertical embeddings, news subvertical embeddings and user id embedding. Therefore, it is necessary to generate a word dictionary, a vertical dictionary, a subvertical dictionary and a `userid` dictionary to convert words, news verticals, subverticals and user ids from strings to indexes. To use the pretrain word embedding, an embedding matrix is generated as the initial weight of the word embedding layer.\n", "\n", - "This notebook gives examples about how to generate\n", - "* word_dict.pkl: convert the words in news titles into indexes.\n", - "* word_dict_all.pkl: convert the words in news titles and abstracts into indexes.\n", - "* embedding.npy: pretrained word embedding matrix of words in word_dict.pkl\n", - "* embedding_all.npy: pretrained embedding matrix of words in word_dict_all.pkl\n", - "* vert_dict.pkl: convert news verticals into indexes.\n", - "* subvert_dict.pkl: convert news subverticals into indexes.\n", - "* uid2index.pkl: convert user ids into indexes." + "This notebook gives examples about how to generate:\n", + "* `word_dict.pkl`: convert the words in news titles into indexes.\n", + "* `word_dict_all.pkl`: convert the words in news titles and abstracts into indexes.\n", + "* `embedding.npy`: pretrained word embedding matrix of words in word_dict.pkl\n", + "* `embedding_all.npy`: pretrained embedding matrix of words in word_dict_all.pkl\n", + "* `vert_dict.pkl`: convert news verticals into indexes.\n", + "* `subvert_dict.pkl`: convert news subverticals into indexes.\n", + "* `uid2index.pkl`: convert user ids into indexes." ] }, { @@ -49,9 +50,9 @@ "import pandas as pd\n", "from tqdm import tqdm\n", "import pickle\n", - "import scrapbook as sb\n", "from collections import Counter\n", "from tempfile import TemporaryDirectory\n", + "\n", "from recommenders.datasets.mind import (download_mind,\n", " extract_mind,\n", " download_and_extract_glove,\n", @@ -59,6 +60,7 @@ " word_tokenize\n", " )\n", "from recommenders.datasets.download_utils import unzip_file\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n" ] @@ -66,10 +68,15 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "tags": [ + "parameters" + ] + }, "outputs": [], "source": [ - "mind_type=\"demo\"\n", + "# MIND sizes: \"demo\", \"small\" or \"large\"\n", + "mind_type=\"demo\" \n", "# word_embedding_dim should be in [50, 100, 200, 300]\n", "word_embedding_dim = 300" ] @@ -465,7 +472,14 @@ } ], "source": [ - "sb.glue(\"utils_state\", utils_state)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"vert_num\", len(vert_dict))\n", + "store_metadata(\"subvert_num\", len(subvert_dict))\n", + "store_metadata(\"word_num\", len(word_dict))\n", + "store_metadata(\"word_num_all\", len(word_dict_all))\n", + "store_metadata(\"embedding_exist_num\", len(exist_word))\n", + "store_metadata(\"embedding_exist_num_all\", len(exist_all_word))\n", + "store_metadata(\"uid2index\", len(uid2index))" ] }, { @@ -476,6 +490,15 @@ "source": [ "tmpdir.cleanup()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "\\[1\\] Wu, Fangzhao, et al. \"MIND: A Large-scale Dataset for News Recommendation\" Proceedings of the 58th Annual Meeting of the Association for Computational Linguistics. https://msnews.github.io/competition.html
" + ] } ], "metadata": { @@ -500,4 +523,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/01_prepare_data/wikidata_knowledge_graph.ipynb b/examples/01_prepare_data/wikidata_knowledge_graph.ipynb index 067f917c4..7d8c36490 100644 --- a/examples/01_prepare_data/wikidata_knowledge_graph.ipynb +++ b/examples/01_prepare_data/wikidata_knowledge_graph.ipynb @@ -32,14 +32,12 @@ "source": [ "# Set logging error\n", "import logging\n", - "level = logging.ERROR\n", "logger = logging.getLogger()\n", - "logger.setLevel(level)\n", + "logger.setLevel(logging.ERROR)\n", "for handler in logger.handlers:\n", " handler.setLevel(level)\n", "\n", "import sys\n", - "import scrapbook as sb\n", "import pandas as pd\n", "import networkx as nx\n", "import matplotlib.pyplot as plt\n", @@ -50,6 +48,7 @@ " query_entity_links, \n", " read_linked_entities,\n", " query_entity_description)\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(f\"System version: {sys.version}\")" ] @@ -582,8 +581,8 @@ } ], "source": [ - "# Record results with papermill for unit-tests\n", - "sb.glue(\"length_result\", number_movies)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"length_result\", number_movies)" ] } ], diff --git a/tests/data_validation/examples/test_mind.py b/tests/data_validation/examples/test_mind.py index e03162bf9..632c01c82 100644 --- a/tests/data_validation/examples/test_mind.py +++ b/tests/data_validation/examples/test_mind.py @@ -1,14 +1,12 @@ # Copyright (c) Recommenders contributors. # Licensed under the MIT License. -import pytest -import papermill as pm -import scrapbook as sb +from recommenders.utils.notebook_utils import execute_notebook, read_notebook def test_mind_utils_runs(notebooks, output_notebook, kernel_name, tmp): notebook_path = notebooks["mind_utils"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -18,20 +16,18 @@ def test_mind_utils_runs(notebooks, output_notebook, kernel_name, tmp): def test_mind_utils_values(notebooks, output_notebook, kernel_name, tmp): notebook_path = notebooks["mind_utils"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, - parameters=dict(mind_type="small", word_embedding_dim=300), + parameters=dict(mind_type="demo", word_embedding_dim=300), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) - assert results["utils_state"]["vert_num"] == 17 - assert results["utils_state"]["subvert_num"] == 17 - assert results["utils_state"]["word_num"] == 23404 - assert results["utils_state"]["word_num_all"] == 41074 - assert results["utils_state"]["embedding_exist_num"] == 22408 - assert results["utils_state"]["embedding_exist_num_all"] == 37634 - assert results["utils_state"]["uid2index"] == 5000 + assert results["vert_num"] == 17 + assert results["subvert_num"] == 17 + assert results["word_num"] == 23404 + assert results["word_num_all"] == 41074 + assert results["embedding_exist_num"] == 22408 + assert results["embedding_exist_num_all"] == 37634 + assert results["uid2index"] == 5000 diff --git a/tests/data_validation/examples/test_wikidata.py b/tests/data_validation/examples/test_wikidata.py index cdee1699b..65c676f67 100644 --- a/tests/data_validation/examples/test_wikidata.py +++ b/tests/data_validation/examples/test_wikidata.py @@ -3,16 +3,16 @@ import pytest -import papermill as pm -import scrapbook as sb + +from recommenders.utils.notebook_utils import execute_notebook, read_notebook @pytest.mark.notebooks -@pytest.mark.skip(reason="Wikidata API is unstable") +# @pytest.mark.skip(reason="Wikidata API is unstable") def test_wikidata_runs(notebooks, output_notebook, kernel_name, tmp): notebook_path = notebooks["wikidata_knowledge_graph"] MOVIELENS_SAMPLE_SIZE = 5 - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -25,10 +25,10 @@ def test_wikidata_runs(notebooks, output_notebook, kernel_name, tmp): @pytest.mark.notebooks -@pytest.mark.skip(reason="Wikidata API is unstable") +# @pytest.mark.skip(reason="Wikidata API is unstable") def test_wikidata_values(notebooks, output_notebook, kernel_name): notebook_path = notebooks["wikidata_knowledge_graph"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -36,9 +36,7 @@ def test_wikidata_values(notebooks, output_notebook, kernel_name): MOVIELENS_DATA_SIZE="100k", MOVIELENS_SAMPLE=True, MOVIELENS_SAMPLE_SIZE=5 ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) # NOTE: The return number should be always 5, but sometimes we get less because wikidata is unstable assert results["length_result"] >= 1 From e1c2b63727898176e79014765a19e818f1a10176 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Tue, 31 Oct 2023 10:57:04 +0100 Subject: [PATCH 06/22] Replace papermill and scrapbook for new internal function Signed-off-by: miguelgfierro --- examples/00_quick_start/als_movielens.ipynb | 1632 ++++++++--------- examples/00_quick_start/dkn_MIND.ipynb | 2 +- .../00_quick_start/fastai_movielens.ipynb | 22 +- .../00_quick_start/geoimc_movielens.ipynb | 654 +++---- examples/00_quick_start/ncf_movielens.ipynb | 20 +- examples/00_quick_start/rbm_movielens.ipynb | 10 +- examples/00_quick_start/sar_movielens.ipynb | 12 +- .../00_quick_start/wide_deep_movielens.ipynb | 12 +- examples/00_quick_start/xdeepfm_criteo.ipynb | 4 +- .../baseline_deep_dive.ipynb | 16 +- .../cornac_bivae_deep_dive.ipynb | 8 +- .../cornac_bpr_deep_dive.ipynb | 8 +- .../lightgcn_deep_dive.ipynb | 8 +- .../ncf_deep_dive.ipynb | 16 +- .../sar_deep_dive.ipynb | 8 +- .../surprise_svd_deep_dive.ipynb | 20 +- .../mmlspark_lightgbm_criteo.ipynb | 4 +- .../vowpal_wabbit_deep_dive.ipynb | 20 +- examples/02_model_hybrid/fm_deep_dive.ipynb | 2 +- .../02_model_hybrid/lightfm_deep_dive.ipynb | 8 +- examples/README.md | 2 +- examples/template.ipynb | 2 +- tests/README.md | 8 +- .../functional/examples/test_notebooks_gpu.py | 93 +- .../examples/test_notebooks_pyspark.py | 23 +- .../examples/test_notebooks_python.py | 56 +- tests/smoke/examples/test_notebooks_gpu.py | 63 +- .../smoke/examples/test_notebooks_pyspark.py | 16 +- tests/smoke/examples/test_notebooks_python.py | 40 +- tests/unit/examples/test_notebooks_gpu.py | 16 +- tests/unit/examples/test_notebooks_pyspark.py | 16 +- tests/unit/examples/test_notebooks_python.py | 22 +- .../recommenders/utils/test_notebook_utils.py | 2 +- 33 files changed, 1376 insertions(+), 1469 deletions(-) diff --git a/examples/00_quick_start/als_movielens.ipynb b/examples/00_quick_start/als_movielens.ipynb index 4e1c21a09..dc784f996 100644 --- a/examples/00_quick_start/als_movielens.ipynb +++ b/examples/00_quick_start/als_movielens.ipynb @@ -1,818 +1,818 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Recommenders contributors.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Running ALS on MovieLens (PySpark)\n", - "\n", - "Matrix factorization by [ALS](https://spark.apache.org/docs/latest/api/python/_modules/pyspark/ml/recommendation.html#ALS) (Alternating Least Squares) is a well known collaborative filtering algorithm.\n", - "\n", - "This notebook provides an example of how to utilize and evaluate ALS PySpark ML (DataFrame-based API) implementation, meant for large-scale distributed datasets. We use a smaller dataset in this example to run ALS efficiently on multiple cores of a [Data Science Virtual Machine](https://azure.microsoft.com/en-gb/services/virtual-machines/data-science-virtual-machines/)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Note**: This notebook requires a PySpark environment to run properly. Please follow the steps in [SETUP.md](../../SETUP.md) to install the PySpark environment." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "System version: 3.8.0 (default, Nov 6 2019, 21:49:08) \n", - "[GCC 7.3.0]\n", - "Spark version: 3.2.0\n" - ] - } - ], - "source": [ - "# set the environment path to find Recommenders\n", - "import sys\n", - "import pyspark\n", - "from pyspark.ml.recommendation import ALS\n", - "import pyspark.sql.functions as F\n", - "from pyspark.sql import SparkSession\n", - "from pyspark.sql.types import StructType, StructField\n", - "from pyspark.sql.types import StringType, FloatType, IntegerType, LongType\n", - "import warnings\n", - "warnings.simplefilter(action='ignore', category=FutureWarning)\n", - "\n", - "from recommenders.utils.timer import Timer\n", - "from recommenders.datasets import movielens\n", - "from recommenders.utils.notebook_utils import is_jupyter\n", - "from recommenders.datasets.spark_splitters import spark_random_split\n", - "from recommenders.evaluation.spark_evaluation import SparkRatingEvaluation, SparkRankingEvaluation\n", - "from recommenders.utils.spark_utils import start_or_get_spark\n", - "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Spark version: {}\".format(pyspark.__version__))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Set the default parameters." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "tags": [ - "parameters" - ] - }, - "outputs": [], - "source": [ - "# top k items to recommend\n", - "TOP_K = 10\n", - "\n", - "# Select MovieLens data size: 100k, 1m, 10m, or 20m\n", - "MOVIELENS_DATA_SIZE = '100k'\n", - "\n", - "# Column names for the dataset\n", - "COL_USER = \"UserId\"\n", - "COL_ITEM = \"MovieId\"\n", - "COL_RATING = \"Rating\"\n", - "COL_TIMESTAMP = \"Timestamp\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 0. Set up Spark context\n", - "\n", - "The following settings work well for debugging locally on VM - change when running on a cluster. We set up a giant single executor with many threads and specify memory cap. " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# the following settings work well for debugging locally on VM - change when running on a cluster\n", - "# set up a giant single executor with many threads and specify memory cap\n", - "spark = start_or_get_spark(\"ALS PySpark\", memory=\"16g\")\n", - "spark.conf.set(\"spark.sql.analyzer.failAmbiguousSelfJoin\", \"false\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1. Download the MovieLens dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4.81k/4.81k [00:05<00:00, 882KB/s]\n", - " \r" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Recommenders contributors.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Running ALS on MovieLens (PySpark)\n", + "\n", + "Matrix factorization by [ALS](https://spark.apache.org/docs/latest/api/python/_modules/pyspark/ml/recommendation.html#ALS) (Alternating Least Squares) is a well known collaborative filtering algorithm.\n", + "\n", + "This notebook provides an example of how to utilize and evaluate ALS PySpark ML (DataFrame-based API) implementation, meant for large-scale distributed datasets. We use a smaller dataset in this example to run ALS efficiently on multiple cores of a [Data Science Virtual Machine](https://azure.microsoft.com/en-gb/services/virtual-machines/data-science-virtual-machines/)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Note**: This notebook requires a PySpark environment to run properly. Please follow the steps in [SETUP.md](../../SETUP.md) to install the PySpark environment." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System version: 3.8.0 (default, Nov 6 2019, 21:49:08) \n", + "[GCC 7.3.0]\n", + "Spark version: 3.2.0\n" + ] + } + ], + "source": [ + "# set the environment path to find Recommenders\n", + "import sys\n", + "import pyspark\n", + "from pyspark.ml.recommendation import ALS\n", + "import pyspark.sql.functions as F\n", + "from pyspark.sql import SparkSession\n", + "from pyspark.sql.types import StructType, StructField\n", + "from pyspark.sql.types import StringType, FloatType, IntegerType, LongType\n", + "import warnings\n", + "warnings.simplefilter(action='ignore', category=FutureWarning)\n", + "\n", + "from recommenders.utils.timer import Timer\n", + "from recommenders.datasets import movielens\n", + "from recommenders.utils.notebook_utils import is_jupyter\n", + "from recommenders.datasets.spark_splitters import spark_random_split\n", + "from recommenders.evaluation.spark_evaluation import SparkRatingEvaluation, SparkRankingEvaluation\n", + "from recommenders.utils.spark_utils import start_or_get_spark\n", + "\n", + "print(\"System version: {}\".format(sys.version))\n", + "print(\"Spark version: {}\".format(pyspark.__version__))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set the default parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], + "source": [ + "# top k items to recommend\n", + "TOP_K = 10\n", + "\n", + "# Select MovieLens data size: 100k, 1m, 10m, or 20m\n", + "MOVIELENS_DATA_SIZE = '100k'\n", + "\n", + "# Column names for the dataset\n", + "COL_USER = \"UserId\"\n", + "COL_ITEM = \"MovieId\"\n", + "COL_RATING = \"Rating\"\n", + "COL_TIMESTAMP = \"Timestamp\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 0. Set up Spark context\n", + "\n", + "The following settings work well for debugging locally on VM - change when running on a cluster. We set up a giant single executor with many threads and specify memory cap. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# the following settings work well for debugging locally on VM - change when running on a cluster\n", + "# set up a giant single executor with many threads and specify memory cap\n", + "spark = start_or_get_spark(\"ALS PySpark\", memory=\"16g\")\n", + "spark.conf.set(\"spark.sql.analyzer.failAmbiguousSelfJoin\", \"false\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Download the MovieLens dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4.81k/4.81k [00:05<00:00, 882KB/s]\n", + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------+-------+------+---------+\n", + "|UserId|MovieId|Rating|Timestamp|\n", + "+------+-------+------+---------+\n", + "| 196| 242| 3.0|881250949|\n", + "| 186| 302| 3.0|891717742|\n", + "| 22| 377| 1.0|878887116|\n", + "| 244| 51| 2.0|880606923|\n", + "| 166| 346| 1.0|886397596|\n", + "| 298| 474| 4.0|884182806|\n", + "| 115| 265| 2.0|881171488|\n", + "| 253| 465| 5.0|891628467|\n", + "| 305| 451| 3.0|886324817|\n", + "| 6| 86| 3.0|883603013|\n", + "| 62| 257| 2.0|879372434|\n", + "| 286| 1014| 5.0|879781125|\n", + "| 200| 222| 5.0|876042340|\n", + "| 210| 40| 3.0|891035994|\n", + "| 224| 29| 3.0|888104457|\n", + "| 303| 785| 3.0|879485318|\n", + "| 122| 387| 5.0|879270459|\n", + "| 194| 274| 2.0|879539794|\n", + "| 291| 1042| 4.0|874834944|\n", + "| 234| 1184| 2.0|892079237|\n", + "+------+-------+------+---------+\n", + "only showing top 20 rows\n", + "\n" + ] + } + ], + "source": [ + "# Note: The DataFrame-based API for ALS currently only supports integers for user and item ids.\n", + "schema = StructType(\n", + " (\n", + " StructField(COL_USER, IntegerType()),\n", + " StructField(COL_ITEM, IntegerType()),\n", + " StructField(COL_RATING, FloatType()),\n", + " StructField(COL_TIMESTAMP, LongType()),\n", + " )\n", + ")\n", + "\n", + "data = movielens.load_spark_df(spark, size=MOVIELENS_DATA_SIZE, schema=schema)\n", + "data.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Split the data using the Spark random splitter provided in utilities" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "N train 75018\n", + "N test 24982\n" + ] + } + ], + "source": [ + "train, test = spark_random_split(data, ratio=0.75, seed=123)\n", + "print (\"N train\", train.cache().count())\n", + "print (\"N test\", test.cache().count())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Train the ALS model on the training data, and get the top-k recommendations for our testing data\n", + "\n", + "To predict movie ratings, we use the rating data in the training set as users' explicit feedback. The hyperparameters used in building the model are referenced from [here](http://mymedialite.net/examples/datasets.html). We do not constrain the latent factors (`nonnegative = False`) in order to allow for both positive and negative preferences towards movies.\n", + "Timing will vary depending on the machine being used to train." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "header = {\n", + " \"userCol\": COL_USER,\n", + " \"itemCol\": COL_ITEM,\n", + " \"ratingCol\": COL_RATING,\n", + "}\n", + "\n", + "\n", + "als = ALS(\n", + " rank=10,\n", + " maxIter=15,\n", + " implicitPrefs=False,\n", + " regParam=0.05,\n", + " coldStartStrategy='drop',\n", + " nonnegative=False,\n", + " seed=42,\n", + " **header\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Took 7.5410127229988575 seconds for training.\n" + ] + } + ], + "source": [ + "with Timer() as train_time:\n", + " model = als.fit(train)\n", + "\n", + "print(\"Took {} seconds for training.\".format(train_time.interval))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the movie recommendation use case, recommending movies that have been rated by the users do not make sense. Therefore, the rated movies are removed from the recommended items.\n", + "\n", + "In order to achieve this, we recommend all movies to all users, and then remove the user-movie pairs that exist in the training dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Stage 126:====================================================>(198 + 2) / 200]\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Took 25.246142672998758 seconds for prediction.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r\n", + " \r" + ] + } + ], + "source": [ + "with Timer() as test_time:\n", + "\n", + " # Get the cross join of all user-item pairs and score them.\n", + " users = train.select(COL_USER).distinct()\n", + " items = train.select(COL_ITEM).distinct()\n", + " user_item = users.crossJoin(items)\n", + " dfs_pred = model.transform(user_item)\n", + "\n", + " # Remove seen items.\n", + " dfs_pred_exclude_train = dfs_pred.alias(\"pred\").join(\n", + " train.alias(\"train\"),\n", + " (dfs_pred[COL_USER] == train[COL_USER]) & (dfs_pred[COL_ITEM] == train[COL_ITEM]),\n", + " how='outer'\n", + " )\n", + "\n", + " top_all = dfs_pred_exclude_train.filter(dfs_pred_exclude_train[f\"train.{COL_RATING}\"].isNull()) \\\n", + " .select('pred.' + COL_USER, 'pred.' + COL_ITEM, 'pred.' + \"prediction\")\n", + "\n", + " # In Spark, transformations are lazy evaluation\n", + " # Use an action to force execute and measure the test time \n", + " top_all.cache().count()\n", + "\n", + "print(\"Took {} seconds for prediction.\".format(test_time.interval))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------+-------+----------+\n", + "|UserId|MovieId|prediction|\n", + "+------+-------+----------+\n", + "| 1| 587| 4.1602826|\n", + "| 1| 869| 2.7732863|\n", + "| 1| 1208| 2.033383|\n", + "| 1| 1348| 1.0019257|\n", + "| 1| 1357| 0.9430026|\n", + "| 1| 1677| 2.8777318|\n", + "| 2| 80| 2.351385|\n", + "| 2| 472| 2.5865319|\n", + "| 2| 582| 3.9548612|\n", + "| 2| 838| 0.9482963|\n", + "| 2| 975| 3.1133535|\n", + "| 2| 1260| 1.9871743|\n", + "| 2| 1325| 1.2368056|\n", + "| 2| 1381| 3.5477588|\n", + "| 2| 1530| 2.08829|\n", + "| 3| 22| 3.1524537|\n", + "| 3| 57| 3.6980162|\n", + "| 3| 89| 3.9733813|\n", + "| 3| 367| 3.6629045|\n", + "| 3| 1091| 0.9144474|\n", + "+------+-------+----------+\n", + "only showing top 20 rows\n", + "\n" + ] + } + ], + "source": [ + "top_all.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Evaluate how well ALS performs" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "rank_eval = SparkRankingEvaluation(test, top_all, k = TOP_K, col_user=COL_USER, col_item=COL_ITEM, \n", + " col_rating=COL_RATING, col_prediction=\"prediction\", \n", + " relevancy_method=\"top_k\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Stage 463:> (0 + 2) / 2]\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model:\tALS\n", + "Top K:\t10\n", + "MAP:\t0.006527\n", + "NDCG:\t0.051718\n", + "Precision@K:\t0.051274\n", + "Recall@K:\t0.018840\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r\n", + " \r" + ] + } + ], + "source": [ + "print(\"Model:\\tALS\",\n", + " \"Top K:\\t%d\" % rank_eval.k,\n", + " \"MAP:\\t%f\" % rank_eval.map_at_k(),\n", + " \"NDCG:\\t%f\" % rank_eval.ndcg_at_k(),\n", + " \"Precision@K:\\t%f\" % rank_eval.precision_at_k(),\n", + " \"Recall@K:\\t%f\" % rank_eval.recall_at_k(), sep='\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5. Evaluate rating prediction" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Stage 500:=============================================> (171 + 3) / 200]\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------+-------+------+---------+----------+\n", + "|UserId|MovieId|Rating|Timestamp|prediction|\n", + "+------+-------+------+---------+----------+\n", + "| 580| 148| 4.0|884125773| 3.4059548|\n", + "| 406| 148| 3.0|879540276| 2.7134619|\n", + "| 916| 148| 2.0|880843892| 2.2241986|\n", + "| 663| 148| 4.0|889492989| 2.714362|\n", + "| 330| 148| 4.0|876544781| 4.52321|\n", + "| 935| 148| 4.0|884472892| 4.3838587|\n", + "| 308| 148| 3.0|887740788| 2.6169493|\n", + "| 20| 148| 5.0|879668713| 4.3721194|\n", + "| 923| 148| 4.0|880387474| 3.9818575|\n", + "| 455| 148| 3.0|879110346| 3.0764186|\n", + "| 15| 148| 3.0|879456049| 2.9913845|\n", + "| 374| 148| 4.0|880392992| 3.2223384|\n", + "| 880| 148| 2.0|880167030| 2.8111982|\n", + "| 677| 148| 4.0|889399265| 3.8451843|\n", + "| 49| 148| 1.0|888068195| 1.3751594|\n", + "| 244| 148| 2.0|880605071| 2.6781514|\n", + "| 84| 148| 4.0|883452274| 3.6721768|\n", + "| 627| 148| 3.0|879530463| 2.6362069|\n", + "| 434| 148| 3.0|886724797| 3.0973828|\n", + "| 793| 148| 4.0|875104498| 2.2886577|\n", + "+------+-------+------+---------+----------+\n", + "only showing top 20 rows\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r\n", + "[Stage 500:=================================================> (186 + 3) / 200]\r\n", + "\r\n", + " \r" + ] + } + ], + "source": [ + "# Generate predicted ratings.\n", + "prediction = model.transform(test)\n", + "prediction.cache().show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Stage 775:==============================================> (174 + 2) / 200]\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model:\tALS rating prediction\n", + "RMSE:\t0.967434\n", + "MAE:\t0.753340\n", + "Explained variance:\t0.265916\n", + "R squared:\t0.259532\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r\n", + " \r" + ] + } + ], + "source": [ + "rating_eval = SparkRatingEvaluation(test, prediction, col_user=COL_USER, col_item=COL_ITEM, \n", + " col_rating=COL_RATING, col_prediction=\"prediction\")\n", + "\n", + "print(\"Model:\\tALS rating prediction\",\n", + " \"RMSE:\\t%f\" % rating_eval.rmse(),\n", + " \"MAE:\\t%f\" % rating_eval.mae(),\n", + " \"Explained variance:\\t%f\" % rating_eval.exp_var(),\n", + " \"R squared:\\t%f\" % rating_eval.rsquared(), sep='\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.006527288768086336, + "encoder": "json", + "name": "map", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "map" + } + }, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.051717802220247217, + "encoder": "json", + "name": "ndcg", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "ndcg" + } + }, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.05127388535031851, + "encoder": "json", + "name": "precision", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "precision" + } + }, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r\n", + "[Stage 904:> (0 + 2) / 2]\r\n", + "\r\n", + " \r" + ] + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.018840283525491316, + "encoder": "json", + "name": "recall", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "recall" + } + }, + "output_type": "display_data" + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.9674342234414528, + "encoder": "json", + "name": "rmse", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "rmse" + } + }, + "output_type": "display_data" + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.7533395161385739, + "encoder": "json", + "name": "mae", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "mae" + } + }, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.2659161968930053, + "encoder": "json", + "name": "exp_var", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "exp_var" + } + }, + "output_type": "display_data" + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 0.2595322728476255, + "encoder": "json", + "name": "rsquared", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "rsquared" + } + }, + "output_type": "display_data" + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 7.5410127229988575, + "encoder": "json", + "name": "train_time", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "train_time" + } + }, + "output_type": "display_data" + }, + { + "data": { + "application/scrapbook.scrap.json+json": { + "data": 25.246142672998758, + "encoder": "json", + "name": "test_time", + "version": 1 + } + }, + "metadata": { + "scrapbook": { + "data": true, + "display": false, + "name": "test_time" + } + }, + "output_type": "display_data" + } + ], + "source": [ + "if is_jupyter():\n", + " # Record results with papermill for tests\n", + " import scrapbook as sb\n", + " store_metadata(\"map\", rank_eval.map_at_k())\n", + " store_metadata(\"ndcg\", rank_eval.ndcg_at_k())\n", + " store_metadata(\"precision\", rank_eval.precision_at_k())\n", + " store_metadata(\"recall\", rank_eval.recall_at_k())\n", + " store_metadata(\"rmse\", rating_eval.rmse())\n", + " store_metadata(\"mae\", rating_eval.mae())\n", + " store_metadata(\"exp_var\", rating_eval.exp_var())\n", + " store_metadata(\"rsquared\", rating_eval.rsquared())\n", + " store_metadata(\"train_time\", train_time.interval)\n", + " store_metadata(\"test_time\", test_time.interval)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "# cleanup spark instance\n", + "spark.stop()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (reco)", + "language": "python", + "name": "reco" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.0" + } }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "+------+-------+------+---------+\n", - "|UserId|MovieId|Rating|Timestamp|\n", - "+------+-------+------+---------+\n", - "| 196| 242| 3.0|881250949|\n", - "| 186| 302| 3.0|891717742|\n", - "| 22| 377| 1.0|878887116|\n", - "| 244| 51| 2.0|880606923|\n", - "| 166| 346| 1.0|886397596|\n", - "| 298| 474| 4.0|884182806|\n", - "| 115| 265| 2.0|881171488|\n", - "| 253| 465| 5.0|891628467|\n", - "| 305| 451| 3.0|886324817|\n", - "| 6| 86| 3.0|883603013|\n", - "| 62| 257| 2.0|879372434|\n", - "| 286| 1014| 5.0|879781125|\n", - "| 200| 222| 5.0|876042340|\n", - "| 210| 40| 3.0|891035994|\n", - "| 224| 29| 3.0|888104457|\n", - "| 303| 785| 3.0|879485318|\n", - "| 122| 387| 5.0|879270459|\n", - "| 194| 274| 2.0|879539794|\n", - "| 291| 1042| 4.0|874834944|\n", - "| 234| 1184| 2.0|892079237|\n", - "+------+-------+------+---------+\n", - "only showing top 20 rows\n", - "\n" - ] - } - ], - "source": [ - "# Note: The DataFrame-based API for ALS currently only supports integers for user and item ids.\n", - "schema = StructType(\n", - " (\n", - " StructField(COL_USER, IntegerType()),\n", - " StructField(COL_ITEM, IntegerType()),\n", - " StructField(COL_RATING, FloatType()),\n", - " StructField(COL_TIMESTAMP, LongType()),\n", - " )\n", - ")\n", - "\n", - "data = movielens.load_spark_df(spark, size=MOVIELENS_DATA_SIZE, schema=schema)\n", - "data.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2. Split the data using the Spark random splitter provided in utilities" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "N train 75018\n", - "N test 24982\n" - ] - } - ], - "source": [ - "train, test = spark_random_split(data, ratio=0.75, seed=123)\n", - "print (\"N train\", train.cache().count())\n", - "print (\"N test\", test.cache().count())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3. Train the ALS model on the training data, and get the top-k recommendations for our testing data\n", - "\n", - "To predict movie ratings, we use the rating data in the training set as users' explicit feedback. The hyperparameters used in building the model are referenced from [here](http://mymedialite.net/examples/datasets.html). We do not constrain the latent factors (`nonnegative = False`) in order to allow for both positive and negative preferences towards movies.\n", - "Timing will vary depending on the machine being used to train." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "header = {\n", - " \"userCol\": COL_USER,\n", - " \"itemCol\": COL_ITEM,\n", - " \"ratingCol\": COL_RATING,\n", - "}\n", - "\n", - "\n", - "als = ALS(\n", - " rank=10,\n", - " maxIter=15,\n", - " implicitPrefs=False,\n", - " regParam=0.05,\n", - " coldStartStrategy='drop',\n", - " nonnegative=False,\n", - " seed=42,\n", - " **header\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Took 7.5410127229988575 seconds for training.\n" - ] - } - ], - "source": [ - "with Timer() as train_time:\n", - " model = als.fit(train)\n", - "\n", - "print(\"Took {} seconds for training.\".format(train_time.interval))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the movie recommendation use case, recommending movies that have been rated by the users do not make sense. Therefore, the rated movies are removed from the recommended items.\n", - "\n", - "In order to achieve this, we recommend all movies to all users, and then remove the user-movie pairs that exist in the training dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[Stage 126:====================================================>(198 + 2) / 200]\r" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Took 25.246142672998758 seconds for prediction.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r\n", - " \r" - ] - } - ], - "source": [ - "with Timer() as test_time:\n", - "\n", - " # Get the cross join of all user-item pairs and score them.\n", - " users = train.select(COL_USER).distinct()\n", - " items = train.select(COL_ITEM).distinct()\n", - " user_item = users.crossJoin(items)\n", - " dfs_pred = model.transform(user_item)\n", - "\n", - " # Remove seen items.\n", - " dfs_pred_exclude_train = dfs_pred.alias(\"pred\").join(\n", - " train.alias(\"train\"),\n", - " (dfs_pred[COL_USER] == train[COL_USER]) & (dfs_pred[COL_ITEM] == train[COL_ITEM]),\n", - " how='outer'\n", - " )\n", - "\n", - " top_all = dfs_pred_exclude_train.filter(dfs_pred_exclude_train[f\"train.{COL_RATING}\"].isNull()) \\\n", - " .select('pred.' + COL_USER, 'pred.' + COL_ITEM, 'pred.' + \"prediction\")\n", - "\n", - " # In Spark, transformations are lazy evaluation\n", - " # Use an action to force execute and measure the test time \n", - " top_all.cache().count()\n", - "\n", - "print(\"Took {} seconds for prediction.\".format(test_time.interval))" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "+------+-------+----------+\n", - "|UserId|MovieId|prediction|\n", - "+------+-------+----------+\n", - "| 1| 587| 4.1602826|\n", - "| 1| 869| 2.7732863|\n", - "| 1| 1208| 2.033383|\n", - "| 1| 1348| 1.0019257|\n", - "| 1| 1357| 0.9430026|\n", - "| 1| 1677| 2.8777318|\n", - "| 2| 80| 2.351385|\n", - "| 2| 472| 2.5865319|\n", - "| 2| 582| 3.9548612|\n", - "| 2| 838| 0.9482963|\n", - "| 2| 975| 3.1133535|\n", - "| 2| 1260| 1.9871743|\n", - "| 2| 1325| 1.2368056|\n", - "| 2| 1381| 3.5477588|\n", - "| 2| 1530| 2.08829|\n", - "| 3| 22| 3.1524537|\n", - "| 3| 57| 3.6980162|\n", - "| 3| 89| 3.9733813|\n", - "| 3| 367| 3.6629045|\n", - "| 3| 1091| 0.9144474|\n", - "+------+-------+----------+\n", - "only showing top 20 rows\n", - "\n" - ] - } - ], - "source": [ - "top_all.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 4. Evaluate how well ALS performs" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - " \r" - ] - } - ], - "source": [ - "rank_eval = SparkRankingEvaluation(test, top_all, k = TOP_K, col_user=COL_USER, col_item=COL_ITEM, \n", - " col_rating=COL_RATING, col_prediction=\"prediction\", \n", - " relevancy_method=\"top_k\")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[Stage 463:> (0 + 2) / 2]\r" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model:\tALS\n", - "Top K:\t10\n", - "MAP:\t0.006527\n", - "NDCG:\t0.051718\n", - "Precision@K:\t0.051274\n", - "Recall@K:\t0.018840\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r\n", - " \r" - ] - } - ], - "source": [ - "print(\"Model:\\tALS\",\n", - " \"Top K:\\t%d\" % rank_eval.k,\n", - " \"MAP:\\t%f\" % rank_eval.map_at_k(),\n", - " \"NDCG:\\t%f\" % rank_eval.ndcg_at_k(),\n", - " \"Precision@K:\\t%f\" % rank_eval.precision_at_k(),\n", - " \"Recall@K:\\t%f\" % rank_eval.recall_at_k(), sep='\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5. Evaluate rating prediction" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[Stage 500:=============================================> (171 + 3) / 200]\r" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "+------+-------+------+---------+----------+\n", - "|UserId|MovieId|Rating|Timestamp|prediction|\n", - "+------+-------+------+---------+----------+\n", - "| 580| 148| 4.0|884125773| 3.4059548|\n", - "| 406| 148| 3.0|879540276| 2.7134619|\n", - "| 916| 148| 2.0|880843892| 2.2241986|\n", - "| 663| 148| 4.0|889492989| 2.714362|\n", - "| 330| 148| 4.0|876544781| 4.52321|\n", - "| 935| 148| 4.0|884472892| 4.3838587|\n", - "| 308| 148| 3.0|887740788| 2.6169493|\n", - "| 20| 148| 5.0|879668713| 4.3721194|\n", - "| 923| 148| 4.0|880387474| 3.9818575|\n", - "| 455| 148| 3.0|879110346| 3.0764186|\n", - "| 15| 148| 3.0|879456049| 2.9913845|\n", - "| 374| 148| 4.0|880392992| 3.2223384|\n", - "| 880| 148| 2.0|880167030| 2.8111982|\n", - "| 677| 148| 4.0|889399265| 3.8451843|\n", - "| 49| 148| 1.0|888068195| 1.3751594|\n", - "| 244| 148| 2.0|880605071| 2.6781514|\n", - "| 84| 148| 4.0|883452274| 3.6721768|\n", - "| 627| 148| 3.0|879530463| 2.6362069|\n", - "| 434| 148| 3.0|886724797| 3.0973828|\n", - "| 793| 148| 4.0|875104498| 2.2886577|\n", - "+------+-------+------+---------+----------+\n", - "only showing top 20 rows\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r\n", - "[Stage 500:=================================================> (186 + 3) / 200]\r\n", - "\r\n", - " \r" - ] - } - ], - "source": [ - "# Generate predicted ratings.\n", - "prediction = model.transform(test)\n", - "prediction.cache().show()\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[Stage 775:==============================================> (174 + 2) / 200]\r" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model:\tALS rating prediction\n", - "RMSE:\t0.967434\n", - "MAE:\t0.753340\n", - "Explained variance:\t0.265916\n", - "R squared:\t0.259532\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r\n", - " \r" - ] - } - ], - "source": [ - "rating_eval = SparkRatingEvaluation(test, prediction, col_user=COL_USER, col_item=COL_ITEM, \n", - " col_rating=COL_RATING, col_prediction=\"prediction\")\n", - "\n", - "print(\"Model:\\tALS rating prediction\",\n", - " \"RMSE:\\t%f\" % rating_eval.rmse(),\n", - " \"MAE:\\t%f\" % rating_eval.mae(),\n", - " \"Explained variance:\\t%f\" % rating_eval.exp_var(),\n", - " \"R squared:\\t%f\" % rating_eval.rsquared(), sep='\\n')" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.006527288768086336, - "encoder": "json", - "name": "map", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "map" - } - }, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - " \r" - ] - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.051717802220247217, - "encoder": "json", - "name": "ndcg", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "ndcg" - } - }, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - " \r" - ] - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.05127388535031851, - "encoder": "json", - "name": "precision", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "precision" - } - }, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r\n", - "[Stage 904:> (0 + 2) / 2]\r\n", - "\r\n", - " \r" - ] - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.018840283525491316, - "encoder": "json", - "name": "recall", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "recall" - } - }, - "output_type": "display_data" - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.9674342234414528, - "encoder": "json", - "name": "rmse", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "rmse" - } - }, - "output_type": "display_data" - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.7533395161385739, - "encoder": "json", - "name": "mae", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "mae" - } - }, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - " \r" - ] - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.2659161968930053, - "encoder": "json", - "name": "exp_var", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "exp_var" - } - }, - "output_type": "display_data" - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 0.2595322728476255, - "encoder": "json", - "name": "rsquared", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "rsquared" - } - }, - "output_type": "display_data" - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 7.5410127229988575, - "encoder": "json", - "name": "train_time", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "train_time" - } - }, - "output_type": "display_data" - }, - { - "data": { - "application/scrapbook.scrap.json+json": { - "data": 25.246142672998758, - "encoder": "json", - "name": "test_time", - "version": 1 - } - }, - "metadata": { - "scrapbook": { - "data": true, - "display": false, - "name": "test_time" - } - }, - "output_type": "display_data" - } - ], - "source": [ - "if is_jupyter():\n", - " # Record results with papermill for tests\n", - " import scrapbook as sb\n", - " sb.glue(\"map\", rank_eval.map_at_k())\n", - " sb.glue(\"ndcg\", rank_eval.ndcg_at_k())\n", - " sb.glue(\"precision\", rank_eval.precision_at_k())\n", - " sb.glue(\"recall\", rank_eval.recall_at_k())\n", - " sb.glue(\"rmse\", rating_eval.rmse())\n", - " sb.glue(\"mae\", rating_eval.mae())\n", - " sb.glue(\"exp_var\", rating_eval.exp_var())\n", - " sb.glue(\"rsquared\", rating_eval.rsquared())\n", - " sb.glue(\"train_time\", train_time.interval)\n", - " sb.glue(\"test_time\", test_time.interval)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "# cleanup spark instance\n", - "spark.stop()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python (reco)", - "language": "python", - "name": "reco" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.0" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} + "nbformat": 4, + "nbformat_minor": 1 +} \ No newline at end of file diff --git a/examples/00_quick_start/dkn_MIND.ipynb b/examples/00_quick_start/dkn_MIND.ipynb index d37bb2cd3..6ef941e3a 100644 --- a/examples/00_quick_start/dkn_MIND.ipynb +++ b/examples/00_quick_start/dkn_MIND.ipynb @@ -345,7 +345,7 @@ "metadata": {}, "outputs": [], "source": [ - "sb.glue(\"res\", res)" + "store_metadata(\"res\", res)" ] }, { diff --git a/examples/00_quick_start/fastai_movielens.ipynb b/examples/00_quick_start/fastai_movielens.ipynb index 973906d51..fd64b1f03 100644 --- a/examples/00_quick_start/fastai_movielens.ipynb +++ b/examples/00_quick_start/fastai_movielens.ipynb @@ -915,16 +915,16 @@ ], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)\n", - "sb.glue(\"rmse\", eval_rmse)\n", - "sb.glue(\"mae\", eval_mae)\n", - "sb.glue(\"exp_var\", eval_exp_var)\n", - "sb.glue(\"rsquared\", eval_r2)\n", - "sb.glue(\"train_time\", train_time.interval)\n", - "sb.glue(\"test_time\", test_time.interval)" + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)\n", + "store_metadata(\"rmse\", eval_rmse)\n", + "store_metadata(\"mae\", eval_mae)\n", + "store_metadata(\"exp_var\", eval_exp_var)\n", + "store_metadata(\"rsquared\", eval_r2)\n", + "store_metadata(\"train_time\", train_time.interval)\n", + "store_metadata(\"test_time\", test_time.interval)" ] }, { @@ -964,4 +964,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/geoimc_movielens.ipynb b/examples/00_quick_start/geoimc_movielens.ipynb index 1420df43b..f8518db12 100644 --- a/examples/00_quick_start/geoimc_movielens.ipynb +++ b/examples/00_quick_start/geoimc_movielens.ipynb @@ -1,329 +1,329 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Geometry Aware Inductive Matrix Completion (GeoIMC)\n", - "\n", - "GeoIMC is an inductive matrix completion algorithm based on the works by Jawanpuria et al. (2019)\n", - "\n", - "Consider the case of MovieLens-100K (ML100K), Let $X \\in R^{m \\times d_1}, Z \\in R^{n \\times d_2} $ be the features of users and movies respectively. Let $M \\in R^{m \\times n}$, be the partially observed ratings matrix. GeoIMC models this matrix as $M = XUBV^TZ^T$, where $U \\in R^{d_1 \\times k}, V \\in R^{d_2 \\times k}, B \\in R^{k \\times k}$ are Orthogonal, Orthogonal, Symmetric Positive-Definite matrices respectively. This Optimization problem is solved by using Pymanopt.\n", - "\n", - "\n", - "This notebook provides an example of how to utilize and evaluate GeoIMC implementation in **recommenders**\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import tempfile\n", - "import zipfile\n", - "import pandas as pd\n", - "import numpy as np\n", - "import papermill as pm\n", - "import scrapbook as sb\n", - "\n", - "from recommenders.datasets import movielens\n", - "from recommenders.models.geoimc.geoimc_data import ML_100K\n", - "from recommenders.models.geoimc.geoimc_algorithm import IMCProblem\n", - "from recommenders.models.geoimc.geoimc_predict import Inferer\n", - "from recommenders.evaluation.python_evaluation import (\n", - " rmse, mae\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Choose the MovieLens dataset\n", - "MOVIELENS_DATA_SIZE = '100k'\n", - "# Normalize user, item features\n", - "normalize = True\n", - "# Rank (k) of the model\n", - "rank = 300\n", - "# Regularization parameter\n", - "regularizer = 1e-3\n", - "\n", - "# Parameters for algorithm convergence\n", - "max_iters = 150000\n", - "max_time = 1000\n", - "verbosity = 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Download ML100K dataset and features" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 4.81k/4.81k [00:09<00:00, 519KB/s]\n" - ] - } - ], - "source": [ - "# Create a directory to download ML100K\n", - "dp = tempfile.mkdtemp(suffix='-geoimc')\n", - "movielens.download_movielens(MOVIELENS_DATA_SIZE, f\"{dp}/ml-100k.zip\")\n", - "with zipfile.ZipFile(f\"{dp}/ml-100k.zip\", 'r') as z:\n", - " z.extractall(dp)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Load the dataset using the example features provided in helpers\n", - "\n", - "The features were generated using the same method as the work by Xin Dong et al. (2017)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "dataset = ML_100K(\n", - " normalize=normalize,\n", - " target_transform='binarize'\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "dataset.load_data(f\"{dp}/ml-100k/\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Characteristics:\n", - "\n", - " target: (943, 1682)\n", - " entities: (943, 1822), (1682, 1925)\n", - "\n", - " training: (80000,)\n", - " training_entities: (943, 1822), (1682, 1925)\n", - "\n", - " testing: (20000,)\n", - " test_entities: (943, 1822), (1682, 1925)\n", - "\n" - ] - } - ], - "source": [ - "print(f\"\"\"Characteristics:\n", - "\n", - " target: {dataset.training_data.data.shape}\n", - " entities: {dataset.entities[0].shape}, {dataset.entities[1].shape}\n", - "\n", - " training: {dataset.training_data.get_data().data.shape}\n", - " training_entities: {dataset.training_data.get_entity(\"row\").shape}, {dataset.training_data.get_entity(\"col\").shape}\n", - "\n", - " testing: {dataset.test_data.get_data().data.shape}\n", - " test_entities: {dataset.test_data.get_entity(\"row\").shape}, {dataset.test_data.get_entity(\"col\").shape}\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Initialize the IMC problem" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "np.random.seed(10)\n", - "prblm = IMCProblem(\n", - " dataset.training_data,\n", - " lambda1=regularizer,\n", - " rank=rank\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Optimizing...\n", - "Terminated - max time reached after 1753 iterations.\n", - "\n" - ] - } - ], - "source": [ - "# Solve the Optimization problem\n", - "prblm.solve(\n", - " max_time,\n", - " max_iters,\n", - " verbosity\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "# Initialize an inferer\n", - "inferer = Inferer(\n", - " method='dot'\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# Predict using the parametrized matrices\n", - "predictions = inferer.infer(\n", - " dataset.test_data,\n", - " prblm.W\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "# Prepare the test, predicted dataframes\n", - "user_ids = dataset.test_data.get_data().tocoo().row\n", - "item_ids = dataset.test_data.get_data().tocoo().col\n", - "test_df = pd.DataFrame(\n", - " data={\n", - " \"userID\": user_ids,\n", - " \"itemID\": item_ids,\n", - " \"rating\": dataset.test_data.get_data().data\n", - " }\n", - ")\n", - "predictions_df = pd.DataFrame(\n", - " data={\n", - " \"userID\": user_ids,\n", - " \"itemID\": item_ids,\n", - " \"prediction\": [predictions[uid, iid] for uid, iid in list(zip(user_ids, item_ids))]\n", - " }\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "RMSE: 0.496351244012414\n", - "MAE: 0.47524594431584\n", - "\n" - ] - } - ], - "source": [ - "# Calculate RMSE\n", - "RMSE = rmse(\n", - " test_df,\n", - " predictions_df\n", - ")\n", - "# Calculate MAE\n", - "MAE = mae(\n", - " test_df,\n", - " predictions_df\n", - ")\n", - "print(f\"\"\"\n", - "RMSE: {RMSE}\n", - "MAE: {MAE}\n", - "\"\"\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sb.glue(\"rmse\", RMSE)\n", - "sb.glue(\"mae\", MAE)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "[1] Pratik Jawanpuria, Arjun Balgovind, Anoop Kunchukuttan, Bamdev Mishra. _[Learning Multilingual Word Embeddings in Latent Metric Space: A Geometric Approach](https://www.mitpressjournals.org/doi/full/10.1162/tacl_a_00257)_. Transaction of the Association for Computational Linguistics (TACL), Volume 7, p.107-120, 2019.\n", - "\n", - "[2] Xin Dong, Lei Yu, Zhonghuo Wu, Yuxia Sun, Lingfeng Yuan, Fangxi Zhang. [A Hybrid Collaborative Filtering Model withDeep Structure for Recommender Systems](https://aaai.org/ocs/index.php/AAAI/AAAI17/paper/view/14676/13916).\n", - "Proceedings of the Thirty-First AAAI Conference on Artificial Intelligence (AAAI-17), p.1309-1315, 2017." - ] - } - ], - "metadata": { - "celltoolbar": "Tags", - "kernelspec": { - "display_name": "Python (reco)", - "language": "python", - "name": "reco_base" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.10" - } - }, - "nbformat": 4, - "nbformat_minor": 4 + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Geometry Aware Inductive Matrix Completion (GeoIMC)\n", + "\n", + "GeoIMC is an inductive matrix completion algorithm based on the works by Jawanpuria et al. (2019)\n", + "\n", + "Consider the case of MovieLens-100K (ML100K), Let $X \\in R^{m \\times d_1}, Z \\in R^{n \\times d_2} $ be the features of users and movies respectively. Let $M \\in R^{m \\times n}$, be the partially observed ratings matrix. GeoIMC models this matrix as $M = XUBV^TZ^T$, where $U \\in R^{d_1 \\times k}, V \\in R^{d_2 \\times k}, B \\in R^{k \\times k}$ are Orthogonal, Orthogonal, Symmetric Positive-Definite matrices respectively. This Optimization problem is solved by using Pymanopt.\n", + "\n", + "\n", + "This notebook provides an example of how to utilize and evaluate GeoIMC implementation in **recommenders**\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import tempfile\n", + "import zipfile\n", + "import pandas as pd\n", + "import numpy as np\n", + "import papermill as pm\n", + "import scrapbook as sb\n", + "\n", + "from recommenders.datasets import movielens\n", + "from recommenders.models.geoimc.geoimc_data import ML_100K\n", + "from recommenders.models.geoimc.geoimc_algorithm import IMCProblem\n", + "from recommenders.models.geoimc.geoimc_predict import Inferer\n", + "from recommenders.evaluation.python_evaluation import (\n", + " rmse, mae\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Choose the MovieLens dataset\n", + "MOVIELENS_DATA_SIZE = '100k'\n", + "# Normalize user, item features\n", + "normalize = True\n", + "# Rank (k) of the model\n", + "rank = 300\n", + "# Regularization parameter\n", + "regularizer = 1e-3\n", + "\n", + "# Parameters for algorithm convergence\n", + "max_iters = 150000\n", + "max_time = 1000\n", + "verbosity = 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Download ML100K dataset and features" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 4.81k/4.81k [00:09<00:00, 519KB/s]\n" + ] + } + ], + "source": [ + "# Create a directory to download ML100K\n", + "dp = tempfile.mkdtemp(suffix='-geoimc')\n", + "movielens.download_movielens(MOVIELENS_DATA_SIZE, f\"{dp}/ml-100k.zip\")\n", + "with zipfile.ZipFile(f\"{dp}/ml-100k.zip\", 'r') as z:\n", + " z.extractall(dp)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load the dataset using the example features provided in helpers\n", + "\n", + "The features were generated using the same method as the work by Xin Dong et al. (2017)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = ML_100K(\n", + " normalize=normalize,\n", + " target_transform='binarize'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "dataset.load_data(f\"{dp}/ml-100k/\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Characteristics:\n", + "\n", + " target: (943, 1682)\n", + " entities: (943, 1822), (1682, 1925)\n", + "\n", + " training: (80000,)\n", + " training_entities: (943, 1822), (1682, 1925)\n", + "\n", + " testing: (20000,)\n", + " test_entities: (943, 1822), (1682, 1925)\n", + "\n" + ] + } + ], + "source": [ + "print(f\"\"\"Characteristics:\n", + "\n", + " target: {dataset.training_data.data.shape}\n", + " entities: {dataset.entities[0].shape}, {dataset.entities[1].shape}\n", + "\n", + " training: {dataset.training_data.get_data().data.shape}\n", + " training_entities: {dataset.training_data.get_entity(\"row\").shape}, {dataset.training_data.get_entity(\"col\").shape}\n", + "\n", + " testing: {dataset.test_data.get_data().data.shape}\n", + " test_entities: {dataset.test_data.get_entity(\"row\").shape}, {dataset.test_data.get_entity(\"col\").shape}\n", + "\"\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize the IMC problem" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "np.random.seed(10)\n", + "prblm = IMCProblem(\n", + " dataset.training_data,\n", + " lambda1=regularizer,\n", + " rank=rank\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimizing...\n", + "Terminated - max time reached after 1753 iterations.\n", + "\n" + ] + } + ], + "source": [ + "# Solve the Optimization problem\n", + "prblm.solve(\n", + " max_time,\n", + " max_iters,\n", + " verbosity\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize an inferer\n", + "inferer = Inferer(\n", + " method='dot'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# Predict using the parametrized matrices\n", + "predictions = inferer.infer(\n", + " dataset.test_data,\n", + " prblm.W\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# Prepare the test, predicted dataframes\n", + "user_ids = dataset.test_data.get_data().tocoo().row\n", + "item_ids = dataset.test_data.get_data().tocoo().col\n", + "test_df = pd.DataFrame(\n", + " data={\n", + " \"userID\": user_ids,\n", + " \"itemID\": item_ids,\n", + " \"rating\": dataset.test_data.get_data().data\n", + " }\n", + ")\n", + "predictions_df = pd.DataFrame(\n", + " data={\n", + " \"userID\": user_ids,\n", + " \"itemID\": item_ids,\n", + " \"prediction\": [predictions[uid, iid] for uid, iid in list(zip(user_ids, item_ids))]\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RMSE: 0.496351244012414\n", + "MAE: 0.47524594431584\n", + "\n" + ] + } + ], + "source": [ + "# Calculate RMSE\n", + "RMSE = rmse(\n", + " test_df,\n", + " predictions_df\n", + ")\n", + "# Calculate MAE\n", + "MAE = mae(\n", + " test_df,\n", + " predictions_df\n", + ")\n", + "print(f\"\"\"\n", + "RMSE: {RMSE}\n", + "MAE: {MAE}\n", + "\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "store_metadata(\"rmse\", RMSE)\n", + "store_metadata(\"mae\", MAE)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "[1] Pratik Jawanpuria, Arjun Balgovind, Anoop Kunchukuttan, Bamdev Mishra. _[Learning Multilingual Word Embeddings in Latent Metric Space: A Geometric Approach](https://www.mitpressjournals.org/doi/full/10.1162/tacl_a_00257)_. Transaction of the Association for Computational Linguistics (TACL), Volume 7, p.107-120, 2019.\n", + "\n", + "[2] Xin Dong, Lei Yu, Zhonghuo Wu, Yuxia Sun, Lingfeng Yuan, Fangxi Zhang. [A Hybrid Collaborative Filtering Model withDeep Structure for Recommender Systems](https://aaai.org/ocs/index.php/AAAI/AAAI17/paper/view/14676/13916).\n", + "Proceedings of the Thirty-First AAAI Conference on Artificial Intelligence (AAAI-17), p.1309-1315, 2017." + ] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python (reco)", + "language": "python", + "name": "reco_base" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } \ No newline at end of file diff --git a/examples/00_quick_start/ncf_movielens.ipynb b/examples/00_quick_start/ncf_movielens.ipynb index 4d669e92b..23142e458 100644 --- a/examples/00_quick_start/ncf_movielens.ipynb +++ b/examples/00_quick_start/ncf_movielens.ipynb @@ -60,6 +60,7 @@ "from recommenders.datasets.python_splitters import python_chrono_split\n", "from recommenders.evaluation.python_evaluation import (rmse, mae, rsquared, exp_var, map_at_k, ndcg_at_k, precision_at_k, \n", " recall_at_k, get_top_k_items)\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Pandas version: {}\".format(pd.__version__))\n", @@ -350,16 +351,13 @@ "metadata": {}, "outputs": [], "source": [ - "if is_jupyter():\n", - " # Record results with papermill for tests\n", - " import papermill as pm\n", - " import scrapbook as sb\n", - " sb.glue(\"map\", eval_map)\n", - " sb.glue(\"ndcg\", eval_ndcg)\n", - " sb.glue(\"precision\", eval_precision)\n", - " sb.glue(\"recall\", eval_recall)\n", - " sb.glue(\"train_time\", train_time.interval)\n", - " sb.glue(\"test_time\", test_time.interval)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)\n", + "store_metadata(\"train_time\", train_time.interval)\n", + "store_metadata(\"test_time\", test_time.interval)" ] }, { @@ -392,4 +390,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/rbm_movielens.ipynb b/examples/00_quick_start/rbm_movielens.ipynb index d76fa764c..70df21ac3 100644 --- a/examples/00_quick_start/rbm_movielens.ipynb +++ b/examples/00_quick_start/rbm_movielens.ipynb @@ -779,10 +779,10 @@ ], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"map\", eval_100k['MAP'][0])\n", - "sb.glue(\"ndcg\", eval_100k['nDCG@k'][0])\n", - "sb.glue(\"precision\", eval_100k['Precision@k'][0])\n", - "sb.glue(\"recall\", eval_100k['Recall@k'][0])" + "store_metadata(\"map\", eval_100k['MAP'][0])\n", + "store_metadata(\"ndcg\", eval_100k['nDCG@k'][0])\n", + "store_metadata(\"precision\", eval_100k['Precision@k'][0])\n", + "store_metadata(\"recall\", eval_100k['Recall@k'][0])" ] }, { @@ -869,4 +869,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/sar_movielens.ipynb b/examples/00_quick_start/sar_movielens.ipynb index f5922a861..9fe09c6ad 100644 --- a/examples/00_quick_start/sar_movielens.ipynb +++ b/examples/00_quick_start/sar_movielens.ipynb @@ -773,12 +773,12 @@ "outputs": [], "source": [ "# Record results with papermill for tests - ignore this cell\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)\n", - "sb.glue(\"train_time\", train_time.interval)\n", - "sb.glue(\"test_time\", test_time.interval)" + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)\n", + "store_metadata(\"train_time\", train_time.interval)\n", + "store_metadata(\"test_time\", test_time.interval)" ] } ], diff --git a/examples/00_quick_start/wide_deep_movielens.ipynb b/examples/00_quick_start/wide_deep_movielens.ipynb index 201a0bc36..bacac0122 100644 --- a/examples/00_quick_start/wide_deep_movielens.ipynb +++ b/examples/00_quick_start/wide_deep_movielens.ipynb @@ -951,7 +951,7 @@ }, { "data": { - "image/png": "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b0BSLSkpkdPp1LRp0/xjzve54+PjNXv27IAxPp9Pb775pmX35lw+n09tbW1RfU/mz5+vvXv3qqKiwr9cddVVWrZsmf/P0XpvztXc3Kw//elPysrKiup/ZjD6kUPJoYNBDiWHBiPic2hQr+IYhZqamszy8nKzvLzclGT+53/+p1leXm4eOXLENM3uV+G6XC7zlVdeMffs2WMuXry431fh5uXlme+995759ttvm5dffnnAq3Dr6+vNjIwM87bbbjP37dtnrl271kxKSurzKtzY2FjzP/7jP8wPPvjAfPDBBy19Fe7f/d3fmampqWZpaWnAKzxPnTrlH3P33XebHo/H3LJli7lr1y4zPz/fzM/P9+/veYXnDTfcYFZUVJibNm0yx40b1+8rPO+//37zgw8+MB9//PF+X+HpcDjM4uJi88CBA+b//t//23S5XAFvqBkp//iP/2hu27bNrKysNPfs2WP+4z/+o2mz2czNmzebphmd92Qgvd/IZJrRe2/uu+8+s7S01KysrDTfeecdc8GCBebYsWPNEydOmKYZvfcFowM5tH/k0P6RQwePHNpttOXQqC+8tm7dakrqs9x+++2maXa/Dvf//t//a2ZkZJgOh8OcP3++efDgwYBzfP755+bSpUvN5ORk0+l0mnfccYfZ1NQUMOb99983/+zP/sx0OBzm+PHjzYceeqhPLC+++KJ5xRVXmPHx8eb06dPN1157LWSf+0L6uyeSzN/+9rf+MadPnzb/z//5P6bb7TaTkpLMv/zLvzRramoCzvPJJ5+YixYtMhMTE82xY8ea9913n9nR0REwZuvWreaVV15pxsfHm5dddlnANXo8+uijpsfjMePj481rrrnG3LlzZyg+9gX97d/+ren1es34+Hhz3Lhx5vz58/0JwzSj854M5NykEa335lvf+paZlZVlxsfHm+PHjze/9a1vmYcOHfLvj9b7gtGBHNo/cmj/yKGDRw7tNtpyqM00TTO438gAAAAAAMFgjhcAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhBiFFwAAAACEGIUXEALf+c53tGTJEqvDAAAg4pBDMVpReAEAAABAiFF4ARdh3bp1mjlzphITE3XJJZdowYIFuv/++/X//t//0yuvvCKbzSabzabS0lJJUnV1tb75zW/K5XIpLS1Nixcv1ieffOI/X8+3fD/72c80btw4OZ1O3X333Wpvbz/vNVtaWkb4kwMAcHHIoYg2sVYHAESqmpoaLV26VL/4xS/0l3/5l2pqatJbb72l5cuXq6qqSo2Njfrtb38rSUpLS1NHR4cWLlyo/Px8vfXWW4qNjdW//Mu/6MYbb9SePXsUHx8vSXrzzTeVkJCg0tJSffLJJ7rjjjt0ySWX6F//9V8HvKZpmlbeCgAAgkIORVQyAQzJ7t27TUnmJ5980mff7bffbi5evDhg23//93+bkydPNn0+n39bW1ubmZiYaL7xxhv+49LS0syWlhb/mDVr1pjJyclmV1fXea8JAECkIIciGvGoITBEubm5mj9/vmbOnKlbb71Vv/nNb3Ty5MkBx7///vs6dOiQUlJSlJycrOTkZKWlpam1tVV/+tOfAs6blJTkX8/Pz1dzc7Oqq6uDviYAAOGIHIpoROEFDFFMTIxKSkr0+uuva9q0aXr00Uc1efJkVVZW9ju+ublZs2fPVkVFRcDy0Ucf6W/+5m9Cck0AAMIRORTRiMILuAg2m01z587Vz372M5WXlys+Pl7r169XfHy8urq6AsYahqGPP/5Y6enpmjRpUsCSmprqH/f+++/r9OnT/vWdO3cqOTlZOTk5570mAACRhByKaEPhBQzRe++9p3/7t3/Trl27VFVVpZdfflmffvqppk6dqksvvVR79uzRwYMH9dlnn6mjo0PLli3T2LFjtXjxYr311luqrKxUaWmpvv/97+vo0aP+87a3t+vOO+/UgQMHtHHjRj344INasWKF7Hb7ea8JAECkIIciGvFWQ2CInE6ntm/frocffliNjY3yer1atWqVFi1apKuuukqlpaW66qqr1NzcrK1bt2revHnavn27fvjDH+rrX/+6mpqaNH78eM2fP19Op9N/3vnz5+vyyy/Xddddp7a2Ni1dulQ//elPL3hNAAAiBTkU0chmmrxDEwgX3/nOd1RfX68NGzZYHQoAABGFHIpwx6OGAAAAABBiFF4AAAAAEGI8aggAAAAAIcYvXgAAAAAQYhReAAAAABBiFF4AAAAAEGIUXgAAAAAQYhReAAAAABBiFF4AAAAAEGIUXgAAAAAQYhReAAAAABBiFF4AAAAAEGL/H6tugObc1Ge8AAAAAElFTkSuQmCC\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA14AAANBCAYAAADwWG+8AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAADHwklEQVR4nOzdeVxU9f4/8NcMywwIwyICsqsYirKriFpWUpimkn29ZN4wM7uapsa93i7+SvNWF2+Z6VXTtGw3zUorM9JQXHFjQMR9ZVBZRGfYVLY5vz+MKXJQBmc4M8Pr+XjM48Gc+cyZ95x8zLv3OZ/zeUsEQRBAREREREREJiMVOwAiIiIiIiJrx8KLiIiIiIjIxFh4ERERERERmRgLLyIiIiIiIhNj4UVERERERGRiLLyIiIiIiIhMjIUXERERERGRibHwIiIiIiIiMjFbsQMwR1qtFpcvX4azszMkEonY4RAR3TNBEFBZWQkfHx9IpTznRqbDHEpE1sZYOZSFlx6XL1+Gv7+/2GEQERldYWEh/Pz8xA6DrBhzKBFZq3vNoSy89HB2dgZw6+AqFAqRoyEiuncVFRXw9/fX/b4RmQpzKBFZG2PlULMovJYtW4Z33nkHxcXFiIiIwJIlS9CvXz+9Y48ePYo5c+YgOzsbBQUFeO+99zBz5sxm9z1//nykpqZixowZWLRoUYviaZwaoVAomDSIyKpw6heZGnMoEVmre82hok/0X7duHVJSUjB37lwolUpEREQgISEBpaWlesdfv34dXbt2xfz58+Ht7X3HfR88eBAffPABwsPDTRE6ERERERFRi4heeC1cuBCTJk3ChAkTEBoaihUrVsDR0RGrV6/WO75v375455138NRTT0EmkzW736qqKowbNw6rVq2Cm5ubqcInIiIiIiK6K1ELr9raWmRnZyM+Pl63TSqVIj4+HllZWfe076lTp2L48OFN9t2cmpoaVFRUNHkQEREREREZi6iFV1lZGRoaGuDl5dVku5eXF4qLi1u937Vr10KpVCItLa1F49PS0uDi4qJ7cDUmIiIiIiIyJtGnGhpbYWEhZsyYgS+//BJyubxF70lNTUV5ebnuUVhYaOIoiYiIiIioPRF1VUMPDw/Y2NigpKSkyfaSkpK7LpzRnOzsbJSWliI6Olq3raGhATt37sTSpUtRU1MDGxubJu+RyWR3vF+MiIiIiIjoXoh6xcve3h4xMTHIyMjQbdNqtcjIyEBcXFyr9jlkyBAcOXIEubm5ukefPn0wbtw45Obm3lZ0ERERERERmZrofbxSUlIwfvx49OnTB/369cOiRYtQXV2NCRMmAACSk5Ph6+uru1+rtrYWx44d0/196dIl5ObmwsnJCcHBwXB2dkbv3r2bfEaHDh3QsWPH27YTERERERG1BdELr6SkJFy5cgVz5sxBcXExIiMjkZ6erltwQ6VSQSr9/cLc5cuXERUVpXu+YMECLFiwAIMHD0ZmZmZbh09ERERERHRXEkEQBLGDMDcVFRVwcXFBeXk5FAqF2OEQEd0z/q5RW+G/NSKyNsb6XbO6VQ2JiIiIiIjMDQsvahO8sEpERNQ6zKFE1oGFF5lcfYMWD7yzHbPWH8bNugaxwyEiIrIY9Q1a3P/2drz0VQ4qbtaJHQ4R3QPRF9cg63eiuBKF127govoiTpVWYeUzMfBStKy5NRERUXt2orgSF9U3UFJxE7mFaiwZG41If1exwyKiVuAVLzK5HJUatlIJvv5bHEorbmLEkt1QqtRih0VERGT2clRq2NlIsHn6/XDvIMP/Ld+LD3achVbL6YdEloaFF5mcUqVBLx8F+ga54/tpA+Hv7oinPtiH9YcKxQ6NiIjIrClVGoR2VqC7lzPW/y0OEwd1QdrPJ/DsJwdRVlUjdnhEZAAWXmRySpUaUQFuAABPZznWTIrF6GhfzPomD/N+PIr6Bq3IERIREZmnP+ZQe1spUof1xKfP9cPRS+V4bPEu7DlTJnKERNRSLLzIpK5W1aDg6nVEBbjqtslsbZA2OgxvjOqFz7MKMP7jA1BX14oXJBERkRlqzKHRgW5Ntg++rxN+nnE/7vNywl8/2o93fjnBk5hEFoCFF5lUjkoDAIgOaJo0JBIJnokLwucTY3G8qBIjl+3GieIKESIkIiIyT405NErPYhqeCjk+fy4W/3g0BCt2nEPSyn24qL7etgESkUFYeJFJKVVqdHKWwc/NQe/rcd064vupA+Eks8Po9/ciPb+ojSMkIiIyT3fLoVKpBFMfCsbXf+uP4vKbGLZ4F/MokRlj4UUmlaPSIMrfFRKJpNkx/u6O+HZKHB4K8cTkL5R4b+sprtZERETtXo5Kg+iAO+dQAIgJdMfm6fdjYLAHJn+hxKsbj7BvJpEZYuFFJlPfoMXhi5rb5qbr42hvi6VPR2FWQgj+t+00Jn+Rjaqa+jaIkoiIyPw05tCogLvnUABwcbTD++Oi8WZib3x96CISl+3BmdJKE0dJRIZg4UUmc7KkEtdrG267v6s5EsmtKROrnumDvWevYvT7e1BwtdrEURIREZkfQ3MocCuP/rV/IH6YNhD1WgEjluzB1wcLIQicRUJkDlh4kckoVRrYSiUI83Ux6H3xoV7YOHUAauu1GLl0D3af5lK5RETUvjTm0HA/w3IoAPTwVuCHaQMxMsIH//w2DzPW5qLyZp0JoiQiQ7DwIpPJUanRs7MCDvY2Br832NMZ308dhAh/VySv3o+Pdp/nGTsiImo3clRqhPooILczPIcCt6bw//f/wvG/sVHYdqIUw/+3G4cLNcYNkogMwsKLTKbxpuDWcnG0w8fP9sWk+7vijU3H8I/1ebxZmIiI2oXGxanu1cgIH/w0fRDcHO3w5PK9WLXzHBewIhIJCy8yiWvVtThfVt2ihTXuxEYqQeqwnliUFIlNeZeRtHIfSipuGilKIiIi82OsHNoosGMHrJ88AM8N6oK3Nh/Hc58exNWqGqPsm4hajoUXmURuoRoAEOVvnKSRGOWL9ZPjUFpxEyOW7IZSpTbKfomIiMxNYw41ZGGNu7G3lWL2sJ74eEJfHLlYjscW78LeM7yHmqgtsfAik1AWaODhZA9/d/1NH1sj3M8V308bCH93Rzz1wT58fajQaPsmIrJUy5YtQ1BQEORyOWJjY3HgwIFmxx49ehRPPvkkgoKCIJFIsGjRotvGpKWloW/fvnB2doanpycSExNx8uRJE34D+rPGHNpc4+R78VCIJ36ecT+CPZ0w7qP9eHfLSdQ3aI3+OUR0OxZeZBJKlRpRAW53bfpoKE9nOdZMisXoaF/885s8zPvxKBMGEbVb69atQ0pKCubOnQulUomIiAgkJCSgtLRU7/jr16+ja9eumD9/Pry9vfWO2bFjB6ZOnYp9+/Zh69atqKurw6OPPorqarb3aCumyqGNPBVyfD4xFv94NATvZ57FUyv34ZLmhkk+i4h+x8KLjK5BK+BwoQZR97Cwxp3IbG2QNjoMb4zqhc+zCpC8+gDU1bUm+SwiInO2cOFCTJo0CRMmTEBoaChWrFgBR0dHrF69Wu/4vn374p133sFTTz0FmUymd0x6ejqeffZZ9OrVCxEREfjkk0+gUqmQnZ1tyq9Cv2nMocacZqiPjfRW78x1L/RHUflNDFu8C+n5xSb9TKL2joUXGd2pkkpUG9j00VASiQTPxAXh84mxOFFciZHLduNEcYXJPo+IyNzU1tYiOzsb8fHxum1SqRTx8fHIysoy2ueUl5cDANzd3fW+XlNTg4qKiiYPar3GHGqqk5d/1ifIHZun34/+Xd0x+YtszPk+nysIE5kICy8yOqVKDZtWNn00VFy3jvh+6kA4yeww+v29SM8vMvlnEhGZg7KyMjQ0NMDLy6vJdi8vLxQXG+fKhVarxcyZMzFw4ED07t1b75i0tDS4uLjoHv7+/kb57PaqLXNoIxdHO6z4awzeSOyNtQcLkbhsD86UVrXZ5xO1Fyy8yOhyVBr08HaGo71tm3yev7sjvp0Sh4dCPDH5CyXe23qKPUqIiIxg6tSpyM/Px9q1a5sdk5qaivLyct2jsJALH92LHJUGPTu3XQ5tJJFI8Ez/QHw/dSDqGrQYsWQ3vj5UCEFgPiUyFhZeZHRKldrkc9P/zNHeFkufjsKshBD8b9tpTP4iG1U19W0aAxFRW/Lw8ICNjQ1KSkqabC8pKWl24QxDTJs2DZs2bcL27dvh5+fX7DiZTAaFQtHkQa2nVKmN1oqlNXp2VuDHlwZhRERn/PObPMxcl4vKm3WixUNkTVh4kVFprtfi3JVqRAe6tvlnSyS3bhRe9Uwf7D17FaPf34OCq1yFi4isk729PWJiYpCRkaHbptVqkZGRgbi4uFbvVxAETJs2DRs2bMC2bdvQpUsXY4RLLSBmDv0jR3tbvP1/EVj8VCQyjpfi8SW7kXdRI2pMRNaAhRcZVU6hBoDxGie3RnyoFzZOHYDaei1GLt2D3afZIJKIrFNKSgpWrVqFTz/9FMePH8eUKVNQXV2NCRMmAACSk5ORmpqqG19bW4vc3Fzk5uaitrYWly5dQm5uLs6cOaMbM3XqVHzxxRdYs2YNnJ2dUVxcjOLiYty4weXGTa0xh7b1rJHmjIr0xU/TB8HFwQ5PLt+LD3ed49RDonvAwouMKqdADfcO9gjs6ChqHMGezvh+6iBE+LsiefV+fLT7PJMFEVmdpKQkLFiwAHPmzEFkZCRyc3ORnp6uW3BDpVKhqOj3RYcuX76MqKgoREVFoaioCAsWLEBUVBSef/553Zjly5ejvLwcDz74IDp37qx7rFu3rs2/X3vTmEMD3MXNoX8U2LEDvpk8AM8OCMKbPx3HxE8P4WpVjdhhEVkkicD/G71NRUUFXFxcUF5ezrnqBvrrh/sht5Piw/F9xQ4FwK1+KG+nn8AHO8/hyWg/vPVEb8jtbMQOi6jN8XeN2gr/rbWeueXQP9t+ohR/X38YdjYSLEqKQly3jmKHRNQmjPW7xiteZDQNWgG5hRpEmckUCeBWg8jUYT2xKCkSm/IuI2nlPhSX3xQ7LCIioibMMYf+2UM9PPHzjPvR1cMJT3+4Dwu3nER9g1bssIgsBgsvMpozpVWoqqlvs6aPhkiM8sX6yXEorbiJkUt3Q6lSix0SERGRjjnn0D/yUsjxxfOxSIm/D0u3n8HYVftwWcP7/4hagoUXGY1SpYZUAkT4uYodil7hfq74ftpA+Ls74qkP9uHrQ+w1Q0RE5sHcc+gf2UgleGlId6z7WxwuqW/gscW7sOWocZp2E1kzFl5kNMoCNXp4K9BB1rZNHw3h6SzHmkmxGB3ti39+k4d5Px7lNAkiIhKdJeTQP+sb5I7NM+5HbBd3vPB5NuZ+n4+bdQ1ih0Vktlh4kdHkFGrMfooEAMhsbZA2OgxvjOqFz7MKkLz6ANTVtWKHRURE7Zil5NA/c3W0xwfPxODfo3rhq4OFeOL9vTh7pUrssIjMEgsvMory63U4U1plNr1H7kYikeCZuCB8PjEWJ4orMXLZbpworhA7LCIiaocsLYf+mUQiQXJcEDa+OBA19Q0YsWQ3vsm+yDYuRH/CwouMIqfw1mIV0YGWlTTiunXE91MHwklmh9Hv70V6ftHd30RERGRElppD/yzUR4FNLw3C8LDO+Mf6w0j5+jCqaurFDovIbLDwIqPIUWng5miHIJEbJ7eGv7sjvp0Sh4dCPDH5CyXe23oKWi3P0hERUduw5Bz6Z472tnhnTAQWJUViy9FiPP6/Xci/VC52WERmgYUXGYVSpUZUgBskEonYobSKo70tlj4dhVkJIfjfttOY/EU2z9IREVGbsPQcqk9ilC9+mn4/nOV2eOL9PVi9+zynHlK7x8KL7pn2t6aP0RZ4U/AfSSQSTH0oGKue6YO9Z69i9Pt7UHC1WuywiIjIillLDtUnyKMDvp0yAOPjgvDvTcfw/KeHcI2LWVE7xsKL7tnZK1WovFmPKAu9KfjP4kO9sHHqANTWazFy6R7sOn1F7JCIiMhKNeZQS11Y427sbaV49fFQrH62D3IKNXhs8U7sO3dV7LCIRMHCi+6Zrumjv6vYoRhNsKczvp86CBH+rhi/+gA+3HWOUySIiMjoGnNouBXlUH0e7uGFzdPvRxePDnh61T68t/UU+2hSu2MWhdeyZcsQFBQEuVyO2NhYHDhwoNmxR48exZNPPomgoCBIJBIsWrTotjFpaWno27cvnJ2d4enpicTERJw8edKE36B9UxZocJ+XM5wsqOljS7g42uHjZ/ti0v1d8eZPx/H39YfZGJKIiIzKWnOoPt4ucnz5fH/MjL8PS7adxtMf7kdR+Q2xwyJqM6IXXuvWrUNKSgrmzp0LpVKJiIgIJCQkoLS0VO/469evo2vXrpg/fz68vb31jtmxYwemTp2Kffv2YevWrairq8Ojjz6K6mrer2MKOYVqq5lm+Gc2UglSh/XEoqRI/JRXhKSV+1BcflPssIiIyErkFKotfhl5Q9hIJZg+pDvWvhCHwmvX8djiXdh6rETssIjahOiF18KFCzFp0iRMmDABoaGhWLFiBRwdHbF69Wq94/v27Yt33nkHTz31FGQymd4x6enpePbZZ9GrVy9ERETgk08+gUqlQnZ2tim/SrtUcbMOp0urrPKm4D9KjPLF+slxKK24iZFLd0OpUosdEhERWbjGHBpl5dMM9enXxR2bp9+PvkHumPTZIbz+w1HU1HNWCVk3UQuv2tpaZGdnIz4+XrdNKpUiPj4eWVlZRvuc8vJb/SPc3d31vl5TU4OKioomD2qZXJUGgmD5TR9bItzPFd9PGwh/d0c89cE+fH2oUOyQiIjIgrWnHKqPWwd7rHwmBvNG9sKa/SqMfn8vzl2pEjssIpMRtfAqKytDQ0MDvLy8mmz38vJCcXGxUT5Dq9Vi5syZGDhwIHr37q13TFpaGlxcXHQPf39/o3x2e5Cj0sDFwQ5dOnYQO5Q24eksx5pJsRgd7Yt/fpOHeT8e5c3BRETUKo05tKtH+8ih+kgkEowfEIQNUwfgRm0DHl+yG98pL4odFpFJiD7V0NSmTp2K/Px8rF27ttkxqampKC8v1z0KC3klo6VuNX10hVRqPU0f70Zma4O00WF4Y1QvfJ5VgOTVB6BmXxIiIjJQYw61psbJrdXLxwU/vjQIj/XujJSvDyPl61xU19SLHRaRUYlaeHl4eMDGxgYlJU1vqiwpKWl24QxDTJs2DZs2bcL27dvh5+fX7DiZTAaFQtHkQXen1QrIUamttvfInUgkEjwTF4TPJ8biRHElRi7bjRPFnKJKREQt055zaHM6yGzx7l8i8F5SBH7JL8bjS3Yj/1K52GERGY2ohZe9vT1iYmKQkZGh26bVapGRkYG4uLhW71cQBEybNg0bNmzAtm3b0KVLF2OES39yrqwKFTfrEWXlC2vcSVy3jvh+6kA4yeww+v29SM8vEjskIiKyAI05lIXX7Z6I8sOm6fejg8wGo9/fi4/3nGcvTbIKok81TElJwapVq/Dpp5/i+PHjmDJlCqqrqzFhwgQAQHJyMlJTU3Xja2trkZubi9zcXNTW1uLSpUvIzc3FmTNndGOmTp2KL774AmvWrIGzszOKi4tRXFyMGzfYK8KYlCoNJBIgsh2uxvRH/u6O+HZKHB4K8cTkL5RYuPUUtFomCCIial5jDo3wdxE7FLPUxaMDvp0yAH/tH4h5Px7DpM8OcVo/WTzRu/UlJSXhypUrmDNnDoqLixEZGYn09HTdghsqlQpS6e/14eXLlxEVFaV7vmDBAixYsACDBw9GZmYmAGD58uUAgAcffLDJZ3388cd49tlnTfp92pMclRr3eTrDWW4ndiiic7S3xdKnoxCaqcCCLSdxvKgC7yVFtouGmEREZDjm0LuT2dpgzohQDAzuiH+sP4zHFu/C4qciEdu1o9ihEbWKROC129tUVFTAxcUF5eXlvN/rDhLe24noQFekjQ4XOxSz8uuxEsxclwsfVzlWJfdBYDtZ8ZHMG3/XqK3w31rLMIcaprj8JmaszcHBC9cwfUh3vPRwd9i0o4W9SFzG+l0TfaohWabKm3U4VVqJKH/OTf+z+FAvbJw6ALX1Woxcuge7Tl8ROyQiIjIjzKGG83aRY82k/pgx5D78L+M0nl61D0XlvIWELAsLL2qVw4XlvzV9dBU7FLMU7OmM76cOQoS/K8avPoAPd53jjcFERASAObS1bKQSzIjvjq8m9UfB1esYtngXNh8p4n3VZDFYeFGrKFVqKOS26OrhJHYoZsvF0Q4fP9sXk+7vijd/Oo6/rz+Mm3UNYodFREQiYw69N7FdO+LnGfcjJtAdL36pRPzCHfh07wVUse8XmTkWXtQqOSo1IgPc2lXj5NawkUqQOqwnFiVF4qe8IiSt3Ifi8ptih0VERCLKUakRxRx6T9w62GNVcgy+nRKHnj4K/HvTMcT9JwNvbDqGwmvXxQ6PSC8WXmQwQRCQU6hBdDvu32WoxChfrJ8ch9KKmxi5dDeUKrXYIRERkQgac2h77oFpLBKJBDGB7lj2dDR2/fMh/DUuEN9kX8Tgd7bjhc8OIevsVU7zJ7PCwosMdq6sGprrdWz6aKBwP1d8P20g/N0d8dQH+/D1oUKxQyIiojbGHGoaPq4OeGVoD+xLHYI3E8NwvqwaY1ftw2OLd+Hrg4Wc6k9mgYUXGSxHpQEARLTzxsmt4eksx5pJsRgd7Yt/fpOHeT8eRX2DVuywiIiojeT81jg5kle8TMLB3gZPxwZgy8sP4IuJsfB1dcA/v83DgPnbsOCXkyip4HR/Eg+7u5LBlCo1uns6wcWBTR9bQ2Zrg7TRYejlo8C8H48h72I5Zg/rgZhAd7FDIyIiE1Oq1Aju5AQFGyeblEQiwaDuHhjU3QPny6rx6d4L+HjPeazYcRbDwztjwsAuiOQJZGpjvOJFBlMWqDlF4h5JJBI8ExeEL5+PRXVNPZ5cnoVnPz6AIxfLxQ6NiIhMiDm07XXx6IDXR/ZC1uwhSB3WEzkqDRKX7cET7+/Bj4cvo44zT6iNsPAig1TV1ONUSSVvCjaS2K4dsXn6/VgyNgqqa9cxYulu/O3zQzhRXCF2aEREZGSNOZT9u8ShkNth4qAu2P6PB7HymRjIbW3w0lc5uP+/27Fs+xmoq2vFDpGsHKcakkHyCjXQCkB0IM/WGYtUKsGICB881tsb3+dexqKMU3hs8S48Hu6DmfHd0a0T+7wQEVmDxhwaxSteorKRSvBoL2882ssbx4sq8MmeC1iccRr/yziNJ6J8MWFgF4R4O4sdJlkhXvEigyhVajjLbBHMYsDobG2keDLGD9v+/iDeSgzDoQvX8MjCHfjH+sPsSUJEZAWUKjWc5cyh5qRnZwX++3/h2Jc6BNOHdMf2k6VIWLQT4z7ch1+PlUCr5XL0ZDy84kUGUao0iAxwZdNHE7KzkeLp2ACMjvbFVwdUWLb9LDbmXMJf+vrjpYeD0dnFQewQiYioFZQqDSL9mUPNkXsHe0x9KBgvPNAVm48U4eM9F/D8Z4cQ2NER4+OCMKaPH5y5IArdI17xohYTBAE5KjWnSLQRuZ0NJgzsgl3/fAizEkLw85EiDH4nE/N+PIrSSi6HS0RkSZhDLYOdjRSjIn2xcepAbHhxACL8XPGfzccRl7YNr/9wFBfKqsUOkSwYr3hRi124eh3q63WI5sIabcrB3gZ/G9wNT8cG4OM9F7Bq1zmsPVCI5AGBmPxAN7h1sBc7RCIiugvmUMsTFeCGqAA3zB7WE5/vu4A1+1X4NOsChvTwxISBXTCgW0dIJLx6SS3HK17UYsoCNQAgyp9n68TgLLfD9CHdsfufD2PioC74PKsA97+9HQu3nkL5jTqxwyMiojtgDrVc3i5yzErogazUIZg/OgwX1Tcw7sP9GLpoF746oMLNugaxQyQLwcKLWiynUI1unTrAxZFznMXk4miHfySEYNc/H8LYfv74YMdZPPD2raVwq2vqxQ6PiIj0YA61fHI7GyT1DcDPM+7Hmkmx8Hd3xOwNR9A/LQNvp59AUfkNsUMkM8ephtRiygINmz6akY5OMvy/4aGYdH9XLNt+Bot+PYXVu89jyoPd8Nf+gZDb2YgdIhER/YY51HpIJBIM6OaBAd08UHC1Gp/uLcBnWQX4YOc5PNbbGxMGdkF0gCunIdJteMWLWqS6ph4niivYv8sMeSrkmDeqN7b/40E8EuqFtJ9P4IG3t+OzrAuoqef0ByIisTGHWq/Ajh0wZ0Qo9s0egteG90T+pXI8uXwvEpftwfe5l1BbrxU7RDIjLLyoRfIulv/W9NFV7FCoGX5ujpj/ZDgyUgZjULAH5v5wFA8v2IF1B1Woa+APPxGRWJhDrZ+TzBbPDuyCbX9/EB+N7wNnuR1mrM3FoP9uw5KM07haVSN2iGQGWHhRiyhVajjJbNHdk53czV2QRwcsTIrElpkPINLfFa98ewTxC3dgQ85FNLARJBFRm2MObT+kUgmG9PTCF8/HYsvLD2BITy8s3X4GcfO3Ydb6wzh2uULsEElELLyoRXJUakT6u8KGTR8tRncvZywbF42fpg9Cd09nvLzuMBIW7cTmI0XQsgAjImozzKHt031ezkgbHYZ9qUMwM747dp8pw7D/7cJTK7Pwy9Fingxth1h40V3davqo4RQJC9XLxwUfju+DjVMHwsfVAS9+qcTwJbvx67ESCAJ/9Iks3bJlyxAUFAS5XI7Y2FgcOHCg2bFHjx7Fk08+iaCgIEgkEixatOie90l3xhxKbh3s8eKDwdj5z4ew9Oko1DUI+Nvn2XhwwXZ8uOscKm6yJUx7wcKL7kp17TquVtdyNSYLF+nvis+e64ev/xYHZ7ktnv/sEBLf34udp66wACOyUOvWrUNKSgrmzp0LpVKJiIgIJCQkoLS0VO/469evo2vXrpg/fz68vb2Nsk+6M+ZQamRnI8Xj4T74dsoAfD91IGIC3PDf9BPo/58MzP0+H+euVIkdIpkYCy+6K6XqVtPHSH9XcQMho+jXxR3rXuiPLybGQgIgefUBJH2wD/vPXRU7NCIy0MKFCzFp0iRMmDABoaGhWLFiBRwdHbF69Wq94/v27Yt33nkHTz31FGQymVH2SXfWmEN5xYv+KMLfFYueisKeVx7G84O6YFNeER5+dwcmfHyAJ0StGAsvuqsclQZdPTrArYO92KGQkUgkEgzq7oENLw7A6mf7oLq2Hkkr9+GZj/Yj57f/SSAi81ZbW4vs7GzEx8frtkmlUsTHxyMrK6vN9llTU4OKioomD/pdjkqDrp06wNWROZRu56mQI+XREOz518N45//CUVxRg+TVB/DIezvx5f4C3KhlWxhrwsKL7kqpUiOKUySskkQiwcM9vPDjtEFY8ddoFJffxBPv78XETw4i/1K52OER0R2UlZWhoaEBXl5eTbZ7eXmhuLi4zfaZlpYGFxcX3cPf379Vn22tlCo1ovyZQ+nO5HY2GNPHH5unD8LaF/qjW6cOeG1jPvqnZSDt5+O4pLkhdohkBCy86I6u19bjeFElogNdxQ6FTEgqlWBo785In/kAFj8VibNXqvD4kt148ctsnC6pFDs8IjJjqampKC8v1z0KCwvFDslsMIeSoSQSCfp37YgPnumDHbMewpgYP6zZr8IDb2/Hi19m4+CFa5yGaMFsxQ6AzNuRi+Vo0Ao8W9dO2EglGBXpi+FhnfGd8hIWZ5zGo4t2YlSED2bE34cuHh3EDpGIfuPh4QEbGxuUlJQ02V5SUtLswhmm2KdMJmv2frH2rjGHcmENag1/d0e8+ngoXn7kPnyrvIhP9lzAmBVZCPN1wYSBQRge3hkyWxuxwyQD8IoX3ZFSpUEHexuEeLPpY3tiayPFX/r6Y/s/HsS/R/VG1rmriF+4A698k4eL6utih0dEAOzt7RETE4OMjAzdNq1Wi4yMDMTFxZnNPtuzxhx6nxdzKLVeB5ktkuOC8GvKYHw8oS/cOtgj5evDGDh/Oxb9egpXKmvEDpFaiFe86I6UKjUi2PSx3bK3leKZ/oEYE+OHL/YVYHnmWXyXcxFP9Q3AtIeD4aWQix0iUbuWkpKC8ePHo0+fPujXrx8WLVqE6upqTJgwAQCQnJwMX19fpKWlAbi1eMaxY8d0f1+6dAm5ublwcnJCcHBwi/ZJLcccSsYklUrwUIgnHgrxxJnSSny85wI+2HEO728/ixERPpgwMAi9fV3EDpPugIUXNetW00c1kvryRun2Tm5ng+fv74qx/QLwadatH/qvDxXimf6BmPxgN3g4cZoRkRiSkpJw5coVzJkzB8XFxYiMjER6erpucQyVSgWp9PfJLZcvX0ZUVJTu+YIFC7BgwQIMHjwYmZmZLdontUxjDn2qb4DYoZAVCvZ0xltPhOGfCT2w9qAKn2UV4FvlRfQLcseEgUF4JNQLtjac2GZuJALv0LtNRUUFXFxcUF5eDoVCIXY4oim8dh33v70dH43vgyE9mXDpdxU36/DRrvP4aPd5aAUBzw4IwgsPdOVyyWaMv2vUVvhv7RbmUGpL9Q1abD1WgtV7zuPgBTV8XR3waC8vDAr2QGzXjnCS8VrLvTDW7xr/K1Czfm/6yJuCqSmF3A4vP3Ifnh0QhJW7zuHjPRfweVYBnr+/K54bFARnuZ3YIRIRiYo5lNqSrY0Uj4V1xmNhnZF/qRxf7ldhy9ESfLznAmylEkT6u2JgsAcGdfdApL8r7Hg1TBQsvKhZygI1gjo6wp2Nk6kZbh3s8crQHnhuYBcszzyLZZln8PHe8/jbA90wfkAgHO35E0NE7ZOyQI0uHh2YQ6nN9fZ1QdroMAiCgIKr17H7TBn2nCnDJ3svYHHGaXSwt0Fs1463CrFgD9zn5QSJhPchtgX+XxE1K6dQwyVwqUU6OcswZ0QoJj3QBcu2n8HCrSfx0e5zePHBYDwdGwC5HZe7JaL2JadQgyh/V7HDoHZMIpEgyKMDgjw64K/9A9GgFXD0crmuEPtv+gm8Ua+Fh5MMg4JvFWIDgz3g4+ogduhWi4UX6XWzrgHHLldgTB8urEEt19nFAW8mhuFvD3TD/zJO482fjmHlznOY9nAw/tLHH/a2nNpARNaPOZTMkY1UgnA/V4T7ueLFB4Nxs64Bhy6odYXY94cvQxCArp06YNBvRVj/rh3h4sDbB4yFhRfplXexHPVaAdEBrmKHQhbI390R74yJwJQHu2Fxxmm89n0+Vuw4ixlDuuOJKF+utEREVo05lCyB3M4Gg7rfuu8LANTVtcg6dxW7z5Rhx6kr+CyrAFIJEO7nqivEogNd2bT5HrDwIr1yVGo42tsghE0f6R507eSExU9F4cUHg/He1lOY9U0elmeexYz47hgR7gMpe9sQkRViDiVL5NbBHsPCOmNYWGcAt1bm3HOmDLvPlGHNARWWbj8DuZ0U/bp01E1N7OmtYC43AAsv0kupUiPcz4VXJsgoQrydseKZGORfKsfCracwY20u3t9+Fi8/ch8Sennxpl4isirMoWQN/N0d8VS/ADzVLwBarYDjxRXYc6YMu06XYeHWU/jP5hNw72CPAd066q6I+bs7ih22WWPhRbcRBAFKlQZjYvzEDoWsTG9fF6x+ti+yC9RYuPUkJn+Rjd6+Cvz9kRA8GNKJBRgRWTzmULJGUqkEvXxc0MvHBS880A036xqgVKl/uyJ2FbM3HIFWAAI7OupWS4zr2hFuXNWzCbM4FbNs2TIEBQVBLpcjNjYWBw4caHbs0aNH8eSTTyIoKAgSiQSLFi26531SU5c0N3Clsoa9R8hkYgLd8OXz/fHVpP5wsLPBhE8O4snle7H3TJnYoRER3RPmUGoP5HY2GNDNA7MSeuD7qQOR89qjWPHXGAy+rxP2nbuKF79UIvrNrRixZDfm/3wCu0+X4WZdg9hhi070K17r1q1DSkoKVqxYgdjYWCxatAgJCQk4efIkPD09bxt//fp1dO3aFWPGjMHLL79slH1SU0qVBgAQxZuCycTiunXE113jsPN0Gd7dchJPf7gf/bu64++PhqBvkLvY4RERGYw5lNojF0c7DO3tjaG9vQHcOgGx57fVEr/JLsSKHWdhbytF3yA33RWxXj4usGln94dJBEEQxAwgNjYWffv2xdKlSwEAWq0W/v7+eOmll/Cvf/3rju8NCgrCzJkzMXPmTKPtEwAqKirg4uKC8vJyKBSK1n0xC/b6D0ex/WQpdsx6SOxQqB0RBAG/Hi/Fu1tO4kRxJR64rxP+GhuAwSGduIKSEbT33zVqO+393xpzKFFTgiDgZEkldp++VYjtP38N12sb4OJghwHdfm/kHNjR0WxvOTDW75qoV7xqa2uRnZ2N1NRU3TapVIr4+HhkZWW12T5rampQU1Oje15RUdGqz7YWbPpIYpBIJHgk1AtDenhic34Rlm0/ixc+z4azzBaP9PLCiHAfDAz2YC8wIjJrOYUaRHOaIZGORCJBD28Fengr8Pz9XVFbr0VuoUbXP2zuD0fRoBXg6+pwa5GO7h4Y0K0jPJxkYodudKIWXmVlZWhoaICXl1eT7V5eXjhx4kSb7TMtLQ3z5s1r1edZm1tNH8vxZLSv2KFQOyWVSvB4uA8eD/fB6ZJK/JhXhE15l/Gd8hJcHOwwtJc3Ho/ojLiuHbliGBGZFeZQoruzt5WiXxd39OvijpRH7kPlzTrsP3dNV4itO1QIAOjZWaFbtr5fF3c42ot+h9Q9s/xvYASpqalISUnRPa+oqIC/f/vsNp9/qRx1DQLP1pFZ6O7ljJRHnPFyfHccL6rEprzL2JRXhHWHCtGxgz0eC/PG4+E+6Bvk3u7miROR+WEOJTKcs9wO8aFeiA+9ddGkpOKmrn/YD4cvY9Wu87CzkSA6wE13RSzc1zLbNYhaeHl4eMDGxgYlJSVNtpeUlMDb27vN9imTySCTWd/lzNZQqtSQ20kR4s2mj2Q+JBIJQn0UCPVRYFZCCI5cKsePhy/jp7wifLFPBU9nGYaFdcaIiM6I8ndjM0ciEkVjDu3BHErUal4KOUZH+2F0tB8EQcDZK1XYffrWsvUf7DyHd7eegrPcFv27/t4/rFunDmZ7f9gfiVp42dvbIyYmBhkZGUhMTARwayGMjIwMTJs2zWz22Z7kqDQI93OFnQWeRaD2QSKRINzPFeF+rkh9rCdyCtX48XARNh8pwid7L8DHRY7h4Z3xeLgPwv1cLOKHmIisQ2MOtcQz8UTmSCKRINjTGcGeznh2YBfUN2hx+GK57orYmz8dQ12DgM4uct0iHQOCO8LTWS526HqJPtUwJSUF48ePR58+fdCvXz8sWrQI1dXVmDBhAgAgOTkZvr6+SEtLA3Br8Yxjx47p/r506RJyc3Ph5OSE4ODgFu2T9LvV9FGNJ6LY9JEsg1QqQUygO2IC3fHa46E4eOGa7n6wVbvOI8DdEY//VoT17OzMIoyITIY5lMj0bG2kiAl0Q0ygG6YP6Y7qmnocuHANe07fKsS+yb4IAAjxcr5ViHXviH5dOsJJJnrJA8AMCq+kpCRcuXIFc+bMQXFxMSIjI5Genq5bHEOlUkEq/f3M0eXLlxEVFaV7vmDBAixYsACDBw9GZmZmi/ZJ+l0uv4mSihr2HiGLZCOVoH/XjujftSNeH9EL+87dKsLWHFDh/cyz6NqpAx4P98GI8M7o7sVpQERkXI05NJo5lKjNdJDZ4qEQTzwUcqtP75XKGuw9e2uRjvT8Iqzecx62UgmiAlx1V8Qi/MWb2SV6Hy9z1F57kGzKu4xpa3Jw8P/Fo5Mz73kj61DXoMXuM2XYdLgIW44Wo7KmHiFezreuhEX4oItHB7FDbBPt9XeN2l57/bfGHEpkXgRBwIWr12+tlni6DHvPlqHiZj0i/Fzw/bRBBu3LKvp4kXlRFmjg7+7AhEFWxc5GqjsbdrOuN3aeuoJNeUVYvuMs3t16Cr18FL8tX98Z/u6OYodLRBaKOZTIvEgkEnTx6IAuHh3wTP9ANGgF5F8qR8XNOtFiYuFFOkqVGlH+XAKXrJfczgaP9vLGo728caO2AdtPlmJT3mUszjiF/6afQKS/Kx4P74zh4Z3R2cVB7HCJyIIoVWouI09kxmykEkT4u4oaAwsvAgDU1Dfg2OUKJEb6iB0KUZtwsLfBsLDOGBbWGdU19fj1eAk25RXh7fSTePOn4+gb5IbHw33wWJi32a6ORETmgTmUiFqChRcBAPIvVaC2QYvoQJ6to/ang8wWoyJ9MSrSFxU367D1aAk25V3GG5uOYd6PRxHbpSMej+iMx3p3hnsHe7HDJSIzwxxKRC3BwosAADkqNWS2UvTwbj83QhPpo5Db4ckYPzwZ4wfN9Vr8crQYm/KK8NrGfMz5/igGdOuIEeE+SOjlDRdHO7HDJSIz0JhDe3ZmDiWi5rHwIgCNTR9dYG/Lpo9EjVwd7ZHUNwBJfQNQVlWDn/OLsenwZbzyXR7+38YjeKB7Jzwe0RnxPb3gLGcRRtReNeZQsZaoJiLLwMKLANy6KXhkBOemEzXHw0mGZ/oH4pn+gSipuInNR4qwKa8IL687DHtbKR4K6YTHw30wpKcnHO3500rUnjCHElFL8P8OCEXlN1BUfhNRXI2JqEW8FHJMGNgFEwZ2wSXNDWzOK8KmvMt46ascONjZ4OGenhgR3hkPhnhCbmcjdrhEZELMoUTUUiy8CDkqDQAgOsBV1DiILJGvqwMmPdAVkx7oCtXV69h05DI2HS7C5C+UcJLZ4pFQLzwe3hn3d+/EqbxEVog5lIhaioUXQVmghq+rAzwVXDKb6F4EdHTEiw8G48UHg3H2ShV++u1K2IacS1DIbZHQyxuPR/hgQLeOvBeEyEowhxJRS7HwoltNH7kELpFRdevkhOlDumP6kO44WVyJTXmXsSmvCOuzL8LN0Q5De3fGiPDOiO3aETZSidjhElErMYcSUUux8GrnauobkH+pAo+H86ZgIlMJ8XZGiHcIUh65D0cvV2DTb1fCvjqggoeTDMPCvDEiwgcxAW6Qsgizevn5+ejdu7fe1zZu3IjExMS2DYhajTmUiAzBwqudO3aZTR+J2opEIkFvXxf09nXBK0NDcPhiOTYdvoyfjhThs6wCeCvkGB7eGY+Hd0akvyskEhZh1ighIQG7d+9Gly5dmmz/9ttvkZycjOrqapEiI0MxhxKRIVh4tXNKlQb2tlKEsukjUZuSSCSI9HdFpL8rZg/rCaVKjR8PX8YPhy/jo93n4efmgOHhnTEi3Ae9fBQswqzI888/j/j4eOzZswfe3t4AgHXr1uG5557DJ598Im5wZBClSgMZcygRtRALr3ZOqVIjzJeNk4nEJJVK0CfIHX2C3DFnRC/sP3/11v1ghy7igx3nENTREY+H++DxiM4I8XJmEWbh5s2bh2vXriE+Ph47d+5Eeno6nn/+eXz++ed48sknxQ6PDMAcSkSGYOHVzuWqNBgW5i12GET0GxupBAO6eWBANw/8e2Qv7D17FZvyLuOzrAtYuv0Mgj2d8H8xfpg8uJvYodI9WLJkCcaNG4f+/fvj0qVL+OqrrzBq1CixwyIDMYcSkSFYeLVjJRU3cUlzA9Fs+khklmxtpHjgvk544L5OeDMxDLvPXMGmw0U4W1oldmhkoB9++OG2baNHj8auXbswduxYSCQS3ZiRI0e2dXjUCsyhRGQoFl7tmLJADQCIYtIgMnv2tlI83MMLD/fwEjsUaoU7rVS4evVqrF69GsCte/8aGhraKCq6F405lAtrEFFLsfBqx3IKNfBxkcPbhU0fiYhMSavVih0CGVljDvVi42QiaiHeDdqOKQvUiOKZOiIisxQWFobCwkKxw6BmMIcSkaFYeLVTtfVa5F0qR5S/q9ihEBGRHhcuXEBdXZ3YYZAejTmU93cRkSFYeLVTx4sqUFvPpo9ERESGasyhUQGuYodCRBaEhVc7pVSpYW8jRS8fNn0kIiIyBHMoEbUGC692SqnSoJevAjJbG7FDISIisihKlQa9mUOJyEAsvNqpHJWac9OJiIhaIUelZisWIjKY0QuvGzduGHuXZGSllTdxUc2mj0RERIZiDiWi1mpV4TV9+nS926urqzFs2LB7CohMT1mgAQBEB7qKGgcRUXv02Wefoaam5rbttbW1+Oyzz3TPP/jgA3h53b1h9rJlyxAUFAS5XI7Y2FgcOHDgjuPXr1+PHj16QC6XIywsDJs3b27yelVVFaZNmwY/Pz84ODggNDQUK1asaOG3s37MoUTUWq0qvH766SfMnTu3ybbq6moMHToU9fX1RgmMTCenUA1vhRydXRzEDoWIqN2ZMGECysvLb9teWVmJCRMm6J4//fTT6NChwx33tW7dOqSkpGDu3LlQKpWIiIhAQkICSktL9Y7fu3cvxo4di4kTJyInJweJiYlITExEfn6+bkxKSgrS09PxxRdf4Pjx45g5cyamTZuGH374oZXf2LowhxJRa7Wq8NqyZQtWrVqFRYsWAbiVLB555BFIJBKkp6cbMz4ygZwCDc/UERGJRBAESCSS27ZfvHgRLi4uBu1r4cKFmDRpEiZMmKC7MuXo6IjVq1frHb948WIMHToUs2bNQs+ePfHGG28gOjoaS5cu1Y3Zu3cvxo8fjwcffBBBQUF44YUXEBERcdcrae0FcygRtZZta97UrVs3pKen46GHHoJUKsVXX30FmUyGn3766a5n50hcdQ1a5F3S4B+PhogdChFRuxIVFQWJRAKJRIIhQ4bA1vb3FNzQ0IDz589j6NChLd5fbW0tsrOzkZqaqtsmlUoRHx+PrKwsve/JyspCSkpKk20JCQnYuHGj7vmAAQPwww8/4LnnnoOPjw8yMzNx6tQpvPfee3r3WVNT02TqZEVFRYu/g6VhDiWie9GqwgsAwsPDsWnTJjzyyCOIjY3Fpk2b4ODAy+7m7nhRBW7WsekjEVFbS0xMBADk5uYiISEBTk5Outfs7e0RFBSEJ598ssX7KysrQ0NDw233gXl5eeHEiRN631NcXKx3fHFxse75kiVL8MILL8DPzw+2traQSqVYtWoVHnjgAb37TEtLw7x581octyVjDiWie9HiwqvxTN2fyWQyXL58GQMHDtRtUyqVxomOjC5HpYGdjQS9fAybzkJERPem8d7ooKAgJCUlQS6XixyRfkuWLMG+ffvwww8/IDAwEDt37sTUqVPh4+OD+Pj428anpqY2uYpWUVEBf3//tgy5zTCHEtG9aHHh1XimjiybUqVGLx8XyO3Y9JGISAzjx483yn48PDxgY2ODkpKSJttLSkrg7e2t9z3e3t53HH/jxg3Mnj0bGzZswPDhwwHcmuGSm5uLBQsW6C28ZDIZZDKZMb6S2WMOJaJ70eLC68+rGLbEV199hZEjR/K+LzOiVKkR3/PuyxMTEZFpNDQ04L333sPXX38NlUqF2traJq9fu3atRfuxt7dHTEwMMjIydCdHtVotMjIyMG3aNL3viYuLQ0ZGBmbOnKnbtnXrVsTFxQEA6urqUFdXB6m06dpbNjY20Gq1LfyG1kupUuORnvqLWiKiuzF6A+U/+tvf/nbbmTUSz5XKGhReY9NHIiIxzZs3DwsXLkRSUhLKy8uRkpKC0aNHQyqV4vXXXzdoXykpKVi1ahU+/fRTHD9+HFOmTEF1dbVuWfrk5OQmi2/MmDED6enpePfdd3HixAm8/vrrOHTokK5QUygUGDx4MGbNmoXMzEycP38en3zyCT777DM88cQTRjsGlqgxh/L+LiJqrVYvrtESgiCYcvdkoByVGgAQHcjCi4hILF9++SVWrVqF4cOH4/XXX8fYsWPRrVs3hIeHY9++fZg+fXqL95WUlIQrV65gzpw5KC4uRmRkJNLT03ULaKhUqiZXrwYMGIA1a9bg1VdfxezZs9G9e3ds3LgRvXv31o1Zu3YtUlNTMW7cOFy7dg2BgYF46623MHnyZOMdBAvEHEpE98qkhReZF6VKA09nGXxczPOGbiKi9qC4uBhhYWEAACcnJ10z5ccffxyvvfaawfubNm1as1MLMzMzb9s2ZswYjBkzptn9eXt74+OPPzY4DmunVGngpWAOJaLWM+lUQzIvOSo1ogPc9K5OSUREbcPPzw9FRUUAbvXF3LJlCwDg4MGD7WaRCkuUo1Ijyp85lIhaj4VXO1HfoEXexXJEB7qKHQoRUbv2xBNPICMjAwDw0ksv4bXXXkP37t2RnJyM5557TuToSB/mUCIyBk41bCdOFFfiRl0DoriwBhGRqObPn6/7OykpCYGBgdi7dy+6d++OESNGiBgZNacxh3JxKiK6FyYtvAIDA2FnZ2fKj6AWylGpYSuVIMyXTR+JiMxJ//790b9//9u2Dx8+HB9++CE6d+4sQlT0R405tDdzKBHdg1ZNNezatSuuXr1623aNRoOuXbvqnufn57eoe/2yZcsQFBQEuVyO2NhYHDhw4I7j169fjx49ekAulyMsLAybN29u8npVVRWmTZsGPz8/ODg4IDQ0FCtWrGjht7NOSpUGvXwUbPpIRGQhdu7ciRs3bogdBoE5lIiMo1WF14ULF9DQ0HDb9pqaGly6dMmgfa1btw4pKSmYO3culEolIiIikJCQgNLSUr3j9+7di7Fjx2LixInIyclBYmIiEhMTkZ+frxuTkpKC9PR0fPHFFzh+/DhmzpyJadOm4YcffjDsi1oRpUrNaYZEREStwBxKRMZg0FTDPxYuv/zyC1xcfr/k3tDQgIyMDAQFBRkUwMKFCzFp0iRds8cVK1bgp59+wurVq/Gvf/3rtvGLFy/G0KFDMWvWLADAG2+8ga1bt2Lp0qW6q1p79+7F+PHj8eCDDwIAXnjhBXzwwQc4cOAARo4caVB81uBqVQ0Krl5n00ciIiIDMYcSkbEYVHglJiYCACQSCcaPH9/kNTs7OwQFBeHdd99t8f5qa2uRnZ2N1NRU3TapVIr4+HhkZWXpfU9WVhZSUlKabEtISMDGjRt1zwcMGIAffvgBzz33HHx8fJCZmYlTp07hvffe07vPmpoa1NTU6J5XVFS0+DtYghyVBgB4UzAREZGBmEOJyFgMKry0Wi0AoEuXLjh48CA8PDzu6cPLysrQ0NAALy+vJtu9vLxw4sQJve8pLi7WO764uFj3fMmSJXjhhRfg5+cHW1tbSKVSrFq1Cg888IDefaalpWHevHn39F3MmVKlRidnGfzcHMQOhYiIyKIwhxKRsbTqHq/z58/fc9FlSkuWLMG+ffvwww8/IDs7G++++y6mTp2KX3/9Ve/41NRUlJeX6x6FhYVtHLFpKVVqRPm7sukjERGRgZhDichYWr2cfHV1NXbs2AGVSoXa2tomr02fPr1F+/Dw8ICNjQ1KSkqabC8pKYG3t7fe93h7e99x/I0bNzB79mxs2LABw4cPBwCEh4cjNzcXCxYsQHx8/G37lMlkkMlkLYrZ0jQ2fZw+pLvYoRARkQFmz54Nd3d3scNo15hDiciYWlV45eTkYNiwYbh+/Tqqq6vh7u6OsrIyODo6wtPTs8WFl729PWJiYpCRkaG7f0yr1SIjIwPTpk3T+564uDhkZGRg5syZum1bt25FXFwcAKCurg51dXWQSptezLOxsdFNlWxPTpZU4notmz4SEZmT06dPY/v27SgtLb0tN82ZMwcAmtz/TOJgDiUiY2pV4fXyyy9jxIgRWLFiBVxcXLBv3z7Y2dnhr3/9K2bMmGHQvlJSUjB+/Hj06dMH/fr1w6JFi1BdXa1b5TA5ORm+vr5IS0sDAMyYMQODBw/Gu+++i+HDh2Pt2rU4dOgQVq5cCQBQKBQYPHgwZs2aBQcHBwQGBmLHjh347LPPsHDhwtZ8XYumVGnYOJmIyIysWrUKU6ZMgYeHB7y9vZtMYZNIJLrCi8THHEpExtSqwis3NxcffPABpFIpbGxsUFNTg65du+Ltt9/G+PHjMXr06BbvKykpCVeuXMGcOXNQXFyMyMhIpKen6xbQUKlUTa5eDRgwAGvWrMGrr76K2bNno3v37ti4cSN69+6tG7N27VqkpqZi3LhxuHbtGgIDA/HWW29h8uTJrfm6Fi1HpUbPzgo42LPpIxGROXjzzTfx1ltv4ZVXXhE7FLoL5lAiMqZWFV52dna6YsjT0xMqlQo9e/aEi4tLqxammDZtWrNTCzMzM2/bNmbMGIwZM6bZ/Xl7e+Pjjz82OA5rlKPS4IHu5rsQChFRe6NWq++Yw8h8MIcSkTG1alXDqKgoHDx4EAAwePBgzJkzB19++SVmzpzZ5MoTietadS3Ol1UjinPTiYjMxpgxY7Blyxaxw6C7YA4lImNr1RWv//znP6isrAQAvPXWW0hOTsaUKVPQvXt3rF692qgBUuvlFqoBsOkjEZE5CQ4OxmuvvYZ9+/YhLCwMdnZ2TV5v6QJVZFrMoURkbK0qvPr06aP729PTE+np6XrH7dmzB3369LHapdrNnbJAAw8ne/i7s+kjEZG5WLlyJZycnLBjxw7s2LGjyWsSiYSFl5lgDiUiY2t1H6+WeOyxx5Cbm4uuXbua8mOoGUqVGpH+bmz6SERkRs6fPy92CNQCSpUaUQHMoURkPK26x6ulBEEw5e7pDhq0Ag4XahAd6Cp2KERE1AxBEJgrzVBjDo0KcBU7FCKyIiYtvEg8p0oqUc2mj0REZumzzz5DWFgYHBwc4ODggPDwcHz++edih0W/YQ4lIlMw6VRDEo9SpYaNVIJwPzZ9JCIyJwsXLsRrr72GadOmYeDAgQCA3bt3Y/LkySgrK8PLL78scoTEHEpEpsDCy0rlqDTo4e0MR3v+JyYiMidLlizB8uXLkZycrNs2cuRI9OrVC6+//joLLzPAHEpEpmDSqYa8IVU8SpWaUySIiMxQUVERBgwYcNv2AQMGoKioSISI6M+YQ4nIFLi4hhXSXK/FuSvVvCmYiMgMBQcH4+uvv75t+7p169C9e3cRIqI/asyhXJyKiIzNpNfQG5ssU9vKUWkAsOkjEZE5mjdvHpKSkrBz507dPV579uxBRkaG3oKM2lZjDo3yZw4lIuNqVeEVFRWldxqhRCKBXC5HcHAwnn32WTz00EP3HCAZLkelhnsHewR2dBQ7FCIi+pMnn3wS+/fvx3vvvYeNGzcCAHr27IkDBw4gKipK3OCIOZSITKZVhdfQoUOxfPlyhIWFoV+/fgCAgwcPIi8vD88++yyOHTuG+Ph4fPfddxg1apRRA6a7U6o0iA5w5T12RERmKiYmBl988YXYYZAezKFEZCqtKrzKysrw97//Ha+99lqT7W+++SYKCgqwZcsWzJ07F2+88QYLrzbWoBWQW6jBlAe7iR0KERH9pqKiAgqFQvf3nTSOo7bHHEpEptSqxTW+/vprjB079rbtTz31lG5++tixY3Hy5Ml7i44Mdqa0ClU19VxYg4jIjLi5uaG0tBQA4OrqCjc3t9sejdtJPMyhRGRKrbriJZfLsXfvXgQHBzfZvnfvXsjlcgCAVqvV/U1tR6lSQyoBIvxcxQ6FiIh+s23bNri7uwMAtm/fLnI01BzmUCIypVYVXi+99BImT56M7Oxs9O3bF8Cte7w+/PBDzJ49GwDwyy+/IDIy0miBUssoC9QI8Vagg4xNH4mIzMXgwYP1/k3mhTmUiEypVb8sr776Krp06YKlS5fi888/BwCEhIRg1apVePrppwEAkydPxpQpU4wXKbVITqEGsV3cxQ6DiIiakZ6eDicnJwwaNAgAsGzZMqxatQqhoaFYtmwZpxuKiDmUiEyp1Q2Ux40bh6ysLFy7dg3Xrl1DVlaWrugCAAcHB041bGPl1+twprSK/buIiMzYrFmzdAtsHDlyBCkpKRg2bBjOnz+PlJQUkaNrv5hDicjUWnXF6+DBg9BqtYiNjW2yff/+/bCxsUGfPn2MEhwZJqdQDQC8KZiIyIydP38eoaGhAIBvv/0WI0aMwH/+8x8olUoMGzZM5OjaL+ZQIjK1Vl3xmjp1KgoLC2/bfunSJUydOvWeg6LWyVFp4OZohy4eHcQOhYiImmFvb4/r168DAH799Vc8+uijAAB3d/e7LjVPpsMcSkSm1qorXseOHUN0dPRt26OionDs2LF7DopaR6lSIyrAjU0fiYjM2KBBg5CSkoKBAwfiwIEDWLduHQDg1KlT8PPzEzm69os5lIhMrVVXvGQyGUpKSm7bXlRUBFtbrgQkBu1vTR+j/F3FDoWIiO5g6dKlsLW1xTfffIPly5fD19cXAPDzzz9j6NChIkfXPjXm0GhOMyQiE2pVlfToo48iNTUV33//PVxcXAAAGo0Gs2fPxiOPPGLUAKllzl6pQuXNekQH8qZgIiJzFhAQgE2bNt22/b333hMhGgJ+z6FRXFiDiEyoVYXXggUL8MADDyAwMBBRUVEAgNzcXHh5eemWl6e2pVSpIZEAEbziRURkdioqKqBQKHR/30njOGo7usbJzKFEZEKtKrx8fX2Rl5eHL7/8EocPH4aDgwMmTJiAsWPHws7OztgxUgsoCzQI8XKGE5s+EhGZHTc3NxQVFcHT0xOurq567yMSBAESiQQNDQ0iRNi+KQs0uI85lIhMrNW/MB06dMALL7xgzFjoHihVavQJYtNHIiJztG3bNri73/qN3r59u8jR0J8xhxJRW2hx4fXDDz+0eKcjR45sVTDUOuU36nC6tAovPNBV7FCIiEiPwYMH6/2bxMccSkRtpcWFV2JiYpPnEokEgiA0ed6I0yTa1uFCDQDwpmAiIgvw8ccfw8nJCWPGjGmyff369bh+/TrGjx8vUmTtU2MO5eJURGRqLV5OXqvV6h5btmxBZGQkfv75Z2g0Gmg0GmzevBnR0dFIT083Zbykh1KlhouDHbqy6SMRkdlLS0uDh4fHbds9PT3xn//8R4SI2rfGHNqlI3MoEZlWq/p4zZw5E4sXL0ZCQgIUCgUUCgUSEhKwcOFCTJ8+3dgx0l3kqDSICnCFVMqmj0RE5k6lUqFLly63bQ8MDIRKpTJ4f8uWLUNQUBDkcjliY2Nx4MCBO45fv349evToAblcjrCwMGzevPm2McePH8fIkSPh4uKCDh06oG/fvq2KzRIwhxJRW2lV4XX27Fm4urrett3FxQUXLly4x5DIEFqtgByVGtGcZkhEZBE8PT2Rl5d32/bDhw+jY8eOBu1r3bp1SElJwdy5c6FUKhEREYGEhASUlpbqHb93716MHTsWEydORE5ODhITE5GYmIj8/HzdmLNnz2LQoEHo0aMHMjMzkZeXh9deew1yudywL2oBmEOJqC21qvDq27cvUlJSUFJSottWUlKCWbNmoV+/fkYLju7uXFkVKm7WIyrAVexQiIioBcaOHYvp06dj+/btaGhoQENDA7Zt24YZM2bgqaeeMmhfCxcuxKRJkzBhwgSEhoZixYoVcHR0xOrVq/WOX7x4MYYOHYpZs2ahZ8+eeOONNxAdHY2lS5fqxvy///f/MGzYMLz99tuIiopCt27dMHLkSHh6et7T9zZHzKFE1JZaVXitXr0aRUVFCAgIQHBwMIKDg+Hv749Lly7hww8/NHaMdAdKlQYSCRDJpo9ERBbhjTfeQGxsLIYMGQIHBwc4ODjg0UcfxcMPP2zQPV61tbXIzs5GfHy8bptUKkV8fDyysrL0vicrK6vJeABISEjQjddqtfjpp59w3333ISEhAZ6enoiNjcXGjRubjaOmpgYVFRVNHpaCOZSI2lKr+ngFBwcjLy8Pv/76K44fPw4A6NmzJ+Lj4/U2hSTTyVGpcZ+nM5zlbFxNRGQJ7O3tsW7dOrzxxhs4fPgwHBwcEBYWhsDAQIP2U1ZWhoaGBnh5eTXZ7uXlhRMnTuh9T3Fxsd7xxcXFAIDS0lJUVVVh/vz5ePPNN/Hf//4X6enpGD16NLZv3653Kfy0tDTMmzfPoNjNBXMoEbWlVjdQ3rZtG7Zv347S0lJotVrk5ubiq6++AoBmpziQ8SkLNJwiQURkgYKCgiAIArp16wZb21anY6PSarUAgFGjRuHll18GAERGRmLv3r1YsWKF3sIrNTUVKSkpuucVFRXw9/dvm4DvEXMoEbWlVk01nDdvHh599FFkZGSgrKwMarW6yYPaRuXNOpwqreRNwUREFuT69euYOHEiHB0d0atXL91qgS+99BLmz5/f4v14eHjAxsamyf3WwK17rr29vfW+x9vb+47jPTw8YGtri9DQ0CZjevbs2eyqhjKZTLfCcePDEjCHElFba1XhtWLFCnzyySfYv38/Nm7ciA0bNjR5UNs4XFgOQQCiA13FDoWIiFooNTUVhw8fRmZmZpOVAuPj47Fu3boW78fe3h4xMTHIyMjQbdNqtcjIyEBcXJze98TFxTUZDwBbt27Vjbe3t0ffvn1x8uTJJmNOnTpl8FRIc8ccSkRtrVVzG2prazFgwABjx0IGUqrUUMht0dXDSexQiIiohTZu3Ih169ahf//+Te6L7tWrF86ePWvQvlJSUjB+/Hj06dMH/fr1w6JFi1BdXY0JEyYAAJKTk+Hr64u0tDQAwIwZMzB48GC8++67GD58ONauXYtDhw5h5cqVun3OmjULSUlJeOCBB/DQQw8hPT0dP/74IzIzM+/9y5sR5lAiamutuuL1/PPPY82aNcaOhQyUo1IjMsCNTR+JiCzIlStX9C7NXl1dbfACVUlJSViwYAHmzJmDyMhI5ObmIj09XbeAhkqlQlFRkW78gAEDsGbNGqxcuRIRERH45ptvsHHjRvTu3Vs35oknnsCKFSvw9ttvIywsDB9++CG+/fZbDBo0qJXf2DwxhxJRW2vVFa+bN29i5cqV+PXXXxEeHg47u6arAS1cuNAowVHzBEFATqEGzw4IEjsUIiIyQJ8+ffDTTz/hpZdeAgBdsfXhhx82O0XwTqZNm4Zp06bpfU3fVaoxY8ZgzJgxd9znc889h+eee87gWCwFcygRiaFVV7zy8vIQGRkJqVSK/Px85OTk6B65ubkG72/ZsmUICgqCXC5HbGwsDhw4cMfx69evR48ePSCXyxEWFobNmzffNub48eMYOXIkXFxc0KFDB/Tt27fZG4Mt0bmyamiu1yGKNwUTEVmU//znP5g9ezamTJmC+vp6LF68GI8++ig+/vhjvPXWW2KH1y4whxKRGFp1xWv79u1GC2DdunVISUnBihUrEBsbi0WLFiEhIQEnT57UOxVj7969GDt2LNLS0vD4449jzZo1SExMhFKp1E2VOHv2LAYNGoSJEydi3rx5UCgUOHr0aJObmC1djkoDgE0fiYgszaBBg3D48GGkpaUhLCwMW7ZsQXR0NLKyshAWFiZ2eO0CcygRiUEiCIIgZgCxsbHo27cvli5dCuDWikz+/v546aWX8K9//eu28UlJSaiursamTZt02/r374/IyEisWLECAPDUU0/Bzs4On3/+eatiqqiogIuLC8rLy812WdzZG47g4Plr2Jpye08VIqI/s4Tftfagrq4Of/vb3/Daa6+hS5cuYodjEpbwb405lIgMYazftVZNNTSW2tpaZGdnIz4+XrdNKpUiPj4eWVlZet+TlZXVZDwAJCQk6MZrtVr89NNPuO+++5CQkABPT0/ExsZi48aNJvseYlAWqNn0kYjIwtjZ2eHbb78VO4x2T1mgZv8uImpzohZeZWVlaGho0K2+1MjLywvFxcV631NcXHzH8aWlpaiqqsL8+fMxdOhQbNmyBU888QRGjx6NHTt26N1nTU0NKioqmjzMWVVNPU6VsOkjEZElSkxMtLqTgZakMYfy5CURtbVW3eNlzrRaLQBg1KhRePnllwEAkZGR2Lt3L1asWIHBg2+fVpCWloZ58+a1aZz3Iq9QA60ARAey8CIisjTdu3fHv//9b+zZswcxMTHo0KFDk9enT58uUmTtA3MoEYlF1MLLw8MDNjY2KCkpabK9pKQE3t7eet/j7e19x/EeHh6wtbVFaGhokzE9e/bE7t279e4zNTUVKSkpuucVFRXw9/c3+Pu0FaVKDWeZLYI7sekjEZGl+eijj+Dq6ors7GxkZ2c3eU0ikbDwMjHmUCISi6iFl729PWJiYpCRkYHExEQAt65YZWRkNNuTJC4uDhkZGZg5c6Zu29atW3W9T+zt7dG3b1+cPHmyyftOnTqFwMBAvfuUyWSQyWT3/oXaiFKlQWSAK5s+EhFZoPPnz+v+blzfytDGydR6zKFEJBZR7/ECgJSUFKxatQqffvopjh8/jilTpqC6uhoTJkwAACQnJyM1NVU3fsaMGUhPT8e7776LEydO4PXXX8ehQ4eaFGqzZs3CunXrsGrVKpw5cwZLly7Fjz/+iBdffLHNv5+xCYKAHJWavUeIiCzYRx99hN69e0Mul0Mul6N379748MMPxQ7L6jGHEpGYRL/HKykpCVeuXMGcOXNQXFyMyMhIpKen6xbQUKlUkEp/rw8HDBiANWvW4NVXX8Xs2bPRvXt3bNy4UdfDCwCeeOIJrFixAmlpaZg+fTpCQkLw7bffYtCgQW3+/YztwtXrUF+vQzRvCiYiskhz5szBwoUL8dJLL+lma2RlZeHll1+GSqXCv//9b5EjtF7MoUQkJtH7eJkjc+5B8m32Rfx9/WEcnvMoXBztxA6HiCyEOf+utTedOnXC//73P4wdO7bJ9q+++govvfQSysrKRIrMOMz53xpzKBG1hlX08SLD5RSq0a1TByYMIiILVVdXhz59+ty2PSYmBvX19SJE1H4whxKRmFh4WRhlgYb9u4iILNgzzzyD5cuX37Z95cqVGDdunAgRtR/MoUQkJtHv8aKWq66px4niCvy1v/7VGYmIyDJ89NFH2LJlC/r37w8A2L9/P1QqFZKTk5u0N1m4cKFYIVod5lAiEhsLLwuSd7H8t6aPrmKHQkRErZSfn4/o6GgAwNmzZwHc6kHp4eGB/Px83TguMW9czKFEJDYWXhZEqVLDSWaL7p7OYodCRESttH37drFDaJeYQ4lIbLzHy4LkqNSI8HeBDZs+EhERGYQ5lIjExsLLQtxq+sibgomIiAzFHEpE5oCFl4VQXbuOq9W1TBpEREQGYg4lInPAwstCKFVqAECkv6u4gRAREVkY5lAiMgcsvCyEskCDrh4d4NbBXuxQiIiILApzKBGZAxZeFiKnUI0oTpEgIiIyGHMoEZkDFl4W4HptPY4XVSIqwFXsUIiIiCxKYw5l/y4iEhsLLwuQd7EcDVqBNwUTEREZqDGHRvkzhxKRuFh4WYAclQaO9jYI8WbTRyIiIkPkqDTowBxKRGaAhZcFUKrUiPBzZdNHIiIiAylVakT4M4cSkfhYeJm5W00f1ZybTkREZKDGHMp7pInIHLDwMnMX1TdQVsWmj0RERIZiDiUic8LCy8w1Nn3kMrhERESGYQ4lInPCwsvMKQvUCOroCHc2fSQiIjIIcygRmRMWXmYup1DDKRJEREStwBxKROaEhZcZu1nXgGOXKxAVyKRBRERkCOZQIjI3LLzMWN7FctRrBUT5u4odChERkUVhDiUic8PCy4zlqNRwtLdBDzZ9JCIiMghzKBGZGxZeZkypUiPczwW2NvzPREREZAjmUCIyN/w1MlOCIECp0nAJXCIiIgMxhxKROWLhZaYuqm/gSmUNV2MiIiIyEHMoEZkjFl5mKqdQAwCICnAVNQ4iIiJLwxxKROaIhZeZUhaoEeDuCA8nmdihEBERWRTmUCIyRyy8zFSOSo1onqkjIiIyGHMoEZkjFl5m6GZdA45erkA0mz4SEREZhDmUiMwVCy8zlH+psekjkwYREZEhGnMoF9YgInPDwssMKVVqyO2k6NGZTR+JiIgM0ZhDQ9g4mYjMDAsvM5Sj0iDczxV2bPpIRERkEOZQIjJX/FUyM7eaPqq5BC4REZGBGnMopxkSkTli4WVmLpffREkFmz4SEVHLLFu2DEFBQZDL5YiNjcWBAwfuOH79+vXo0aMH5HI5wsLCsHnz5mbHTp48GRKJBIsWLTJy1KbRmEN58pKIzBELLzOTo1IDAAsvIiK6q3Xr1iElJQVz586FUqlEREQEEhISUFpaqnf83r17MXbsWEycOBE5OTlITExEYmIi8vPzbxu7YcMG7Nu3Dz4+Pqb+GkbDHEpE5oyFl5lRFmjg7+6ATs5s+khERHe2cOFCTJo0CRMmTEBoaChWrFgBR0dHrF69Wu/4xYsXY+jQoZg1axZ69uyJN954A9HR0Vi6dGmTcZcuXcJLL72EL7/8EnZ2dm3xVYyCOZSIzBkLLzOjVKm5jDwREd1VbW0tsrOzER8fr9smlUoRHx+PrKwsve/JyspqMh4AEhISmozXarV45plnMGvWLPTq1euucdTU1KCioqLJQyzMoURkzlh4mZGa+gYcu1yBaM5NJyKiuygrK0NDQwO8vLyabPfy8kJxcbHe9xQXF991/H//+1/Y2tpi+vTpLYojLS0NLi4uuoe/v7+B38Q4mEOJyNyx8DIj+ZcqUNugRXQgz9YREVHby87OxuLFi/HJJ59AIpG06D2pqakoLy/XPQoLC00cpX7MoURk7lh4mZEclRoyWyl6eCvEDoWIiMych4cHbGxsUFJS0mR7SUkJvL299b7H29v7juN37dqF0tJSBAQEwNbWFra2tigoKMDf//53BAUF6d2nTCaDQqFo8hADcygRmTuzKLy4FO4tt5o+usDe1iz+sxARkRmzt7dHTEwMMjIydNu0Wi0yMjIQFxen9z1xcXFNxgPA1q1bdeOfeeYZ5OXlITc3V/fw8fHBrFmz8Msvv5juyxgBcygRmTvRf524FO7v2PSRiIgMkZKSglWrVuHTTz/F8ePHMWXKFFRXV2PChAkAgOTkZKSmpurGz5gxA+np6Xj33Xdx4sQJvP766zh06BCmTZsGAOjYsSN69+7d5GFnZwdvb2+EhISI8h1bijmUiMyd6IUXl8K9paj8BorKb7LpIxERtVhSUhIWLFiAOXPmIDIyErm5uUhPT9ctoKFSqVBUVKQbP2DAAKxZswYrV65EREQEvvnmG2zcuBG9e/cW6ysYBXMoEVkCWzE/vHEp3D+ejWvJUrgpKSlNtiUkJGDjxo2654YuhWsOlAUaAGz6SEREhpk2bZruitWfZWZm3rZtzJgxGDNmTIv3f+HChVZG1naYQ4nIEohaeN1pKdwTJ07ofY8plsKtqalBTU2N7rkYPUhyVGr4ujrAUyFv888mIiKyZMyhRGQJRJ9qaGytWQrXHHqQKFVqTpEgIiJqBeZQIrIEohZe5rIUrtg9SGrqG5B/qYJTJIiIiAzEHEpElkLUwstclsIVuwfJscts+khERNQazKFEZClEvccLuLUU7vjx49GnTx/069cPixYtum0pXF9fX6SlpQG4tRTu4MGD8e6772L48OFYu3YtDh06hJUrVwK4tRRux44dm3yGuS+Fq1RpYG8rRWhnNn0kIiIyBHMoEVkK0QuvpKQkXLlyBXPmzEFxcTEiIyNvWwpXKv39wlzjUrivvvoqZs+eje7du1v8UrhKlRphvmz6SEREZCjmUCKyFKIXXgCXws1VaTAsTP89bURERNQ85lAishQ8PSSykoqbuKS5gSjeFExERGSQxhzKhTWIyBKw8BKZskANgE0fiYiIDNWYQ3nykogsAQsvkeUUauDjIoe3C5s+EhERGYI5lIgsCQsvkSkL1IjiErhEREQGYw4lIkvCwktEtfVa5F0qR5S/q9ihEBERWRTmUCKyNCy8RHS8qAK19Wz6SEREZCjmUCKyNCy8RKRUqWFvI0UvHzZ9JCIiMgRzKBFZGhZeIlKqNOjlq4DM1kbsUIiIiCwKcygRWRoWXiJSFqi5jDwREVErMIcSkaVh4SWSUjZ9JCIiahXmUCKyRCy8RKJUaQAAUQGuosZBRERkaZhDicgSsfASSY5KDW+FHD6uDmKHQkREZFGYQ4nIErHwEkmOSoPoQFexwyAiIrI4zKFEZIlYeImgrkGLvEsaRPlzbjoREZEhmEOJyFKx8BLB8aIK3KzT8mwdERGRgZhDichSsfASQY5KAzsbCXr5uIgdChERkUVhDiUiS8XCSwRKlRqhPi6Q27HpIxERkSGYQ4nIUrHwEoFSpUY0l8AlIiIyGHMoEVkqFl5t7EplDQqvsekjERGRoZhDiciSsfBqYzkqNQA2fSQiIjJUYw6NDmThRUSWh4VXG1OqNPB0lsGXTR+JiIgM0phDfVzkYodCRGQwFl5tLEelRnSAGyQSidihEBERWRTmUCKyZCy82lB9gxZ5F8vZe4SIiMhAzKFEZOlYeLWhE8WVuFHXgCjeFExERGQQ5lAisnQsvNqQUqWGrVSCMF82fSQiIjIEcygRWToWXm0oR6VBLx8Fmz4SEREZiDmUiCwdC682pFSpOUWCiIioFZhDicjSsfBqI2VVNSi4ep39u4iIiAzEHEpE1oCFVxvJVWkAANE8W0dERGQQ5lAisgYsvNqIUqWGh5MMfm5snExERGQI5lAisgYsvNqIUqVGdIArmz4SEREZiDmUiKwBC6828HvTR06RICIiMgRzKBFZCxZebeBkSSWu1zYgyt9V7FCIiIgsCnMoEVkLFl5tQKnSwFYqQbifq9ihEBERWRTmUCKyFiy82kCOSo2enRVwsGfTRyIiIkMwhxKRtWDh1QZyVBr2HiEiImoF5lAishYsvEzsWnUtzpdVs/cIERGRgZhDiciasPAysdxCNQA2fSQiIjIUcygRWRMWXiamLNCgYwd7+Luz6SMRERnfsmXLEBQUBLlcjtjYWBw4cOCO49evX48ePXpALpcjLCwMmzdv1r1WV1eHV155BWFhYejQoQN8fHyQnJyMy5cvm/pr6MUcSkTWhIWXiSlVakQFuLHpIxERGd26deuQkpKCuXPnQqlUIiIiAgkJCSgtLdU7fu/evRg7diwmTpyInJwcJCYmIjExEfn5+QCA69evQ6lU4rXXXoNSqcR3332HkydPYuTIkW35tXSYQ4nImkgEQRDEDsLcVFRUwMXFBeXl5VAoFK3eT4NWQPjrv2Dqw8F48cFgI0ZIRGQYY/2ukXmJjY1F3759sXTpUgCAVquFv78/XnrpJfzrX/+6bXxSUhKqq6uxadMm3bb+/fsjMjISK1as0PsZBw8eRL9+/VBQUICAgIC7xsQcSkTWxli/a2Zxxctap0mcKqlEdW0D56YTEZHR1dbWIjs7G/Hx8bptUqkU8fHxyMrK0vuerKysJuMBICEhodnxAFBeXg6JRAJXV1ejxN1SzKFEZG1EL7yseZqEUqWGjVSCcD+XNv9sIiKybmVlZWhoaICXl1eT7V5eXiguLtb7nuLiYoPG37x5E6+88grGjh3b7FnempoaVFRUNHkYA3MoEVkb0QuvhQsXYtKkSZgwYQJCQ0OxYsUKODo6YvXq1XrHL168GEOHDsWsWbPQs2dPvPHGG4iOjtZNs3BxccHWrVvxl7/8BSEhIejfvz+WLl2K7OxsqFSqtvxqUBZo0MPbGY72tm36uURERPeqrq4Of/nLXyAIApYvX97suLS0NLi4uOge/v7+Rvl85lAisjaiFl7WPk0ip1DNKRJERGQSHh4esLGxQUlJSZPtJSUl8Pb21vseb2/vFo1vLLoKCgqwdevWO97TkJqaivLyct2jsLCwld+oKeZQIrI2ohZe1jxNQnO9FueuVCMqwPWe90VERPRn9vb2iImJQUZGhm6bVqtFRkYG4uLi9L4nLi6uyXgA2Lp1a5PxjUXX6dOn8euvv6Jjx453jEMmk0GhUDR53CvmUCKyRqJPNTQlMadJ5Kg0ANj0kYiITCclJQWrVq3Cp59+iuPHj2PKlCmorq7GhAkTAADJyclITU3VjZ8xYwbS09Px7rvv4sSJE3j99ddx6NAhTJs2DcCtvPl///d/OHToEL788ks0NDSguLgYxcXFqK2tbbPvxRxKRNZI1InTbTVNYtu2bXedJpGSkqJ7XlFRcc/FV45KDfcO9gjs6HhP+yEiImpOUlISrly5gjlz5qC4uBiRkZFIT0/XzQxRqVSQSn8/xzpgwACsWbMGr776KmbPno3u3btj48aN6N27NwDg0qVL+OGHHwAAkZGRTT5r+/btePDBB9vkezGHEpE1ErXw+uM0icTERAC/T5NoPPv2Z43TJGbOnKnbdqdpEtu3b2/RNAmZTHbP3+ePlCoNovxd2fSRiIhMatq0ac3mzMzMzNu2jRkzBmPGjNE7PigoCObQ3pM5lIiskehTDa1xmkSDVkBuoQbRgZwiQUREZAjmUCKyVqKv0WqN0yTOlFahqqaeNwUTEREZiDmUiKyV6IUXYH3TJJQqNaQSIMLPVdQ4iIiILA1zKBFZK9GnGlojZYEaId4KdJCZRV1LRERkMZhDichasfAygZxCDaI5RYKIiMhgzKFEZK1YeBlZ+fU6nCmtQhR7jxARERmEOZSIrBkLLyPLKVQDAM/WERERGYg5lIisGQsvI8tRaeDqaIcuHh3EDoWIiMiiMIcSkTVj4WVkSpWaTR+JiIhagTmUiKwZCy8j0jY2feTcdCIiIoMwhxKRtWPhZURnrlSh8mY9ogOZNIiIiAzBHEpE1o6FlxHlqNSQSIAIf1exQyEiIrIozKFEZO1YeBlRdy9npMTfByc2fSQiIjIIcygRWTv+uhlRdIAb56YTERG1AnMoEVk7XvEiIiIiIiIyMRZeREREREREJsbCi4iIiIiIyMRYeBEREREREZkYCy8iIiIiIiITY+FFRERERERkYiy8iIiIiIiITIyFFxERERERkYmx8CIiIiIiIjIxFl5EREREREQmxsKLiIiIiIjIxFh4ERERERERmRgLLyIiIiIiIhOzFTsAcyQIAgCgoqJC5EiIiIyj8fes8feNyFSYQ4nI2hgrh7Lw0qOyshIA4O/vL3IkRETGVVlZCRcXF7HDICvGHEpE1upec6hE4OnP22i1Wly+fBnOzs6QSCQtfl9FRQX8/f1RWFgIhUJhwggtD4+NfjwuzeOx0a+1x0UQBFRWVsLHxwdSKWeZk+kwhxofj41+PC7N47HRT+wcyiteekilUvj5+bX6/QqFgv/Im8Fjox+PS/N4bPRrzXHhlS5qC8yhpsNjox+PS/N4bPQTK4fytCcREREREZGJsfAiIiIiIiIyMRZeRiSTyTB37lzIZDKxQzE7PDb68bg0j8dGPx4Xslb8t908Hhv9eFyax2Ojn9jHhYtrEBERERERmRiveBEREREREZkYCy8iIiIiIiITY+FFRERERERkYiy8iIiIiIiITKzdF147d+7EiBEj4OPjA4lEgo0bNzZ5XRAEzJkzB507d4aDgwPi4+Nx+vTpJmOuXbuGcePGQaFQwNXVFRMnTkRVVVWTMXl5ebj//vshl8vh7++Pt99++7ZY1q9fjx49ekAulyMsLAybN282+vdtqbS0NPTt2xfOzs7w9PREYmIiTp482WTMzZs3MXXqVHTs2BFOTk548sknUVJS0mSMSqXC8OHD4ejoCE9PT8yaNQv19fVNxmRmZiI6OhoymQzBwcH45JNPbotn2bJlCAoKglwuR2xsLA4cOGD079wSy5cvR3h4uK7xXlxcHH7++Wfd6+3xmOgzf/58SCQSzJw5U7etvR6b119/HRKJpMmjR48eutfb63Eh68Acqh9zqH7MoS3DHPo7q8uhQju3efNm4f/9v/8nfPfddwIAYcOGDU1enz9/vuDi4iJs3LhROHz4sDBy5EihS5cuwo0bN3Rjhg4dKkRERAj79u0Tdu3aJQQHBwtjx47VvV5eXi54eXkJ48aNE/Lz84WvvvpKcHBwED744APdmD179gg2NjbC22+/LRw7dkx49dVXBTs7O+HIkSMmPwb6JCQkCB9//LGQn58v5ObmCsOGDRMCAgKEqqoq3ZjJkycL/v7+QkZGhnDo0CGhf//+woABA3Sv19fXC7179xbi4+OFnJwcYfPmzYKHh4eQmpqqG3Pu3DnB0dFRSElJEY4dOyYsWbJEsLGxEdLT03Vj1q5dK9jb2wurV68Wjh49KkyaNElwdXUVSkpK2uZg/MEPP/wg/PTTT8KpU6eEkydPCrNnzxbs7OyE/Px8QRDa5zH5swMHDghBQUFCeHi4MGPGDN329nps5s6dK/Tq1UsoKirSPa5cuaJ7vb0eF7IOzKH6MYfqxxx6d8yhTVlbDm33hdcf/TlpaLVawdvbW3jnnXd02zQajSCTyYSvvvpKEARBOHbsmABAOHjwoG7Mzz//LEgkEuHSpUuCIAjC+++/L7i5uQk1NTW6Ma+88ooQEhKie/6Xv/xFGD58eJN4YmNjhb/97W9G/Y6tVVpaKgAQduzYIQjCreNgZ2cnrF+/Xjfm+PHjAgAhKytLEIRbCVkqlQrFxcW6McuXLxcUCoXuWPzzn/8UevXq1eSzkpKShISEBN3zfv36CVOnTtU9b2hoEHx8fIS0tDTjf9FWcHNzEz788EMeE0EQKisrhe7duwtbt24VBg8erEsa7fnYzJ07V4iIiND7Wns+LmR9mEObxxzaPObQ3zGH3s7acmi7n2p4J+fPn0dxcTHi4+N121xcXBAbG4usrCwAQFZWFlxdXdGnTx/dmPj4eEilUuzfv1835oEHHoC9vb1uTEJCAk6ePAm1Wq0b88fPaRzT+DliKy8vBwC4u7sDALKzs1FXV9ck5h49eiAgIKDJsQkLC4OXl5duTEJCAioqKnD06FHdmDt979raWmRnZzcZI5VKER8fL/qxaWhowNq1a1FdXY24uDgeEwBTp07F8OHDb4u/vR+b06dPw8fHB127dsW4ceOgUqkA8LiQdWMO/R1z6O2YQ2/HHKqfNeVQFl53UFxcDABN/mM1Pm98rbi4GJ6enk1et7W1hbu7e5Mx+vbxx89obkzj62LSarWYOXMmBg4ciN69ewO4Fa+9vT1cXV2bjP3zsWnt966oqMCNGzdQVlaGhoYGszo2R44cgZOTE2QyGSZPnowNGzYgNDS0XR8TAFi7di2USiXS0tJue609H5vY2Fh88sknSE9Px/Lly3H+/Hncf//9qKysbNfHhawfc+gtzKFNMYfqxxyqn7XlUFuDRlO7NHXqVOTn52P37t1ih2IWQkJCkJubi/LycnzzzTcYP348duzYIXZYoiosLMSMGTOwdetWyOVyscMxK4899pju7/DwcMTGxiIwMBBff/01HBwcRIyMiNoCc2hTzKG3Yw5tnrXlUF7xugNvb28AuG11lJKSEt1r3t7eKC0tbfJ6fX09rl271mSMvn388TOaG9P4ulimTZuGTZs2Yfv27fDz89Nt9/b2Rm1tLTQaTZPxfz42rf3eCoUCDg4O8PDwgI2NjVkdG3t7ewQHByMmJgZpaWmIiIjA4sWL2/Uxyc7ORmlpKaKjo2FrawtbW1vs2LED//vf/2BrawsvL692e2z+zNXVFffddx/OnDnTrv/NkPVjDmUO1Yc59HbMoS1n6TmUhdcddOnSBd7e3sjIyNBtq6iowP79+xEXFwcAiIuLg0ajQXZ2tm7Mtm3boNVqERsbqxuzc+dO1NXV6cZs3boVISEhcHNz04354+c0jmn8nLYmCAKmTZuGDRs2YNu2bejSpUuT12NiYmBnZ9ck5pMnT0KlUjU5NkeOHGmSVLdu3QqFQoHQ0FDdmDt9b3t7e8TExDQZo9VqkZGRIdqx+TOtVouampp2fUyGDBmCI0eOIDc3V/fo06cPxo0bp/u7vR6bP6uqqsLZs2fRuXPndv1vhqwfcyhzaEswhzKHGsLic6hBS3FYocrKSiEnJ0fIyckRAAgLFy4UcnJyhIKCAkEQbi2F6+rqKnz//fdCXl6eMGrUKL1L4UZFRQn79+8Xdu/eLXTv3r3JUrgajUbw8vISnnnmGSE/P19Yu3at4OjoeNtSuLa2tsKCBQuE48ePC3PnzhV1KdwpU6YILi4uQmZmZpMlPK9fv64bM3nyZCEgIEDYtm2bcOjQISEuLk6Ii4vTvd64hOejjz4q5ObmCunp6UKnTp30LuE5a9Ys4fjx48KyZcv0LuEpk8mETz75RDh27JjwwgsvCK6urk1WqGkr//rXv4QdO3YI58+fF/Ly8oR//etfgkQiEbZs2SIIQvs8Js3544pMgtB+j83f//53ITMzUzh//rywZ88eIT4+XvDw8BBKS0sFQWi/x4WsA3Oofsyh+jGHthxz6C3WlkPbfeG1fft2AcBtj/HjxwuCcGs53Ndee03w8vISZDKZMGTIEOHkyZNN9nH16lVh7NixgpOTk6BQKIQJEyYIlZWVTcYcPnxYGDRokCCTyQRfX19h/vz5t8Xy9ddfC/fdd59gb28v9OrVS/jpp59M9r3vRt8xASB8/PHHujE3btwQXnzxRcHNzU1wdHQUnnjiCaGoqKjJfi5cuCA89thjgoODg+Dh4SH8/e9/F+rq6pqM2b59uxAZGSnY29sLXbt2bfIZjZYsWSIEBAQI9vb2Qr9+/YR9+/aZ4mvf1XPPPScEBgYK9vb2QqdOnYQhQ4boEoYgtM9j0pw/J432emySkpKEzp07C/b29oKvr6+QlJQknDlzRvd6ez0uZB2YQ/VjDtWPObTlmENvsbYcKhEEQTDsGhkREREREREZgvd4ERERERERmRgLLyIiIiIiIhNj4UVERERERGRiLLyIiIiIiIhMjIUXERERERGRibHwIiIiIiIiMjEWXkRERERERCbGwouIiIiIiMjEWHgRmcCzzz6LxMREscMgIiKyOMyhZK1YeBEREREREZkYCy+ie/DNN98gLCwMDg4O6NixI+Lj4zFr1ix8+umn+P777yGRSCCRSJCZmQkAKCwsxF/+8he4urrC3d0do0aNwoULF3T7azzLN2/ePHTq1AkKhQKTJ09GbW3tHT+zurq6jb85ERHRvWEOpfbGVuwAiCxVUVERxo4di7fffhtPPPEEKisrsWvXLiQnJ0OlUqGiogIff/wxAMDd3R11dXVISEhAXFwcdu3aBVtbW7z55psYOnQo8vLyYG9vDwDIyMiAXC5HZmYmLly4gAkTJqBjx4546623mv1MQRDEPBREREQGYQ6ldkkgolbJzs4WAAgXLly47bXx48cLo0aNarLt888/F0JCQgStVqvbVlNTIzg4OAi//PKL7n3u7u5CdXW1bszy5csFJycnoaGh4Y6fSUREZCmYQ6k94lRDolaKiIjAkCFDEBYWhjFjxmDVqlVQq9XNjj98+DDOnDkDZ2dnODk5wcnJCe7u7rh58ybOnj3bZL+Ojo6653FxcaiqqkJhYaHBn0lERGSOmEOpPWLhRdRKNjY22Lp1K37++WeEhoZiyZIlCAkJwfnz5/WOr6qqQkxMDHJzc5s8Tp06haefftokn0lERGSOmEOpPWLhRXQPJBIJBg4ciHnz5iEnJwf29vbYsGED7O3t0dDQ0GRsdHQ0Tp8+DU9PTwQHBzd5uLi46MYdPnwYN27c0D3ft28fnJyc4O/vf8fPJCIisiTModTesPAiaqX9+/fjP//5Dw4dOgSVSoXvvvsOV65cQc+ePREUFIS8vDycPHkSZWVlqKurw7hx4+Dh4YFRo0Zh165dOH/+PDIzMzF9+nRcvHhRt9/a2lpMnDgRx44dw+bNmzF37lxMmzYNUqn0jp9JRERkKZhDqT3iqoZEraRQKLBz504sWrQIFRUVCAwMxLvvvovHHnsMffr0QWZmJvr06YOqqips374dDz74IHbu3IlXXnkFo0ePRmVlJXx9fTFkyBAoFArdfocMGYLu3bvjgQceQE1NDcaOHYvXX3/9rp9JRERkKZhDqT2SCALX0CQyF88++yw0Gg02btwodihEREQWhTmUzB2nGhIREREREZkYCy8iIiIiIiIT41RDIiIiIiIiE+MVLyIiIiIiIhNj4UVERERERGRiLLyIiIiIiIhMjIUXERERERGRibHwIiIiIiIiMjEWXkRERERERCbGwouIiIiIiMjEWHgRERERERGZGAsvIiIiIiIiE2PhRUREREREZGIsvIiIiIiIiEyMhRcREREREZGJsfAiIiIiIiIyMRZeREREREREJsbCi4iIiIiIyMRYeBEREREREZkYCy8iIiIiIiITY+FFRERERERkYiy8iIiIiIiITIyFFxERERERkYmx8CIiIiIiIjIxFl5EREREREQmxsKLiIiIiIjIxFh4ERERERERmRgLLyIiIiIiIhOzFTsAc6TVanH58mU4OztDIpGIHQ4R0T0TBAGVlZXw8fGBVMpzbmQ6zKFEZG2MlUNZeOlx+fJl+Pv7ix0GEZHRFRYWws/PT+wwyIoxhxKRtbrXHMrCSw9nZ2cAtw6uQqEQORoiontXUVEBf39/3e8bkakwhxKRtTFWDmXhpUfj1AiFQsGkQURWhVO/yNSYQ4nIWt1rDuVEfyIiIiIiIhNj4UVERERERGRiLLyIiIiIiIhMjIUXERERERGRibHwIiIiIiIiMjEWXkRERERERCbGwouIiIiIiMjEWHgRERERERGZGAsvIiIiIiIiE2PhRUREREREZGIsvIiIiIiIiEyMhZcRCYKA0sqbYodBRERkcZhDicjasfAyopU7z2HIuzug1Qpih0JERGRRmEOJyNqx8DKi3r4uqLxZj7NXqsQOhYiIyKIwhxKRtWPhZUQR/q6QSgClSi12KERERBaFOZSIrB0LLyNyktniPi9nKAs0YodCRERkUZhDicjasfAysuhAN56tIyIiagXmUCKyZiy8jCw6wA2nS6tQfqNO7FCIiIgsii6HXmcOJSLrw8LLyKIDXAEAuYUaUeMgIiKyNI05NKeQV72IyPqw8DKyLh4d4OZoB2UBkwYREYlr+fLlCA8Ph0KhgEKhQFxcHH7++edmx3/yySeQSCRNHnK5vM3i1eVQlabNPpOIqK3Yih2AtZFIJIgK4Bx1IiISn5+fH+bPn4/u3btDEAR8+umnGDVqFHJyctCrVy+971EoFDh58qTuuUQiaatwIZFIEB3ghhzmUCKyQiy8TCA6wBUf7DwHrVaAVNp2CYuIiOiPRowY0eT5W2+9heXLl2Pfvn3NFl4SiQTe3t5tEZ5e0YFuWJF5ljmUiKwOpxqaQHSAGypv1uMMm0ASEZGZaGhowNq1a1FdXY24uLhmx1VVVSEwMBD+/v4YNWoUjh49esf91tTUoKKiosnjXkQFuKKyph6nS5lDici6sPAyAV0TSN7nRUREIjty5AicnJwgk8kwefJkbNiwAaGhoXrHhoSEYPXq1fj+++/xxRdfQKvVYsCAAbh48WKz+09LS4OLi4vu4e/vf0/xRvixkTIRWScWXibQQWaLEG8FkwYREYkuJCQEubm52L9/P6ZMmYLx48fj2LFjesfGxcUhOTkZkZGRGDx4ML777jt06tQJH3zwQbP7T01NRXl5ue5RWFh4T/F2kNmih7eCJy+JyOrwHi8TiQ5wxf7z18QOg4iI2jl7e3sEBwcDAGJiYnDw4EEsXrz4jsVUIzs7O0RFReHMmTPNjpHJZJDJZEaLFwCiA12x9+xVo+6TiEhsvOJlItEBbjjDJpBERGRmtFotampqWjS2oaEBR44cQefOnU0cVVPRAW44d6Uamuu1bfq5RESmxMLLRKID3QCwCSQREYknNTUVO3fuxIULF3DkyBGkpqYiMzMT48aNAwAkJycjNTVVN/7f//43tmzZgnPnzkGpVOKvf/0rCgoK8Pzzz7dp3NEBv+VQ9vMiIivCqYYmEtTREe4d7KFUafBgiKfY4RARUTtUWlqK5ORkFBUVwcXFBeHh4fjll1/wyCOPAABUKhWk0t/PwarVakyaNAnFxcVwc3NDTEwM9u7d2+xiHKYS2NERHTvYQ6lS46EezKFEZB1YeJmIRCJBlL8rbw4mIiLRfPTRR3d8PTMzs8nz9957D++9954JI2oZiUSCqAA3LlJFRFaFUw1NKDrQDbmFGjRoBbFDISIisijRga7IVTGHEpH1YOFlQlEBrqiqqcfp0kqxQyEiIrIo0QFuqK5twKkS5lAisg4svExI1wSyQCN2KERERBYl3M8FNlIJpxsSkdVg4WVCuiaQTBpEREQGcbS3Rc/OzsjmvdJEZCVYeJlYdKArCy8iIqJWiA5w45LyRGQ1WHiZGJtAEhERtU50gBvOl1XjWjVzKBFZPhZeJsYmkERERK0TE9iYQzlzhIgsHwsvEwvUNVJm0iAiIjKEn5sDPJxkzKFEZBVYeJmYRCJBNJtAEhERGexWDnXl6sBEZBXMvvBavnw5wsPDoVAooFAoEBcXh59//rnZ8Z988gkkEkmTh1wub8OIb8cmkERERK0THeiGwxc1qG/Qih0KEdE9MfvCy8/PD/Pnz0d2djYOHTqEhx9+GKNGjcLRo0ebfY9CoUBRUZHuUVBQ0IYR345NIImIiFonOsAN12sbcJI5lIgsnK3YAdzNiBEjmjx/6623sHz5cuzbtw+9evXS+x6JRAJvb++2CK9F/tgEsmdnhdjhEBERWYxwPxfYSiVQFqjRy8dF7HCIiFrN7K94/VFDQwPWrl2L6upqxMXFNTuuqqoKgYGB8Pf3v+vVMQCoqalBRUVFk4cxNTaB5Bx1IiIiw8jtbBDqo4CSqwMTkYWziMLryJEjcHJygkwmw+TJk7FhwwaEhobqHRsSEoLVq1fj+++/xxdffAGtVosBAwbg4sWLze4/LS0NLi4uuoe/v7/Rv8OtJpBcYIOIiMhQXKSKiKyBRRReISEhyM3Nxf79+zFlyhSMHz8ex44d0zs2Li4OycnJiIyMxODBg/Hdd9+hU6dO+OCDD5rdf2pqKsrLy3WPwsJCo3+H6AA3nCurhppNIImIiAwSHeiGgqvXUVZVI3YoREStZhGFl729PYKDgxETE4O0tDRERERg8eLFLXqvnZ0doqKicObMmWbHyGQy3aqJjQ9j0zVSLuQZOyIiIkNEB7gCAHI43ZCILJhFFF5/ptVqUVPTsrNeDQ0NOHLkCDp37mziqO7M390BHk72vM+LiIjIQL6uDvB0ZiNlIrJsZr+qYWpqKh577DEEBASgsrISa9asQWZmJn755RcAQHJyMnx9fZGWlgYA+Pe//43+/fsjODgYGo0G77zzDgoKCvD888+L+TUgkUgQxTnqREREBrvVSNkNygLmUCKyXGZfeJWWliI5ORlFRUVwcXFBeHg4fvnlFzzyyCMAAJVKBan09wt3arUakyZNQnFxMdzc3BATE4O9e/c2uxhHW4oOcMPSbafRoBVgI5WIHQ4REZHFiA50xXtbT6OuQQs7G4ucsENE7ZzZF14fffTRHV/PzMxs8vy9997De++9Z8KIWi86wBXVtQ04WVyJUB/28yIiImqp6AA33KhrwImiSoT5sZ8XEVkenjJqQ+F+rreaQHK6IRERkUF6+7rAzoY5lIgsFwuvNuRgb4OenRVMGkRERAa61UjZhTmUiCwWC682Fh3gyuVwiYiIWiGGi1QRkQVj4dXGogPdcL6sGtfYSJmIiMgg0YGuKLx2A1cq2UiZiCwPC682pmukzDN2REREBmnMobzqRUSWiIVXG/Nzc4CHkwzZ7EVCRERkEB9XB3gr5Cy8iMgisfBqY7eaQLoyaRAREbVCdKArcgo0YodBRGQwFl4iiA50w+HCctQ3aMUOhYiIyKJEB7jh8EUNauuZQ4nIsrDwEoGuCWRxpdihEBERWZSoADfU1GtxvKhC7FCIiAzCwksE4X4usJVKuMAGERGRgXr7KmBvI+WUfSKyOCy8RHCrCaQCSvbzIiIiMojM1ga9fZlDicjysPASSTSbQBIREbVKdIAblFwdmIgsDAsvkUQFuKLg6nWUVbEJJBERkSGiA91wSXMDpRU3xQ6FiKjFWHiJ5PdGyhpxAyEiIrIwbKRMRJaIhZdI/Nwc0MlZxqRBRERkIG8XOXxc5LzPi4gsCgsvkUgkEsRwjjoREVGrRAW6IZs5lIgsCAsvEUUHuiLvIhspExERGSo6wA1HLpWzkTIRWQwWXiJiI2UiIqLWiQ5wRW29Fkcvl4sdChFRi7DwElFvXxfY2Uh4nxcREZGBevm4wN5Wyvu8iMhisPAS0a1Gyi68z4uIiMhA9rZShPu68OQlEVkMFl4iiw5w5dk6IiKiVogOdEMOT14SkYVg4SWy6AA3qK6xkTIREZGhogNccbn8JorL2UiZiMwfCy+RRQf+1gSSZ+yIiIgMwkbKRGRJWHiJzMdFDi+FjNMNiYiIDOSpkMPX1YEnL4nIIrDwEplEIkF0gBvP1hEREbVCdKAbsplDicgCsPAyA9EBbsi7qEEdGykTEZERLV++HOHh4VAoFFAoFIiLi8PPP/98x/esX78ePXr0gFwuR1hYGDZv3txG0bZOdIArjl6qQE19g9ihEBHdEQsvMxAd6IqbdVqcKGIjZSIiMh4/Pz/Mnz8f2dnZOHToEB5++GGMGjUKR48e1Tt+7969GDt2LCZOnIicnBwkJiYiMTER+fn5bRx5y8UEuqG2QYv8SxVih0JEdEcsvMxALx82UiYiIuMbMWIEhg0bhu7du+O+++7DW2+9BScnJ+zbt0/v+MWLF2Po0KGYNWsWevbsiTfeeAPR0dFYunRpG0fecj07KyC3kyKHOZSIzBwLLzMgt7NBLx82gSQiItNpaGjA2rVrUV1djbi4OL1jsrKyEB8f32RbQkICsrKy2iLEVrGzkSLc15U5lIjMnq3YAdAt0QFu2Hq8WOwwiIjIyhw5cgRxcXG4efMmnJycsGHDBoSGhuodW1xcDC8vrybbvLy8UFzcfH6qqalBTc3vvSgrKtp+yl9UoCu+z7nc5p9LRGQIXvEyE9GBrii8dgNXKtlImYiIjCckJAS5ubnYv38/pkyZgvHjx+PYsWNG239aWhpcXFx0D39/f6Ptu6WiA9xQXHETlzU32vyziYhaioWXmWATSCIiMgV7e3sEBwcjJiYGaWlpiIiIwOLFi/WO9fb2RklJSZNtJSUl8Pb2bnb/qampKC8v1z0KCwuNGn9LMIcSkSVg4WUmfFwd4K2QswkkERGZlFarbTI18I/i4uKQkZHRZNvWrVubvScMAGQymW65+sZHW+vkLIO/uwOymUOJyIzxHi8zEh3Im4OJiMh4UlNT8dhjjyEgIACVlZVYs2YNMjMz8csvvwAAkpOT4evri7S0NADAjBkzMHjwYLz77rsYPnw41q5di0OHDmHlypVifo0WiQ5wg1KlETsMIqJm8YqXGbnVSLkctfVspExERPeutLQUycnJCAkJwZAhQ3Dw4EH88ssveOSRRwAAKpUKRUVFuvEDBgzAmjVrsHLlSkREROCbb77Bxo0b0bt3b7G+QovFBLrh2OVy3KxjI2UiMk+84mVGogLcUFOvxfGiCkT4u4odDhERWbiPPvrojq9nZmbetm3MmDEYM2aMiSIynegAN9Q1CMi/VI4+Qe5ih0NEdBte8TIjvX0VsLeRcrohERGRgXp4O8PBzoY5lIjMFgsvMyKztUEvXwXnqBMRERnI1kaKcD8XKAs0YodCRKQXCy8zEx3gxpUNiYiIWiE60A1KlRqCIIgdChHRbVh4mZnoADdc0vz/9u49Pqr6zv/4eyaXSUIymYmQG2RGLMqdmIOXBrYWFyqiPxe2W9tlqVhX+6v7C1strnbT7q+2292Nj+6yaNVirWvzc30oKhXchyKSCgEvYAtJ5KYoJZIASfBCrpDrnN8fIUOGJJAJmZyZzOv5eJzKOed7zvnM0frxM3O+53NaJxpbrQ4FAICIYnjcOtHUpmM0UgYQhii8wozhdUmiCSQAAMHK87gkiX5eAMIShVeYyUpNVHZqAvO8AAAI0thkh7yXJKmcHAogDFF4haE8L/O8AAAYitkeN0+NAAhLFF5hyPC4tecYjZQBAAhWntetA8cbaaQMIOxQeIUhw+NSe6dPB2oarQ4FAICIYnhc6vSZ2nO0wepQACBA2Bdea9as0axZs+R0OuV0OpWfn6/XX3/9vMe89NJLmjJlihISEjRz5kxt3LhxhKIdHtOzUxUfa+dxQwAAgjQ5I0VJ8TRSBhB+wr7wmjBhgh566CHt3r1bu3bt0p//+Z9r8eLF2r9/f7/j3333XS1dulR33nmnysvLtWTJEi1ZskT79u0b4ciHLj7WrpnjU0kaAAAEKTbGrtwJLr68BBB2wr7wuuWWW3TTTTfp8ssv1xVXXKF//dd/VXJysnbu3Nnv+EceeUQ33nij7r//fk2dOlU///nPZRiGHnvssRGO/OIYHhdvZQIAYAgMr0tlVfU0UgYQVsK+8Oqtq6tLa9euVUtLi/Lz8/sds2PHDi1YsCBg28KFC7Vjx46RCHHY9DRSrqORMgAAQTE8bn3W3KbqL2ikDCB8xFodwGDs3btX+fn5am1tVXJystavX69p06b1O7a2tlYZGRkB2zIyMlRbWzvg+dva2tTW1uZfb2y0/qUWhtctSSo7clKLZmZZHA0AAJEjz3Mmh1adlOeSJIujAYBuEfGL1+TJk1VRUaH33ntPf/d3f6fbb79dBw4cGLbzFxUVKTU11b/k5OQM27mHKsOZoPGuROZ5AQAQpLQx8bps7BhyKICwEhGFV3x8vCZNmqTZs2erqKhIubm5euSRR/odm5mZqbq6uoBtdXV1yszMHPD8hYWFamho8C/V1dXDGv9Q5Xm6n1EHAADByaORMoAwExGF17l8Pl/Ao4G95efn68033wzYVlJSMuCcMElyOBz+19X3LOHA8Li1l0bKAAAEzfC69EFNk061d1odCgBIioDCq7CwUNu3b9cnn3yivXv3qrCwUKWlpVq2bJkkafny5SosLPSPv+eee7Rp0yatWrVKH374oX76059q165dWrFihVUfYcgMr1vtnT7tP04TSAAAgmF43OqikTKAMBL2hdeJEye0fPlyTZ48WfPnz9cf//hHvfHGG/ra174mSaqqqlJNTY1//Jw5c/Tcc8/pySefVG5urtatW6cNGzZoxowZVn2EIZuW5ZQj1s7jhgAABOmKjBQlO2J53BBA2Aj7txr+13/913n3l5aW9tl266236tZbbw1RRCOndyPlOzXR6nAAAIgYMXabcnNSVXak3upQAEBSBPziFe0Mr1vlR/i2DgCAYBlnXrBBI2UA4YDCK8wZHpeON7SqpoEmkAAABMPwuPVFS7uOfH7K6lAAgMIr3Bk9TSB5VAIAgKDkeVySxDwvAGGBwivMpdNIGQCAIXElxetL42ikDCA8UHhFAMNLE0gAAIbC8Lh5agRAWKDwigCGx6X9xxrV1tlldSgAAEQUw+vWh7WNammjkTIAa1F4RQDD41Z7l0/7jjVaHQoAABHF8LjlM6X3j9ZbHQqAKEfhFQGmnmmkXM7jhgAABOXy9GSlOGJVXlVvdSgAohyFVwSIj7Vr1oRU5nkBABAku92mKz0u7aYnJgCLUXhFCCYHAwAwNHket8pppAzAYhReESLP41ZtY6uO19NIGQCAYMz2unXyVIcqP2uxOhQAUYzCK0IYXpckmkACABCsK3NckqQy5nkBsBCFV4RIT0lQTloijxsCABCk1MQ4XZ6ezJeXACxF4RVBDA+NlAEAGIruudLkUADWofCKIIbHrf3HG9TaQSNlAACCYXhd+qiuSc00UgZgEQqvCGJ43OroMrX/eIPVoQAAEFH8jZSr660OBUCUovCKIFOyUpQQZ2eeFwAAQfrSuGQ5E2Lp5wXAMhReESQuxq5ZE1zM8wIAIEjdjZSZKw3AOhReEabnBRs0gQQAIDizPW6VV9XL5yOHAhh5FF4RxvC4VNfYpuMNrVaHAgBARDG8LjWc7tBhGikDsACFV4QxvG5J4pW4AAAE6cocl2w28bghAEtQeEWYsckOedKSSBoAAAQpJSFOV6SnqJwcCsACFF4RyPC4VFZVb3UYAABEHMPr4u3AACxB4RWBDK9bB2ikDABA0PI8bn10okmNrR1WhwIgylB4RaCeRsr7jtFIGQCAYBget0xTquDJEQAjjMIrAk3JTFFiXAzzvAAA51VUVKSrr75aKSkpSk9P15IlS3Tw4MHzHlNcXCybzRawJCQkjFDEoXfZ2DFKTYwjhwIYcRReESg2xq5ZE1J5Rh0AcF7btm1TQUGBdu7cqZKSEnV0dOiGG25QS8v5X6fudDpVU1PjX44cOTJCEYee3W5jrjQAS8RaHQCGxvC6tW73UZmmKZvNZnU4AIAwtGnTpoD14uJipaena/fu3bruuusGPM5msykzMzPU4VnG8Lj15FuH5fOZstvJoQBGBr94RSjD49anTW06evK01aEAACJEQ0P33OC0tLTzjmtubpbX61VOTo4WL16s/fv3j0R4I8bwutXU2qk/fdpsdSgAogiFV4TK87gk0QQSADA4Pp9P9957r+bOnasZM2YMOG7y5Ml6+umn9corr+jZZ5+Vz+fTnDlzdPTo0X7Ht7W1qbGxMWAJd7k5LtlppAxghFF4RaixyQ55L0lSOc+oAwAGoaCgQPv27dPatWvPOy4/P1/Lly/XlVdeqa9+9at6+eWXNW7cOP3617/ud3xRUZFSU1P9S05OTijCH1bJjlhdkZHCXGkAI4rCK4IZHjff1gEALmjFihV69dVXtXXrVk2YMCGoY+Pi4pSXl6dDhw71u7+wsFANDQ3+pbq6ejhCDjnDSw4FMLIovCKY4XHpwPFGGikDAPplmqZWrFih9evXa8uWLZo4cWLQ5+jq6tLevXuVlZXV736HwyGn0xmwRALD49bHJ5rVcIpGygBGBoVXBMvzuNXpM7XnKI2UAQB9FRQU6Nlnn9Vzzz2nlJQU1dbWqra2VqdPn30x0/Lly1VYWOhf/+d//mdt3rxZhw8fVllZmb797W/ryJEjuuuuu6z4CCFjnJkrXV7Nr14ARgaFVwSbkpmipHgaKQMA+rdmzRo1NDRo3rx5ysrK8i8vvPCCf0xVVZVqamr86ydPntR3v/tdTZ06VTfddJMaGxv17rvvatq0aVZ8hJCZOHaM3Elx9PMCMGLo4xXBzjZSpvACAPRlmuYFx5SWlgasr169WqtXrw5RROHDZrPJ8LhVzpeXAEYIv3hFuO4XbNQPKrkCAICzDK9bFVX18vnIoQBCj8Irwhketz5rppEyAADByvO41NTWqY9P0EgZQOhReEU4w+uWRBNIAACClTuBRsoARg6FV4RLGxOviWPHMM8LAIAgjXHEakqmkxwKYERQeI0CeR4Xb2UCAGAIDK9Lu/nFC8AIoPAaBQyPWx/UNOp0O42UAQAIhuFx6/CnLao/1W51KABGOQqvUcDwN1KutzoUAAAiyuwzc6XLeXIEQIhReI0CkzNTNCY+hscNAQAIkictSZeMiecFGwBCjsJrFIix25Sb4yJpAAAQJJvNpjyPmxwKIOQovEYJw+NWedVJGikDABAkw+tSRVW9umikDCCEwr7wKioq0tVXX62UlBSlp6dryZIlOnjw4HmPKS4uls1mC1gSEhJGKGJrGF6XPmtuV/UXNFIGACAYhsetlvYufVTXZHUoAEaxsC+8tm3bpoKCAu3cuVMlJSXq6OjQDTfcoJaWlvMe53Q6VVNT41+OHDkyQhFbIy+HRsoAAAzFrAmpirHbyKEAQirW6gAuZNOmTQHrxcXFSk9P1+7du3XdddcNeJzNZlNmZmaowwsb7jHxumzsGJVVndSSvPFWhwMAQMRIio/V1KwU7T5yUsuu9VodDoBRKux/8TpXQ0ODJCktLe2845qbm+X1epWTk6PFixdr//79A45ta2tTY2NjwBKJmBwMAMDQdM+Vrrc6DACjWEQVXj6fT/fee6/mzp2rGTNmDDhu8uTJevrpp/XKK6/o2Weflc/n05w5c3T06NF+xxcVFSk1NdW/5OTkhOojhJThdemDmiadau+0OhQAACLKbK9blZ+16IsWGikDCI2IKrwKCgq0b98+rV279rzj8vPztXz5cl155ZX66le/qpdfflnjxo3Tr3/9637HFxYWqqGhwb9UV1eHIvyQMzxudflM7TnaYHUoAABEFMPT00iZJ0cAhEZIC6///u//1ty5c5Wdne1/ucXDDz+sV155JehzrVixQq+++qq2bt2qCRMmBHVsXFyc8vLydOjQoX73OxwOOZ3OgCUSXZGRomRHLI8bAgAQpAnuRI1NdpBDAYRMyAqvNWvWaOXKlbrppptUX1+vrq4uSZLL5dLDDz886POYpqkVK1Zo/fr12rJliyZOnBh0LF1dXdq7d6+ysrKCPjaSdDdSTlXZkXqrQwEAIKLYbDYZHhc5FEDIhKzwevTRR/Wb3/xGP/7xjxUTE+PfftVVV2nv3r2DPk9BQYGeffZZPffcc0pJSVFtba1qa2t1+vTZflXLly9XYWGhf/2f//mftXnzZh0+fFhlZWX69re/rSNHjuiuu+4ang8XxowzL9igkTIARLbhfGoEg2N43Xr/aL06u3xWhwJgFApZ4VVZWam8vLw+2x0OxwV7cPW2Zs0aNTQ0aN68ecrKyvIvL7zwgn9MVVWVampq/OsnT57Ud7/7XU2dOlU33XSTGhsb9e6772ratGkX96EigOFx64uWdh35/JTVoQAAhmi4nhpBcAyPW6fau3SQRsoAQiBkfbwmTpyoiooKeb2B/TA2bdqkqVOnDvo8g/nlprS0NGB99erVWr169aCvMZrkeVySuhspXzp2jLXBAACGpOepkSVLluihhx7yb7/qqqv0D//wDxZGNrrNmpCqWLtNZUdOanp2qtXhABhlQlZ4rVy5UgUFBWptbZVpmvrDH/6g559/XkVFRXrqqadCddmo50qK12Xjuhspf90I7iUkAIDwMFxPjSA4CXExmpbtVFlVvW7LtzoaAKNNyAqvu+66S4mJifqnf/onnTp1Sn/zN3+j7OxsPfLII/rrv/7rUF0WOjPPi8nBABCxhuupEQTP8Li19eAJq8MAMAqFrPCSpGXLlmnZsmU6deqUmpublZ6eHsrL4QzD49bLZUfV0tapMY6Q/i0GAIQAT41Yx/C6VfzuJ/qsuU1jkx1WhwNgFAnZf5WfPn1apmkqKSlJSUlJ+vTTT/Xwww9r2rRpuuGGG0J1WUgyvC75TOn9o/Wa86WxVocDAAgST41YxzgzV7q8ql5fm5ZhbTAARpWQvdVw8eLFeuaZZyRJ9fX1uuaaa7Rq1SotXrxYa9asCdVlIeny9O5GyuVV9VaHAgAYomXLlunjjz9Wc3OzamtrdfToUd15551WhzXqjXclKj2FRsoAhl/ICq+ysjJ95StfkSStW7dOmZmZOnLkiJ555hn98pe/DNVloe5GylfmuFR2hKQBAJEuKSmJR/VHUHcjZTc5FMCwC9mjhqdOnVJKSookafPmzfr6178uu92uL3/5y/5GkAgdw+PSs+9VyTRN2Ww2q8MBAARp3bp1evHFF1VVVaX29vaAfWVlZRZFFR0Mr0urSz5WR5dPcTEh+44aQJQJ2b9NJk2apA0bNqi6ulpvvPGGf17XiRMn5HQ6Q3VZnGF4uxspf0IjZQCIOL/85S91xx13KCMjQ+Xl5brmmmt0ySWX6PDhw1q0aJHV4Y16hset0x1d+rCGRsoAhk/ICq+f/OQn+od/+Addeumluvbaa5Wf390QY/Pmzf32JsHwystxSxKPSgBABPrVr36lJ598Uo8++qji4+P1wAMPqKSkRN///vfV0NBgdXij3ozxqYqLsTHPC8CwClnh9Y1vfENVVVXatWuXNm3a5N8+f/58rV69OlSXxRmpSXGalJ5M0gCACFRVVaU5c+ZIkhITE9XU1P3Ly2233abnn3/eytCiQkJcjKZnp5JDAQyrkD64nJmZqby8PNntZy9zzTXXaMqUKaG8LM4wPC6V8WZDAIg4mZmZ+uKLLyRJHo9HO3fulCRVVlbKNE0rQ4sahsdN4QVgWIXs5Rqtra169NFHtXXrVp04cUI+ny9gPxODQ8/wuLVu91E1t3UqmUbKABAx/vzP/1z/8z//o7y8PN1xxx36wQ9+oHXr1mnXrl36+te/bnV4UcHwuvT0O5X6tKlN41JopAzg4oXsv8bvvPNObd68Wd/4xjd0zTXX8GY9Cxhet3ymtKe6XnMm0UgZACLFk08+6f/CsqCgQGPHjtU777yjv/iLv9Ddd99tcXTRwfCcmStddVILp2daHA2A0SBkhderr76qjRs3au7cuaG6BC5g0rhkpSTEqqzqJIUXAEQQu92u9vZ2lZWV6cSJE0pMTNSCBQskSZs2bdItt9xicYSjX7YrUZnOBAovAMMmZIXX+PHj/X28YA17TyNl5nkBQETZtGmTbrvtNn3++ed99tlsNnV1dVkQVfQxvC6VH6m3OgwAo0TIXq6xatUq/fCHP6RZssUMj1vlVSeZjA0AEeTv//7v9c1vflM1NTXy+XwBC0XXyDE8br1/tF7tnb4LDwaACwhZ4XXVVVeptbVVl112mVJSUpSWlhawYGQYXrdOnupQ5WctVocCABikuro6rVy5UhkZGVaHEtXyPG61dfr0QU2j1aEAGAVC9qjh0qVLdezYMf3bv/2bMjIyeLmGRa7McUmSyqrqddm4ZGuDAQAMyje+8Q2VlpbqS1/6ktWhRLUZ452Kj7GrrOqkcs/kUwAYqpAVXu+++6527Nih3NzcUF0Cg5CaGKfLzzRS/sbsCVaHAwAYhMcee0y33nqr3nrrLc2cOVNxcXEB+7///e9bFFl0ccTGaMZ4p8qq6nUH7woDcJFCVnhNmTJFp0+fDtXpEQTD41bZEZpAAkCkeP7557V582YlJCSotLQ04KkRm81G4TWCDI9br++rtToMAKNAyOZ4PfTQQ7rvvvtUWlqqzz//XI2NjQELRo7hdemjuiY1t3VaHQoAYBB+/OMf62c/+5kaGhr0ySefqLKy0r8cPnzY6vCiiuF161j9aZ1obLU6FAARLmS/eN14442SpPnz5wdsN02TV+GOMMPT3Uj5/ep6zaWfFwCEvfb2dn3rW9+S3R6y70cxSL0bKd84I8viaABEspAUXh0dHZKkJ554QpMnTw7FJRCEL41LljMhVmVHTlJ4AUAEuP322/XCCy/oRz/6kdWhRL3M1ARlpyaorKqewgvARQlJ4RUXF6dLLrlE119/vS6//PJQXAJBsNttutLj1u4q5nkBQCTo6urSL37xC73xxhuaNWtWn5dr/Od//qdFkUWnPK9bu5krDeAihewZhm9/+9v6r//6r1CdHkEyPC6VV9XL56ORMgCEu7179yovL092u1379u1TeXm5f6moqBj0eYqKinT11VcrJSVF6enpWrJkiQ4ePHjB41566SVNmTJFCQkJmjlzpjZu3HgRnybyGR639h5roJEygIsSsjlenZ2devrpp/X73/9es2fP1pgxYwL2823dyDI8bj38+491+LMWTUqnnxcAhLOtW7cOy3m2bdumgoICXX311ers7NSPfvQj3XDDDTpw4ECfvNzj3Xff1dKlS1VUVKT/9b/+l5577jktWbJEZWVlmjFjxrDEFWlme91q7/Rp//EG5Z2Z8wUAwQpZ4bVv3z4ZhiFJ+uijjwL20Ux55F3pcclm654cTOEFANFh06ZNAevFxcVKT0/X7t27dd111/V7zCOPPKIbb7xR999/vyTp5z//uUpKSvTYY4/piSeeCHnM4WhallOOWLvKquopvAAMWcgKr+H6tg7Dw5nQ3Ui5vOqkvnlVjtXhAAAs0NDQIElKS0sbcMyOHTu0cuXKgG0LFy7Uhg0bQhlaWIuPtWvm+FSVVZ3UnZpodTgAIlTICi+En+5GyvVWhwEAsIDP59O9996ruXPnnveRwdraWmVkZARsy8jIUG1t/02E29ra1NbW5l8frb06Da9br75/3OowAEQwGoREEcPj1kcnmtTY2mF1KACAEVZQUKB9+/Zp7dq1w3reoqIipaam+pecnNH5VIXhcel4Q6tqG2ikDGBoKLyiiOF1yTzTSBkAED1WrFihV199VVu3btWECRPOOzYzM1N1dXUB2+rq6pSZmdnv+MLCQjU0NPiX6urqYYs7nPRupAwAQ0HhFUUuG9vTSLne6lAAACPANE2tWLFC69ev15YtWzRx4oXnJ+Xn5+vNN98M2FZSUqL8/Px+xzscDjmdzoBlNEp3Jmi8K5F+XgCGjDleUcRutynP4+bbOgCIEgUFBXruuef0yiuvKCUlxT9PKzU1VYmJiZKk5cuXa/z48SoqKpIk3XPPPfrqV7+qVatW6eabb9batWu1a9cuPfnkk5Z9jnBheMmhAIaOX7yijOFxq7zqJI2UASAKrFmzRg0NDZo3b56ysrL8ywsvvOAfU1VVpZqaGv/6nDlz9Nxzz+nJJ59Ubm6u1q1bpw0bNkRtD6/eZntc2n+sUW2dXVaHAiAC8YtXlJntdWv17zt1+LNmTUpPsTocAEAImeaFv2QrLS3ts+3WW2/VrbfeGoKIIpvhdau9y6d9xxo120s/LwDB4RevKJObk9rdSJl5XgAABGVqllMJcXaV87ghgCGg8IoyKQlxmpyRwjPqAAAEKS7GrlnjXeRQAENC4RWFeMEGAABDk+d18dQIgCGh8IpChselj08000gZAIAgGR63ahtbdbz+tNWhAIgwFF5RyPC6ZZpSRVW91aEAABBRehop088LQLAovKLQZWPHyJUUx+OGAAAEaVyKQzlpieRQAEGj8IpCNptNeTkulfGLFwAAQZvtcZNDAQSNwitK0UgZAIChMbxuHTjeoNYOGikDGDwKryhleN1qau3Unz5ttjoUAAAiiuFxq6PL1L5jDVaHAiCCUHhFqdwcl+w28Yw6AABBmpKZosS4GHIogKCEfeFVVFSkq6++WikpKUpPT9eSJUt08ODBCx730ksvacqUKUpISNDMmTO1cePGEYg2ciQ7YnVFRgq9SAAACFJsjF2zJqSSQwEEJewLr23btqmgoEA7d+5USUmJOjo6dMMNN6ilpWXAY959910tXbpUd955p8rLy7VkyRItWbJE+/btG8HIw5/hpZEyAABDYXjd2l11UqbJXGkAgxP2hdemTZv0ne98R9OnT1dubq6Ki4tVVVWl3bt3D3jMI488ohtvvFH333+/pk6dqp///OcyDEOPPfbYCEYe/gyPWx+faFbDaRopAwAQDMPj1qdNbTp6kkbKAAYn7AuvczU0dE9kTUtLG3DMjh07tGDBgoBtCxcu1I4dO0IaW6QxPC5JUkV1vaVxAAAQafLO5FCeHAEwWBFVePl8Pt17772aO3euZsyYMeC42tpaZWRkBGzLyMhQbW1tv+Pb2trU2NgYsESDiWPHyJ0Up7IjJA0AAIIxNtmhSy9JUjn9vAAMUkQVXgUFBdq3b5/Wrl07rOctKipSamqqf8nJyRnW84crm82mPA/zvAAAGAqDHAogCBFTeK1YsUKvvvqqtm7dqgkTJpx3bGZmpurq6gK21dXVKTMzs9/xhYWFamho8C/V1dXDFne4MzwuVVTV00gZAIAg5XndOnC8kUbKAAYl7Asv0zS1YsUKrV+/Xlu2bNHEiRMveEx+fr7efPPNgG0lJSXKz8/vd7zD4ZDT6QxYooXhcauprVMfn6CRMgAAwTA8LnX6TO05SiNlABcW9oVXQUGBnn32WT333HNKSUlRbW2tamtrdfr02bcILV++XIWFhf71e+65R5s2bdKqVav04Ycf6qc//al27dqlFStWWPERwhqNlAEAGJrJGSlKiqeRMoDBCfvCa82aNWpoaNC8efOUlZXlX1544QX/mKqqKtXU1PjX58yZo+eee05PPvmkcnNztW7dOm3YsOG8L+SIVmMcsZqc6eQFGwAABCk2xq7cCS5yKIBBibU6gAsZTGPC0tLSPttuvfVW3XrrrSGIaPQxPC7tPPy51WEAABBxDK9LL/yxWqZpymazWR0OgDAW9r94IfQMj1t/+rRF9afarQ4FAICIYnjc+qy5XdVf0EgZwPlReEGG1y1JKqeRMgAAQcnzdOdQ5nkBuBAKL+jSS5KUNiZe5TyjDgBAUNLGxOuysWMovABcEIUXuhsp57hUVlVvdSgAAEScPBopAxgECi9I6n7csKK6Xl00UgYAICiG16UPapp0qr3T6lAAhDEKL0jqnhzc3Napj080WR0KAAARxfC41UUjZQAXQOEFSVJuTqpi7DaVHam3OhQAACLKFRkpSnbE8rghgPOi8IIkKSk+VlMyU0gaAAAEKcZuU25OKo2UAZwXhRf8DCYHAwAwJN05tF6myVxpAP2j8IKf4XXpMI2UAQAImuFx64uWdh35/JTVoQAIUxRe8DPONIEs57XyAAAEJc/jkkQjZQADo/CCnyctSZeMiSdpAAAQJFdSvL40jkbKAAZG4QU/m81GE0gAAIbI8Lh5OzCAAVF4IYDhdamiikbKAAAEy/C69WFto1raaKQMoC8KLwQwPG61tHfpozoaKQMAEAzD45bPlN4/Wm91KADCEIUXAsyacKaRMo8bAgAQlMvTk5XiiKWfF4B+UXghQFJ8rKZmpfCMOgAAQbLbbbrS41IZbwcG0A8KL/RheNwq5xcvAACClncmh9JIGcC5KLzQh+Fx6/BnLTrZQiNlAACCMdvr1slTHar8rMXqUACEGQov9OFvpFzNr14AAATjyhyXJPG4IYA+KLzQR05aosYmx2s3k4MBAAhKamKcLk9P5iVVAPqg8EIf/kbKvGADAICgdTdSpvACEIjCC/0yPG69f7RenV0+q0MBACCiGF6XPqprUlNrh9WhAAgjFF7ol+Fx6VR7lw7SSBkAgKD4GylXN1gdCoAwQuGFfs2a4FKs3cbkYACIYNu3b9ctt9yi7Oxs2Ww2bdiw4bzjS0tLZbPZ+iy1tbUjE/Ao8aVxyXImxDLPC0AACi/0KzE+RlOznCrnGXUAiFgtLS3Kzc3V448/HtRxBw8eVE1NjX9JT08PUYSjU3cjZTeFF4AAsVYHgPBleFza9tGnVocBABiiRYsWadGiRUEfl56eLpfLNfwBRZHZHreefqdSPp8pu91mdTgAwgC/eGFAhtetTz4/pc+b26wOBQAwgq688kplZWXpa1/7mt555x2rw4lIhtelhtMdOkwjZQBnUHhhQP5GyszzAoCokJWVpSeeeEK/+93v9Lvf/U45OTmaN2+eysrKBjymra1NjY2NAQu6GynbbOJxQwB+FF4Y0AR3osYmO0gaABAlJk+erO9973uaPXu25syZo6efflpz5szR6tWrBzymqKhIqamp/iUnJ2cEIw5fKQlxuiI9ReXkUABnUHhhQDabTbO9LgovAIhi11xzjQ4dOjTg/sLCQjU0NPiX6urqEYwuvBlel8qO1FsdBoAwQeGF8zI8br1f3UAjZQCIUhUVFcrKyhpwv8PhkNPpDFjQLc/j1kcnmtRII2UA4q2GuADD69bpji59WNukGeNTrQ4HABCE5ubmgF+rKisrVVFRobS0NHk8HhUWFurYsWN65plnJEkPP/ywJk6cqOnTp6u1tVVPPfWUtmzZos2bN1v1ESKa4XHLNKWKqnpdd8U4q8MBYDEKL5zXzPGpirXbVF51ksILACLMrl27dP311/vXV65cKUm6/fbbVVxcrJqaGlVVVfn3t7e367777tOxY8eUlJSkWbNm6fe//33AOTB4l40do9TEOJVVnaTwAkDhhfNLiIvR9GynyqrqdVu+1dEAAIIxb948maY54P7i4uKA9QceeEAPPPBAiKOKHna7TYbHpTLeDgxAzPHCIOR53LxgAwCAITA8bpVXnZTPN3ABDCA6UHjhggyvW0c+P6XPaKQMAEBQDK9bTa2d+tOnzVaHAsBiFF64IMPjkkQjZQAAgpWb45KdRsoAROGFQRjvSlR6Co2UAQAIVrIjVldkpNDPCwCFFy7MZrPJ8LhVdoTCCwCAYBlet3bz5SUQ9Si8MCiG16U9R2mkDABAsAyPW4dONKvhFI2UgWhG4YVBMTxnGykDAIDB88+VruZXLyCaUXhhUGaMT1VcjI15XgAABGni2DFyJ8XRzwuIchReGJSEuBhNy05lnhcAAEHqmStdzpeXQFSj8MKgGR4X39YBADAEhtetiqp6GikDUYzCC4NmeNyq+uKUPm2ikTIAAMHI87jU1Napj0/QSBmIVmFfeG3fvl233HKLsrOzZbPZtGHDhvOOLy0tlc1m67PU1taOTMCjmOF1S6IJJAAAwcqdQCNlINqFfeHV0tKi3NxcPf7440Edd/DgQdXU1PiX9PT0EEUYPbJTE5ThpJEyAADBGuOI1ZRMp3YzVxqIWrFWB3AhixYt0qJFi4I+Lj09XS6Xa/gDimL+ycFH6q0OBQCAiGN4XXr3T59bHQYAi4T9L15DdeWVVyorK0tf+9rX9M4775x3bFtbmxobGwMW9M/wuLXnWL06aKQMAEBQDI9bhz9tUf2pdqtDAWCBUVd4ZWVl6YknntDvfvc7/e53v1NOTo7mzZunsrKyAY8pKipSamqqf8nJyRnBiCOL4XWptcOnD2ooTgEACMbsM3Oly3lDMBCVRl3hNXnyZH3ve9/T7NmzNWfOHD399NOaM2eOVq9ePeAxhYWFamho8C/V1dUjGHFkmZ59ppEyz6gDABAUT1qSLhkTz1xpIEqNusKrP9dcc40OHTo04H6HwyGn0xmwoH8JcTGanp1KPy8AAIJks9mU53FTeAFRKioKr4qKCmVlZVkdxqhhkDQAABgSw+tSRVW9umikDESdsH+rYXNzc8CvVZWVlaqoqFBaWpo8Ho8KCwt17NgxPfPMM5Kkhx9+WBMnTtT06dPV2tqqp556Slu2bNHmzZut+gijjuF16el3KnWiqVXpKQlWhwMAQMQwPG61tHfpo7omTc3iCRsgmoR94bVr1y5df/31/vWVK1dKkm6//XYVFxerpqZGVVVV/v3t7e267777dOzYMSUlJWnWrFn6/e9/H3AOXBzDc6aR8pF63Tgj0+JoAACIHLMmpCrGbtPuIycpvIAoE/aF17x582SaA/8cX1xcHLD+wAMP6IEHHghxVNEt25WoTGeCyqtOUngBABCEpPhYTc1KUVnVSX37y16rwwEwgqJijheG32wv87wAABgKw+PmlfJAFKLwwpDkeVzac7RB7Z00UgYAIBizvW5VftaiL1popAxEEwovDInhdautk0bKAAAEq2eudDlPjgBRhcILQzI926n4GDuPGwIAEKQJ7kSNTXaQQ4EoQ+GFIXHExmjGeCeNlAEACJLNZpPhcansSL3VoQAYQRReGDLD41bZEb6tAwAgWIbXrfeP1quzi7nSQLSg8MKQGV63jtWf1onGVqtDAQAgohget061d+nD2iarQwEwQii8MGT+Rso8ow4AQFBmTUhVrN3GCzaAKELhhSHLTE1QdmoC87wAAAhSQlyMpmUzVxqIJhReuCh5XuZ5AQAwFIbHzVMjQBSh8MJFMTxu7TlGI2UAAIJleN068vkpfdbcZnUoAEYAhRcuiuFxqb3TpwM0UgYAICiGxyVJKudxQyAqUHjhokzPTlV8rJ3HDQEACNJ4V6LSU2ikDEQLCi9clPhYu2aOTyVpAAAQpO5GysyVBqIFhRcumuFx8ZgEAABDYHhdev9ovTpopAyMehReuGiGp7uRcm0DjZQBAAiG4XGrtcOnD2topAyMdhReuGiGl0bKAAAMxYzxqYqLsZFDgShA4YWLluFM0HhXIs+oAwAQpIS4GE3PZq40EA0ovDAs8jwukgYAAENAI2UgOlB4YVgYHrf2HWtUW2eX1aEAABBRDK9L1V+c1qdNNFIGRjMKLwwLw+tWe5dP+4/TSBkAgGAYHuZKA9GAwgvDYlqWUw4aKQMAELRsV6IynQkUXsAoR+GFYdHTSJl+XgAABM/wuvjyEhjlKLwwbAwvk4MBIJxs375dt9xyi7Kzs2Wz2bRhw4YLHlNaWirDMORwODRp0iQVFxeHPE50P26452iD2jtppAyMVhReGDaGx6WahlbVNJy2OhQAgKSWlhbl5ubq8ccfH9T4yspK3Xzzzbr++utVUVGhe++9V3fddZfeeOONEEeKPI9bbZ0+fVDDXGlgtIq1OgCMHv7JwUfqdfOsRIujAQAsWrRIixYtGvT4J554QhMnTtSqVaskSVOnTtXbb7+t1atXa+HChaEKE5JmjHcqPsausqqTys1xWR0OgBDgFy8Mm3Rngia4E3ncEAAi1I4dO7RgwYKAbQsXLtSOHTssiih6OGJjNGO8U2XMlQZGLX7xwrCiCSQARK7a2lplZGQEbMvIyFBjY6NOnz6txMS+TzO0tbWpre1s/6nGRh6VGyrD49br+2qtDgNAiPCLF4aV4XFpP42UASBqFBUVKTU11b/k5ORYHVLEMrxuHas/rRONrVaHAiAEKLwwrHoaKe87xjeeABBpMjMzVVdXF7Ctrq5OTqez31+7JKmwsFANDQ3+pbq6eiRCHZVopAyMbhReGFZTs5xKiLOrnKQBABEnPz9fb775ZsC2kpIS5efnD3iMw+GQ0+kMWDA0makJyk5N0G76eQGjEoUXhlVcjF2zxrv4tg4AwkBzc7MqKipUUVEhqft18RUVFaqqqpLU/WvV8uXL/ePvvvtuHT58WA888IA+/PBD/epXv9KLL76oH/zgB1aEH5XyvG5esAGMUhReGHZ5XpfKjtRbHQYARL1du3YpLy9PeXl5kqSVK1cqLy9PP/nJTyRJNTU1/iJMkiZOnKjXXntNJSUlys3N1apVq/TUU0/xKvkRZHjc2nuMRsrAaMRbDTHsDI9bv952WMfrTyvbRT8vALDKvHnzZJrmgPuLi4v7Paa8vDyEUeF8Znvdau/0af/xBuWdmfMFYHTgFy8MOyYHAwAwNNOynHLE2nncEBiFKLww7MalOJSTlsjjhgAABCk+1q6Z41P58hIYhSi8EBI0UgYAYGgMr1vlvNkQGHUovBAShset/ccb1NpBI2UAAIJheFw63tCqmobTVocCYBhReCEkDI9bHV2m9h9vsDoUAAAiin+uNI/sA6MKhRdCYkpWihLi7CQNAACClO5M0HhXIo/sA6MMhRdCIi7GrlkTaKQMAMBQGF7mSgOjDYUXQqbnBRvn6yEDAAD6mu1xaf+xRrV1MlcaGC0ovBAyhselusY2HatncjAAAMEwvG61d/m071ij1aEAGCYUXggZw9vTSLne2kAAAIgwU7OcSoizq5zHDYFRg8ILITM22SFPWpLK6EUCAEBQ4mLsmjWeudLAaBL2hdf27dt1yy23KDs7WzabTRs2bLjgMaWlpTIMQw6HQ5MmTVJxcXHI40T/DI+Lb+sAABiCPK9Lu48wVxoYLcK+8GppaVFubq4ef/zxQY2vrKzUzTffrOuvv14VFRW69957ddddd+mNN94IcaToj+F1a//xRhopAwAQJMPjVl1jm443tFodCoBhEGt1ABeyaNEiLVq0aNDjn3jiCU2cOFGrVq2SJE2dOlVvv/22Vq9erYULF4YqTAzA8LjV6TO191iDrr40zepwAACIGGcbKZ/UeFeixdEAuFhh/4tXsHbs2KEFCxYEbFu4cKF27Ngx4DFtbW1qbGwMWDA8pmSmKDEuhnleAAAEaVyKQzlpNFIGRotRV3jV1tYqIyMjYFtGRoYaGxt1+nT/rzUvKipSamqqf8nJyRmJUKNCbIxdsyakkjQAABiC2R43bwcGRolRV3gNRWFhoRoaGvxLdXW11SGNKoa3O2kwORgAgOAYXrcOHG9grjQwCoT9HK9gZWZmqq6uLmBbXV2dnE6nEhP7fz7a4XDI4XCMRHhRyfC4tab0Tzp68rRy0pKsDgcAgIhheNzq6DL17M4j+lJ6suJj7IqPtSsuxq64GJviY7r/3LMtPsauuNju7TF2m2w2m9UfAcAZo67wys/P18aNGwO2lZSUKD8/36KIYHhckqSlv9mplIQ4xcXYFBdjV6z9zF9jbIq1dyeQ2Bi74npt7xkXeybBxNq7t8f3HHdm/ID7e5239/Xi7N2Jqb/9MXaSFAAgPEzJTNG4FIf+5bUPgj7WZpO/GOsuzGznrPfaFmv3F3FxZ8Y6/GPsvcbYzlnvzqdni75e54mx9TlvfEzgMbEUh4giYV94NTc369ChQ/71yspKVVRUKC0tTR6PR4WFhTp27JieeeYZSdLdd9+txx57TA888ID+9m//Vlu2bNGLL76o1157zaqPEPUuSXbo50tmqPLTFnX6fOroMtXZ5VNHl08dvu4/d3aZamn3qbOrQ51dpjp83fs7u8zuv/rMgD93dJ3d3+kb3kcYbTZ1F2YxfQu63oVgfD8FXew5x8X1c9y5BWL3N5KSTZLdbpNNks3Ws80mu607Jpute5+9Z5+t+8/qva2f8TbbebadGa+AY8+O17nbdPbaNtlktwde87zjBzrHOZ9NNgVs6/l70rPWOz/3/Ln3vrPHnNnWexzJHUCEiY2x6+0fXq/m1k51nMmD7V0+tXf6/LmwvdNUe5dPHWe2nd1/Nl+29Rrf0WWqvdPXzzGmTnd0qbG1w3/+9l7n8G87c+72Lp+6LjIH9y4Oewq1s+tnf73r+6ve2TxrtwfmEX8OsZ1d786TPdvOrvfOhXb72WN651v7OefqPu7suXrWe3Jh73P3nCtgvc85A/N673MFnFu9jrH3f+7e5zqb88/c6578273iXz+zGjDeprMJteeYPvt7n9t2zvnPHU/+lRQBhdeuXbt0/fXX+9dXrlwpSbr99ttVXFysmpoaVVVV+fdPnDhRr732mn7wgx/okUce0YQJE/TUU0/xKnmL3fZlb8jObZrm2cLMd6YYO1PUdXT6ehV7F9jfa1zPn7uLu7P7u4u/fvYPUEC2d/l6HddPAdnZ/VdTpkxT3cuZP/tMU6a6t2H49U423euBhV73vl6Jp9e+CxV6fY+xDep6Un/j+ia4WRNS9evbrgrq8wKIXI7YGDmSY6wOo19dvnMLsxEuDn3dydPXkzfP+WvvXOpfNwPXfT35t9e6rzshB6z35Oje6wjOoAq9Xtt6F309+9Rr20CFnnTuvrPnszKHhn3hNW/evPO+lKG4uLjfY8rLy0MYFcKJzWY786iElKjwTEwXyzQDi7HeiaNPoebru+1sAjlnX69z+M4kHTPgekMYP8A1zze++wvTwGv2fIna+///PX/sPov88XRv63Wv/AecHd933MD71OscZq84+h4bGN+Fzttz73qP6/0ZA8/b97P27MtOTRAAhIMYu00x9hglxI3O/HshPTns/EVd70IwME/6TMnn66+oO1sQ9j63b6Bz+cyAnHq24DyTQ878T+AXvWc/w9m8Y56zr9cxvc7XZ985+/tcz3/+wG0KuHZgLOe9Xq91nXv8Ba6XZWFPvLAvvAD0emTC/90NAACwms1mU4xNiiE/YxB4nTwAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhFis1QGEI9M0JUmNjY0WRwIAw6Pn32c9/34DQoUcCmC0Ga4cSuHVj6amJklSTk6OxZEAwPBqampSamqq1WFgFCOHAhitLjaH2ky+/uzD5/Pp+PHjSklJkc1mG/RxjY2NysnJUXV1tZxOZwgjjDzcm/5xXwbGvenfUO+LaZpqampSdna27HaeMkfokEOHH/emf9yXgXFv+md1DuUXr37Y7XZNmDBhyMc7nU7+IR8A96Z/3JeBcW/6N5T7wi9dGAnk0NDh3vSP+zIw7k3/rMqhfO0JAAAAACFG4QUAAAAAIUbhNYwcDocefPBBORwOq0MJO9yb/nFfBsa96R/3BaMV/2wPjHvTP+7LwLg3/bP6vvByDQAAAAAIMX7xAgAAAIAQo/ACAAAAgBCj8AIAAACAEKPwAgAAAIAQi/rCa/v27brllluUnZ0tm82mDRs2BOw3TVM/+clPlJWVpcTERC1YsEAff/xxwJgvvvhCy5Ytk9PplMvl0p133qnm5uaAMXv27NFXvvIVJSQkKCcnR7/4xS/6xPLSSy9pypQpSkhI0MyZM7Vx48Zh/7yDVVRUpKuvvlopKSlKT0/XkiVLdPDgwYAxra2tKigo0CWXXKLk5GT91V/9lerq6gLGVFVV6eabb1ZSUpLS09N1//33q7OzM2BMaWmpDMOQw+HQpEmTVFxc3Ceexx9/XJdeeqkSEhJ07bXX6g9/+MOwf+bBWLNmjWbNmuVvvJefn6/XX3/dvz8a70l/HnroIdlsNt17773+bdF6b37605/KZrMFLFOmTPHvj9b7gtGBHNo/cmj/yKGDQw49a9TlUDPKbdy40fzxj39svvzyy6Ykc/369QH7H3roITM1NdXcsGGD+f7775t/8Rd/YU6cONE8ffq0f8yNN95o5ubmmjt37jTfeustc9KkSebSpUv9+xsaGsyMjAxz2bJl5r59+8znn3/eTExMNH/961/7x7zzzjtmTEyM+Ytf/MI8cOCA+U//9E9mXFycuXfv3pDfg/4sXLjQ/O1vf2vu27fPrKioMG+66SbT4/GYzc3N/jF33323mZOTY7755pvmrl27zC9/+cvmnDlz/Ps7OzvNGTNmmAsWLDDLy8vNjRs3mmPHjjULCwv9Yw4fPmwmJSWZK1euNA8cOGA++uijZkxMjLlp0yb/mLVr15rx8fHm008/be7fv9/87ne/a7pcLrOurm5kbkYv//M//2O+9tpr5kcffWQePHjQ/NGPfmTGxcWZ+/btM00zOu/Juf7whz+Yl156qTlr1izznnvu8W+P1nvz4IMPmtOnTzdramr8y6effurfH633BaMDObR/5ND+kUMvjBwaaLTl0KgvvHo7N2n4fD4zMzPT/Pd//3f/tvr6etPhcJjPP/+8aZqmeeDAAVOS+cc//tE/5vXXXzdtNpt57Ngx0zRN81e/+pXpdrvNtrY2/5gf/vCH5uTJk/3r3/zmN82bb745IJ5rr73W/N73vjesn3GoTpw4YUoyt23bZppm932Ii4szX3rpJf+YDz74wJRk7tixwzTN7oRst9vN2tpa/5g1a9aYTqfTfy8eeOABc/r06QHX+ta3vmUuXLjQv37NNdeYBQUF/vWuri4zOzvbLCoqGv4POgRut9t86qmnuCemaTY1NZmXX365WVJSYn71q1/1J41ovjcPPvigmZub2+++aL4vGH3IoQMjhw6MHHoWObSv0ZZDo/5Rw/OprKxUbW2tFixY4N+Wmpqqa6+9Vjt27JAk7dixQy6XS1dddZV/zIIFC2S32/Xee+/5x1x33XWKj4/3j1m4cKEOHjyokydP+sf0vk7PmJ7rWK2hoUGSlJaWJknavXu3Ojo6AmKeMmWKPB5PwL2ZOXOmMjIy/GMWLlyoxsZG7d+/3z/mfJ+7vb1du3fvDhhjt9u1YMECy+9NV1eX1q5dq5aWFuXn53NPJBUUFOjmm2/uE3+035uPP/5Y2dnZuuyyy7Rs2TJVVVVJ4r5gdCOHnkUO7Ysc2hc5tH+jKYdSeJ1HbW2tJAX8zepZ79lXW1ur9PT0gP2xsbFKS0sLGNPfOXpfY6AxPfut5PP5dO+992ru3LmaMWOGpO544+Pj5XK5Asaee2+G+rkbGxt1+vRpffbZZ+rq6gqre7N3714lJyfL4XDo7rvv1vr16zVt2rSovieStHbtWpWVlamoqKjPvmi+N9dee62Ki4u1adMmrVmzRpWVlfrKV76ipqamqL4vGP3Iod3IoYHIof0jh/ZvtOXQ2KBGIyoVFBRo3759evvtt60OJSxMnjxZFRUVamho0Lp163T77bdr27ZtVodlqerqat1zzz0qKSlRQkKC1eGElUWLFvn/PGvWLF177bXyer168cUXlZiYaGFkAEYCOTQQObQvcujARlsO5Rev88jMzJSkPm9Hqaur8+/LzMzUiRMnAvZ3dnbqiy++CBjT3zl6X2OgMT37rbJixQq9+uqr2rp1qyZMmODfnpmZqfb2dtXX1weMP/feDPVzO51OJSYmauzYsYqJiQmrexMfH69JkyZp9uzZKioqUm5urh555JGovie7d+/WiRMnZBiGYmNjFRsbq23btumXv/ylYmNjlZGREbX35lwul0tXXHGFDh06FNX/zGD0I4eSQ/tDDu2LHDp4kZ5DKbzOY+LEicrMzNSbb77p39bY2Kj33ntP+fn5kqT8/HzV19dr9+7d/jFbtmyRz+fTtdde6x+zfft2dXR0+MeUlJRo8uTJcrvd/jG9r9Mzpuc6I800Ta1YsULr16/Xli1bNHHixID9s2fPVlxcXEDMBw8eVFVVVcC92bt3b0BSLSkpkdPp1LRp0/xjzve54+PjNXv27IAxPp9Pb775pmX35lw+n09tbW1RfU/mz5+vvXv3qqKiwr9cddVVWrZsmf/P0XpvztXc3Kw//elPysrKiup/ZjD6kUPJoYNBDiWHBiPic2hQr+IYhZqamszy8nKzvLzclGT+53/+p1leXm4eOXLENM3uV+G6XC7zlVdeMffs2WMuXry431fh5uXlme+995759ttvm5dffnnAq3Dr6+vNjIwM87bbbjP37dtnrl271kxKSurzKtzY2FjzP/7jP8wPPvjAfPDBBy19Fe7f/d3fmampqWZpaWnAKzxPnTrlH3P33XebHo/H3LJli7lr1y4zPz/fzM/P9+/veYXnDTfcYFZUVJibNm0yx40b1+8rPO+//37zgw8+MB9//PF+X+HpcDjM4uJi88CBA+b//t//23S5XAFvqBkp//iP/2hu27bNrKysNPfs2WP+4z/+o2mz2czNmzebphmd92Qgvd/IZJrRe2/uu+8+s7S01KysrDTfeecdc8GCBebYsWPNEydOmKYZvfcFowM5tH/k0P6RQwePHNpttOXQqC+8tm7dakrqs9x+++2maXa/Dvf//t//a2ZkZJgOh8OcP3++efDgwYBzfP755+bSpUvN5ORk0+l0mnfccYfZ1NQUMOb99983/+zP/sx0OBzm+PHjzYceeqhPLC+++KJ5xRVXmPHx8eb06dPN1157LWSf+0L6uyeSzN/+9rf+MadPnzb/z//5P6bb7TaTkpLMv/zLvzRramoCzvPJJ5+YixYtMhMTE82xY8ea9913n9nR0REwZuvWreaVV15pxsfHm5dddlnANXo8+uijpsfjMePj481rrrnG3LlzZyg+9gX97d/+ren1es34+Hhz3Lhx5vz58/0JwzSj854M5NykEa335lvf+paZlZVlxsfHm+PHjze/9a1vmYcOHfLvj9b7gtGBHNo/cmj/yKGDRw7tNtpyqM00TTO438gAAAAAAMFgjhcAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhBiFFwAAAACEGIUXAAAAAIQYhRcAAAAAhBiFFwAAAACEGIUXEALf+c53tGTJEqvDAAAg4pBDMVpReAEAAABAiFF4ARdh3bp1mjlzphITE3XJJZdowYIFuv/++/X//t//0yuvvCKbzSabzabS0lJJUnV1tb75zW/K5XIpLS1Nixcv1ieffOI/X8+3fD/72c80btw4OZ1O3X333Wpvbz/vNVtaWkb4kwMAcHHIoYg2sVYHAESqmpoaLV26VL/4xS/0l3/5l2pqatJbb72l5cuXq6qqSo2Njfrtb38rSUpLS1NHR4cWLlyo/Px8vfXWW4qNjdW//Mu/6MYbb9SePXsUHx8vSXrzzTeVkJCg0tJSffLJJ7rjjjt0ySWX6F//9V8HvKZpmlbeCgAAgkIORVQyAQzJ7t27TUnmJ5980mff7bffbi5evDhg23//93+bkydPNn0+n39bW1ubmZiYaL7xxhv+49LS0syWlhb/mDVr1pjJyclmV1fXea8JAECkIIciGvGoITBEubm5mj9/vmbOnKlbb71Vv/nNb3Ty5MkBx7///vs6dOiQUlJSlJycrOTkZKWlpam1tVV/+tOfAs6blJTkX8/Pz1dzc7Oqq6uDviYAAOGIHIpoROEFDFFMTIxKSkr0+uuva9q0aXr00Uc1efJkVVZW9ju+ublZs2fPVkVFRcDy0Ucf6W/+5m9Cck0AAMIRORTRiMILuAg2m01z587Vz372M5WXlys+Pl7r169XfHy8urq6AsYahqGPP/5Y6enpmjRpUsCSmprqH/f+++/r9OnT/vWdO3cqOTlZOTk5570mAACRhByKaEPhBQzRe++9p3/7t3/Trl27VFVVpZdfflmffvqppk6dqksvvVR79uzRwYMH9dlnn6mjo0PLli3T2LFjtXjxYr311luqrKxUaWmpvv/97+vo0aP+87a3t+vOO+/UgQMHtHHjRj344INasWKF7Hb7ea8JAECkIIciGvFWQ2CInE6ntm/frocffliNjY3yer1atWqVFi1apKuuukqlpaW66qqr1NzcrK1bt2revHnavn27fvjDH+rrX/+6mpqaNH78eM2fP19Op9N/3vnz5+vyyy/Xddddp7a2Ni1dulQ//elPL3hNAAAiBTkU0chmmrxDEwgX3/nOd1RfX68NGzZYHQoAABGFHIpwx6OGAAAAABBiFF4AAAAAEGI8aggAAAAAIcYvXgAAAAAQYhReAAAAABBiFF4AAAAAEGIUXgAAAAAQYhReAAAAABBiFF4AAAAAEGIUXgAAAAAQYhReAAAAABBiFF4AAAAAEGL/H6tugObc1Ge8AAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -964,7 +964,7 @@ "if EVALUATE_WHILE_TRAINING:\n", " logs = evaluation_logger.get_log()\n", " for i, (m, v) in enumerate(logs.items(), 1):\n", - " sb.glue(\"eval_{}\".format(m), v)\n", + " store_metadata(\"eval_{}\".format(m), v)\n", " x = [save_checkpoints_steps*i for i in range(1, len(v)+1)]\n", " plot.line_graph(\n", " values=list(zip(v, x)),\n", @@ -1077,7 +1077,7 @@ " rating_results = {}\n", " for m in RATING_METRICS:\n", " result = evaluator.metrics[m](test, prediction_df, **cols)\n", - " sb.glue(m, result)\n", + " store_metadata(m, result)\n", " rating_results[m] = result\n", " print(rating_results)" ] @@ -1167,7 +1167,7 @@ " ranking_results = {}\n", " for m in RANKING_METRICS:\n", " result = evaluator.metrics[m](test, prediction_df, **{**cols, 'k': TOP_K})\n", - " sb.glue(m, result)\n", + " store_metadata(m, result)\n", " ranking_results[m] = result\n", " print(ranking_results)" ] @@ -1242,7 +1242,7 @@ " tf_feat_cols=wide_columns+deep_columns,\n", " base_dir=EXPORT_DIR_BASE\n", ")\n", - "sb.glue('saved_model_dir', str(exported_path))\n", + "store_metadata('saved_model_dir', str(exported_path))\n", "print(\"Model exported to\", str(exported_path))" ] }, @@ -1286,4 +1286,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/xdeepfm_criteo.ipynb b/examples/00_quick_start/xdeepfm_criteo.ipynb index 35420ecc8..9479ab99d 100644 --- a/examples/00_quick_start/xdeepfm_criteo.ipynb +++ b/examples/00_quick_start/xdeepfm_criteo.ipynb @@ -331,8 +331,8 @@ } ], "source": [ - "sb.glue(\"auc\", result[\"auc\"])\n", - "sb.glue(\"logloss\", result[\"logloss\"])" + "store_metadata(\"auc\", result[\"auc\"])\n", + "store_metadata(\"logloss\", result[\"logloss\"])" ] }, { diff --git a/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb b/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb index bb6ab4e55..4df1b961c 100644 --- a/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb @@ -863,14 +863,14 @@ "source": [ "if is_jupyter():\n", " # Record results with papermill and scrapbook for tests\n", - " sb.glue(\"map\", eval_map)\n", - " sb.glue(\"ndcg\", eval_ndcg)\n", - " sb.glue(\"precision\", eval_precision)\n", - " sb.glue(\"recall\", eval_recall)\n", - " sb.glue(\"rmse\", eval_rmse)\n", - " sb.glue(\"mae\", eval_mae)\n", - " sb.glue(\"exp_var\", eval_exp_var)\n", - " sb.glue(\"rsquared\", eval_rsquared)" + " store_metadata(\"map\", eval_map)\n", + " store_metadata(\"ndcg\", eval_ndcg)\n", + " store_metadata(\"precision\", eval_precision)\n", + " store_metadata(\"recall\", eval_recall)\n", + " store_metadata(\"rmse\", eval_rmse)\n", + " store_metadata(\"mae\", eval_mae)\n", + " store_metadata(\"exp_var\", eval_exp_var)\n", + " store_metadata(\"rsquared\", eval_rsquared)" ] }, { diff --git a/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb b/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb index 4c1b36583..8d177b07a 100644 --- a/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb @@ -594,10 +594,10 @@ ], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)" + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)" ] }, { diff --git a/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb b/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb index 9d0bcc9fa..50666e82d 100644 --- a/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb @@ -575,10 +575,10 @@ "outputs": [], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)" + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)" ] }, { diff --git a/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb b/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb index 45a465262..bcc9992e4 100644 --- a/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb @@ -733,10 +733,10 @@ ], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)" + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)" ] }, { diff --git a/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb b/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb index 77aec62cf..93ee20717 100644 --- a/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb @@ -1070,14 +1070,14 @@ ], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)\n", - "sb.glue(\"map2\", eval_map2)\n", - "sb.glue(\"ndcg2\", eval_ndcg2)\n", - "sb.glue(\"precision2\", eval_precision2)\n", - "sb.glue(\"recall2\", eval_recall2)" + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)\n", + "store_metadata(\"map2\", eval_map2)\n", + "store_metadata(\"ndcg2\", eval_ndcg2)\n", + "store_metadata(\"precision2\", eval_precision2)\n", + "store_metadata(\"recall2\", eval_recall2)" ] }, { diff --git a/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb b/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb index 4061e1b7b..3d89f31d0 100644 --- a/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb @@ -512,10 +512,10 @@ "outputs": [], "source": [ "# Record results for tests - ignore this cell\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)" + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)" ] }, { diff --git a/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb b/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb index 160d491bf..7c35b3d00 100644 --- a/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb @@ -810,16 +810,16 @@ ], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"rmse\", eval_rmse)\n", - "sb.glue(\"mae\", eval_mae)\n", - "sb.glue(\"rsquared\", eval_rsquared)\n", - "sb.glue(\"exp_var\", eval_exp_var)\n", - "sb.glue(\"map\", eval_map)\n", - "sb.glue(\"ndcg\", eval_ndcg)\n", - "sb.glue(\"precision\", eval_precision)\n", - "sb.glue(\"recall\", eval_recall)\n", - "sb.glue(\"train_time\", train_time.interval)\n", - "sb.glue(\"test_time\", test_time.interval)" + "store_metadata(\"rmse\", eval_rmse)\n", + "store_metadata(\"mae\", eval_mae)\n", + "store_metadata(\"rsquared\", eval_rsquared)\n", + "store_metadata(\"exp_var\", eval_exp_var)\n", + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)\n", + "store_metadata(\"train_time\", train_time.interval)\n", + "store_metadata(\"test_time\", test_time.interval)" ] }, { diff --git a/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb b/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb index 3289c6b71..92ae786ee 100644 --- a/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb +++ b/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb @@ -441,7 +441,7 @@ ], "source": [ "# Record results with papermill for tests\n", - "sb.glue(\"auc\", auc)" + "store_metadata(\"auc\", auc)" ] }, { @@ -506,4 +506,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb b/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb index 024429dc3..441c33876 100644 --- a/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb +++ b/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb @@ -1318,16 +1318,16 @@ "source": [ "# record results for testing\n", "if is_jupyter():\n", - " sb.glue('rmse', saved_result['RMSE'])\n", - " sb.glue('mae', saved_result['MAE'])\n", - " sb.glue('rsquared', saved_result['R2'])\n", - " sb.glue('exp_var', saved_result['Explained Variance'])\n", - " sb.glue(\"train_time\", saved_result['Train Time (ms)'])\n", - " sb.glue(\"test_time\", test_time)\n", - " sb.glue('map', rank_metrics['MAP'])\n", - " sb.glue('ndcg', rank_metrics['NDCG'])\n", - " sb.glue('precision', rank_metrics['Precision'])\n", - " sb.glue('recall', rank_metrics['Recall'])" + " store_metadata('rmse', saved_result['RMSE'])\n", + " store_metadata('mae', saved_result['MAE'])\n", + " store_metadata('rsquared', saved_result['R2'])\n", + " store_metadata('exp_var', saved_result['Explained Variance'])\n", + " store_metadata(\"train_time\", saved_result['Train Time (ms)'])\n", + " store_metadata(\"test_time\", test_time)\n", + " store_metadata('map', rank_metrics['MAP'])\n", + " store_metadata('ndcg', rank_metrics['NDCG'])\n", + " store_metadata('precision', rank_metrics['Precision'])\n", + " store_metadata('recall', rank_metrics['Recall'])" ] }, { diff --git a/examples/02_model_hybrid/fm_deep_dive.ipynb b/examples/02_model_hybrid/fm_deep_dive.ipynb index 25fdbf9d7..5920b021f 100644 --- a/examples/02_model_hybrid/fm_deep_dive.ipynb +++ b/examples/02_model_hybrid/fm_deep_dive.ipynb @@ -606,7 +606,7 @@ } ], "source": [ - "sb.glue('auc_score', auc_score)" + "store_metadata('auc_score', auc_score)" ] }, { diff --git a/examples/02_model_hybrid/lightfm_deep_dive.ipynb b/examples/02_model_hybrid/lightfm_deep_dive.ipynb index ea139260f..5555ac19e 100755 --- a/examples/02_model_hybrid/lightfm_deep_dive.ipynb +++ b/examples/02_model_hybrid/lightfm_deep_dive.ipynb @@ -1890,10 +1890,10 @@ "outputs": [], "source": [ "# Record results for tests\n", - "sb.glue('eval_precision', eval_precision)\n", - "sb.glue('eval_recall', eval_recall)\n", - "sb.glue('eval_precision2', eval_precision2)\n", - "sb.glue('eval_recall2', eval_recall2)" + "store_metadata('eval_precision', eval_precision)\n", + "store_metadata('eval_recall', eval_recall)\n", + "store_metadata('eval_precision2', eval_precision2)\n", + "store_metadata('eval_recall2', eval_recall2)" ] }, { diff --git a/examples/README.md b/examples/README.md index 0b1f0ace8..10967bdca 100644 --- a/examples/README.md +++ b/examples/README.md @@ -69,4 +69,4 @@ cfg = NotebookRunConfig(source_directory='../', run_config=run_config) ``` -All metrics and parameters logged with `sb.glue` will be stored on the run as tracked metrics. The initial notebook that was submitted, will be stored as an output notebook ```out.ipynb``` in the outputs tab of the Azure Portal. +All metrics and parameters logged with `store_metadata` will be stored on the run as tracked metrics. The initial notebook that was submitted, will be stored as an output notebook ```out.ipynb``` in the outputs tab of the Azure Portal. diff --git a/examples/template.ipynb b/examples/template.ipynb index 06c66c0fb..944917db3 100644 --- a/examples/template.ipynb +++ b/examples/template.ipynb @@ -250,7 +250,7 @@ } ], "source": [ - "sb.glue(\"checked_version\", checked_version)" + "store_metadata(\"checked_version\", checked_version)" ] }, { diff --git a/tests/README.md b/tests/README.md index a3eb149e5..523194560 100644 --- a/tests/README.md +++ b/tests/README.md @@ -108,7 +108,7 @@ import papermill as pm @pytest.mark.notebooks def test_sar_single_node_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["sar_single_node"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) ``` Notice that the input of the function is a fixture defined in [conftest.py](conftest.py). For more information, please see the [definition of fixtures in PyTest](https://docs.pytest.org/en/latest/fixture.html). @@ -143,15 +143,13 @@ ABS_TOL = 0.05 def test_sar_single_node_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["sar_single_node"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["precision"] == pytest.approx(0.330753, rel=TOL, abs=ABS_TOL) assert results["recall"] == pytest.approx(0.176385, rel=TOL, abs=ABS_TOL) ``` diff --git a/tests/functional/examples/test_notebooks_gpu.py b/tests/functional/examples/test_notebooks_gpu.py index 2d8c6b0a0..fbfde453a 100644 --- a/tests/functional/examples/test_notebooks_gpu.py +++ b/tests/functional/examples/test_notebooks_gpu.py @@ -3,10 +3,9 @@ import os import pytest -import papermill as pm -import scrapbook as sb from recommenders.utils.gpu_utils import get_number_gpus +from recommenders.utils.notebook_utils import execute_notebook, read_notebook TOL = 0.1 @@ -41,7 +40,7 @@ def test_ncf_functional( notebooks, output_notebook, kernel_name, size, epochs, expected_values, seed ): notebook_path = notebooks["ncf"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -49,9 +48,7 @@ def test_ncf_functional( TOP_K=10, MOVIELENS_DATA_SIZE=size, EPOCHS=epochs, BATCH_SIZE=512, SEED=seed ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -91,7 +88,7 @@ def test_ncf_deep_dive_functional( seed, ): notebook_path = notebooks["ncf_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -103,9 +100,7 @@ def test_ncf_deep_dive_functional( SEED=seed, ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -137,15 +132,13 @@ def test_fastai_functional( notebooks, output_notebook, kernel_name, size, epochs, expected_values ): notebook_path = notebooks["fastai"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE=size, EPOCHS=epochs), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -172,7 +165,7 @@ def test_xdeepfm_functional( seed, ): notebook_path = notebooks["xdeepfm_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -182,9 +175,7 @@ def test_xdeepfm_functional( RANDOM_SEED=seed, ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -237,12 +228,10 @@ def test_wide_deep_functional( "RANKING_METRICS": ["ndcg_at_k", "map_at_k", "precision_at_k", "recall_at_k"], "RANDOM_SEED": seed, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -283,12 +272,10 @@ def test_slirec_quickstart_functional( "BATCH_SIZE": batch_size, "RANDOM_SEED": seed, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key]["auc"] == pytest.approx(value["auc"], rel=TOL, abs=ABS_TOL) @@ -338,12 +325,10 @@ def test_nrms_quickstart_functional( "seed": seed, "MIND_type": MIND_type, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key]["group_auc"] == pytest.approx( @@ -399,12 +384,10 @@ def test_naml_quickstart_functional( "seed": seed, "MIND_type": MIND_type, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key]["group_auc"] == pytest.approx( @@ -460,12 +443,10 @@ def test_lstur_quickstart_functional( "seed": seed, "MIND_type": MIND_type, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key]["group_auc"] == pytest.approx( @@ -521,12 +502,10 @@ def test_npa_quickstart_functional( "seed": seed, "MIND_type": MIND_type, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key]["group_auc"] == pytest.approx( @@ -577,7 +556,7 @@ def test_lightgcn_deep_dive_functional( seed, ): notebook_path = notebooks["lightgcn_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -592,9 +571,7 @@ def test_lightgcn_deep_dive_functional( item_file=os.path.join(data_path, r"item_embeddings"), ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -604,15 +581,13 @@ def test_lightgcn_deep_dive_functional( @pytest.mark.notebooks def test_dkn_quickstart_functional(notebooks, output_notebook, kernel_name): notebook_path = notebooks["dkn_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(EPOCHS=5, BATCH_SIZE=500), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["res"]["auc"] == pytest.approx(0.5651, rel=TOL, abs=ABS_TOL) assert results["res"]["mean_mrr"] == pytest.approx(0.1639, rel=TOL, abs=ABS_TOL) @@ -633,15 +608,13 @@ def test_cornac_bivae_functional( notebooks, output_notebook, kernel_name, size, expected_values ): notebook_path = notebooks["cornac_bivae_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE=size), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -689,15 +662,13 @@ def test_sasrec_quickstart_functional( "model_name": model_name, "seed": seed, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params, ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -719,15 +690,13 @@ def test_benchmark_movielens_gpu( notebooks, output_notebook, kernel_name, size, algos, expected_values_ndcg ): notebook_path = notebooks["benchmark_movielens"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(data_sizes=size, algorithms=algos), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert len(results["results"]) == 4 for i, value in enumerate(results["results"]): assert results["results"][i] == pytest.approx(value, rel=TOL, abs=ABS_TOL) diff --git a/tests/functional/examples/test_notebooks_pyspark.py b/tests/functional/examples/test_notebooks_pyspark.py index 57bd87928..3a88f0d44 100644 --- a/tests/functional/examples/test_notebooks_pyspark.py +++ b/tests/functional/examples/test_notebooks_pyspark.py @@ -4,8 +4,9 @@ import os import sys import pytest -import papermill as pm -import scrapbook as sb + +from recommenders.utils.notebook_utils import execute_notebook, read_notebook + TOL = 0.05 ABS_TOL = 0.05 @@ -17,15 +18,13 @@ @pytest.mark.notebooks def test_als_pyspark_functional(notebooks, output_notebook, kernel_name): notebook_path = notebooks["als_pyspark"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE="1m"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["map"] == pytest.approx(0.00201, rel=TOL, abs=ABS_TOL) assert results["ndcg"] == pytest.approx(0.02516, rel=TOL, abs=ABS_TOL) @@ -45,15 +44,13 @@ def test_als_pyspark_functional(notebooks, output_notebook, kernel_name): @pytest.mark.skipif(sys.platform == "win32", reason="Not implemented on Windows") def test_mmlspark_lightgbm_criteo_functional(notebooks, output_notebook, kernel_name): notebook_path = notebooks["mmlspark_lightgbm_criteo"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(DATA_SIZE="full", NUM_ITERATIONS=50), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["auc"] == pytest.approx(0.68895, rel=TOL, abs=ABS_TOL) @@ -75,15 +72,13 @@ def test_benchmark_movielens_pyspark( os.environ["PYSPARK_DRIVER_PYTHON"] = sys.executable os.environ.pop("SPARK_HOME", None) - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(data_sizes=size, algorithms=algos), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert len(results["results"]) == 1 for i, value in enumerate(results["results"]): assert results["results"][i] == pytest.approx(value, rel=TOL, abs=ABS_TOL) diff --git a/tests/functional/examples/test_notebooks_python.py b/tests/functional/examples/test_notebooks_python.py index 5bb6da42b..0f3d81a45 100644 --- a/tests/functional/examples/test_notebooks_python.py +++ b/tests/functional/examples/test_notebooks_python.py @@ -2,8 +2,8 @@ # Licensed under the MIT License. import pytest -import papermill as pm -import scrapbook as sb + +from recommenders.utils.notebook_utils import execute_notebook, read_notebook TOL = 0.05 @@ -38,15 +38,13 @@ def test_sar_single_node_functional( notebooks, output_notebook, kernel_name, size, expected_values ): notebook_path = notebooks["sar_single_node"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE=size), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -72,15 +70,13 @@ def test_baseline_deep_dive_functional( notebooks, output_notebook, kernel_name, size, expected_values ): notebook_path = notebooks["baseline_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE=size), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -110,15 +106,13 @@ def test_surprise_svd_functional( notebooks, output_notebook, kernel_name, size, expected_values ): notebook_path = notebooks["surprise_svd_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE=size), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -148,15 +142,13 @@ def test_vw_deep_dive_functional( notebooks, output_notebook, kernel_name, size, expected_values ): notebook_path = notebooks["vowpal_wabbit_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE=size, TOP_K=10), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -166,7 +158,7 @@ def test_vw_deep_dive_functional( @pytest.mark.skip(reason="NNI pip package has installation incompatibilities") def test_nni_tuning_svd(notebooks, output_notebook, kernel_name, tmp): notebook_path = notebooks["nni_tuning_svd"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -194,15 +186,13 @@ def test_cornac_bpr_functional( notebooks, output_notebook, kernel_name, size, expected_values ): notebook_path = notebooks["cornac_bpr_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE=size), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -229,15 +219,13 @@ def test_lightfm_functional( notebooks, output_notebook, kernel_name, size, epochs, expected_values ): notebook_path = notebooks["lightfm_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE=size, NO_EPOCHS=epochs), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -251,10 +239,8 @@ def test_lightfm_functional( ) def test_geoimc_functional(notebooks, output_notebook, kernel_name, expected_values): notebook_path = notebooks["geoimc_quickstart"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + results = read_notebook(output_notebook) for key, value in expected_values.items(): assert results[key] == pytest.approx(value, rel=TOL, abs=ABS_TOL) @@ -265,15 +251,13 @@ def test_geoimc_functional(notebooks, output_notebook, kernel_name, expected_val @pytest.mark.skip(reason="xLearn pip package has installation incompatibilities") def test_xlearn_fm_functional(notebooks, output_notebook, kernel_name): notebook_path = notebooks["xlearn_fm_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(LEARNING_RATE=0.2, EPOCH=10), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["auc_score"] == pytest.approx(0.75, rel=TOL, abs=ABS_TOL) @@ -289,7 +273,7 @@ def test_benchmark_movielens_cpu( notebooks, output_notebook, kernel_name, size, algos, expected_values_ndcg ): notebook_path = notebooks["benchmark_movielens"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, diff --git a/tests/smoke/examples/test_notebooks_gpu.py b/tests/smoke/examples/test_notebooks_gpu.py index 082b8664b..6d77fe6b0 100644 --- a/tests/smoke/examples/test_notebooks_gpu.py +++ b/tests/smoke/examples/test_notebooks_gpu.py @@ -3,10 +3,9 @@ import pytest -import papermill as pm -import scrapbook as sb from recommenders.utils.gpu_utils import get_number_gpus +from recommenders.utils.notebook_utils import execute_notebook, read_notebook TOL = 0.5 @@ -22,15 +21,13 @@ def test_gpu_vm(): @pytest.mark.gpu def test_ncf_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["ncf"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE="100k", EPOCHS=1, BATCH_SIZE=256), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["map"] == pytest.approx(0.0409234, rel=TOL, abs=ABS_TOL) assert results["ndcg"] == pytest.approx(0.1773, rel=TOL, abs=ABS_TOL) @@ -42,7 +39,7 @@ def test_ncf_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_ncf_deep_dive_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["ncf_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -50,9 +47,7 @@ def test_ncf_deep_dive_smoke(notebooks, output_notebook, kernel_name): TOP_K=10, MOVIELENS_DATA_SIZE="100k", EPOCHS=1, BATCH_SIZE=1024 ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) # There is too much variability to do an approx equal, just adding top values assert results["map"] == pytest.approx(0.0370396, rel=TOL, abs=ABS_TOL) @@ -69,15 +64,13 @@ def test_ncf_deep_dive_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_fastai_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["fastai"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE="100k", EPOCHS=1), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["rmse"] == pytest.approx(0.959352, rel=TOL, abs=ABS_TOL) assert results["mae"] == pytest.approx(0.766504, rel=TOL, abs=ABS_TOL) @@ -93,7 +86,7 @@ def test_fastai_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_xdeepfm_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["xdeepfm_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -105,9 +98,7 @@ def test_xdeepfm_smoke(notebooks, output_notebook, kernel_name): RANDOM_SEED=42, ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["res_syn"]["auc"] == pytest.approx(0.5043, rel=TOL, abs=ABS_TOL) assert results["res_syn"]["logloss"] == pytest.approx(0.7046, rel=TOL, abs=ABS_TOL) @@ -130,12 +121,10 @@ def test_wide_deep_smoke(notebooks, output_notebook, kernel_name, tmp): "RANKING_METRICS": ["ndcg_at_k", "precision_at_k"], "RANDOM_SEED": 42, } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["rmse"] == pytest.approx(1.06034, rel=TOL, abs=ABS_TOL) assert results["mae"] == pytest.approx(0.876228, rel=TOL, abs=ABS_TOL) @@ -147,15 +136,13 @@ def test_wide_deep_smoke(notebooks, output_notebook, kernel_name, tmp): @pytest.mark.gpu def test_naml_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["naml_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(epochs=1, seed=42, MIND_type="demo"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["res_syn"]["group_auc"] == pytest.approx( 0.5801, rel=TOL, abs=ABS_TOL @@ -167,15 +154,13 @@ def test_naml_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_nrms_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["nrms_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(epochs=1, seed=42, MIND_type="demo"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["res_syn"]["group_auc"] == pytest.approx( 0.5768, rel=TOL, abs=ABS_TOL @@ -187,15 +172,13 @@ def test_nrms_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_npa_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["npa_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(epochs=1, seed=42, MIND_type="demo"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["res_syn"]["group_auc"] == pytest.approx( 0.5861, rel=TOL, abs=ABS_TOL @@ -207,15 +190,13 @@ def test_npa_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_lstur_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["lstur_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(epochs=1, seed=40, MIND_type="demo"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["res_syn"]["group_auc"] == pytest.approx( 0.5977, rel=TOL, abs=ABS_TOL @@ -227,15 +208,13 @@ def test_lstur_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_cornac_bivae_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["cornac_bivae_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["map"] == pytest.approx(0.146552, rel=TOL, abs=ABS_TOL) assert results["ndcg"] == pytest.approx(0.474124, rel=TOL, abs=ABS_TOL) diff --git a/tests/smoke/examples/test_notebooks_pyspark.py b/tests/smoke/examples/test_notebooks_pyspark.py index 2e521104a..6a0f02e27 100644 --- a/tests/smoke/examples/test_notebooks_pyspark.py +++ b/tests/smoke/examples/test_notebooks_pyspark.py @@ -4,8 +4,8 @@ import sys import pytest -import papermill as pm -import scrapbook as sb + +from recommenders.utils.notebook_utils import execute_notebook, read_notebook TOL = 0.05 @@ -18,16 +18,14 @@ @pytest.mark.notebooks def test_als_pyspark_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["als_pyspark"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["map"] == pytest.approx(0.0052, rel=TOL, abs=ABS_TOL) assert results["ndcg"] == pytest.approx(0.0463, rel=TOL, abs=ABS_TOL) @@ -46,14 +44,12 @@ def test_als_pyspark_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.skipif(sys.platform == "win32", reason="Not implemented on Windows") def test_mmlspark_lightgbm_criteo_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["mmlspark_lightgbm_criteo"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(DATA_SIZE="sample", NUM_ITERATIONS=50), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["auc"] == pytest.approx(0.65, rel=TOL, abs=ABS_TOL) diff --git a/tests/smoke/examples/test_notebooks_python.py b/tests/smoke/examples/test_notebooks_python.py index 0bd359ce3..a7463d9b1 100644 --- a/tests/smoke/examples/test_notebooks_python.py +++ b/tests/smoke/examples/test_notebooks_python.py @@ -3,8 +3,8 @@ import pytest -import papermill as pm -import scrapbook as sb + +from recommenders.utils.notebook_utils import execute_notebook, read_notebook TOL = 0.05 @@ -14,15 +14,13 @@ @pytest.mark.notebooks def test_sar_single_node_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["sar_single_node"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["map"] == pytest.approx(0.110591, rel=TOL, abs=ABS_TOL) assert results["ndcg"] == pytest.approx(0.382461, rel=TOL, abs=ABS_TOL) @@ -33,15 +31,13 @@ def test_sar_single_node_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.notebooks def test_baseline_deep_dive_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["baseline_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(TOP_K=10, MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["rmse"] == pytest.approx(1.054252, rel=TOL, abs=ABS_TOL) assert results["mae"] == pytest.approx(0.846033, rel=TOL, abs=ABS_TOL) @@ -56,15 +52,13 @@ def test_baseline_deep_dive_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.notebooks def test_surprise_svd_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["surprise_svd_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["rmse"] == pytest.approx(0.96, rel=TOL, abs=ABS_TOL) assert results["mae"] == pytest.approx(0.75, rel=TOL, abs=ABS_TOL) @@ -80,15 +74,13 @@ def test_surprise_svd_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.skip(reason="VW pip package has installation incompatibilities") def test_vw_deep_dive_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["vowpal_wabbit_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["rmse"] == pytest.approx(0.985920, rel=TOL, abs=ABS_TOL) assert results["mae"] == pytest.approx(0.71292, rel=TOL, abs=ABS_TOL) @@ -103,7 +95,7 @@ def test_vw_deep_dive_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.notebooks def test_lightgbm_quickstart_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["lightgbm_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -116,9 +108,7 @@ def test_lightgbm_quickstart_smoke(notebooks, output_notebook, kernel_name): METRIC="auc", ), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["res_basic"]["auc"] == pytest.approx(0.7674, rel=TOL, abs=ABS_TOL) assert results["res_basic"]["logloss"] == pytest.approx( @@ -133,15 +123,13 @@ def test_lightgbm_quickstart_smoke(notebooks, output_notebook, kernel_name): @pytest.mark.notebooks def test_cornac_bpr_smoke(notebooks, output_notebook, kernel_name): notebook_path = notebooks["cornac_bpr_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=dict(MOVIELENS_DATA_SIZE="100k"), ) - results = sb.read_notebook(output_notebook).scraps.dataframe.set_index("name")[ - "data" - ] + results = read_notebook(output_notebook) assert results["map"] == pytest.approx(0.1091, rel=TOL, abs=ABS_TOL) assert results["ndcg"] == pytest.approx(0.4034, rel=TOL, abs=ABS_TOL) diff --git a/tests/unit/examples/test_notebooks_gpu.py b/tests/unit/examples/test_notebooks_gpu.py index 45073daf5..a5e9b47ab 100644 --- a/tests/unit/examples/test_notebooks_gpu.py +++ b/tests/unit/examples/test_notebooks_gpu.py @@ -4,9 +4,9 @@ import os import pytest -import papermill as pm from recommenders.utils.gpu_utils import get_number_gpus +from recommenders.utils.notebook_utils import execute_notebook @pytest.mark.notebooks @@ -19,7 +19,7 @@ def test_gpu_vm(): @pytest.mark.gpu def test_fastai(notebooks, output_notebook, kernel_name): notebook_path = notebooks["fastai"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -31,7 +31,7 @@ def test_fastai(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_ncf(notebooks, output_notebook, kernel_name): notebook_path = notebooks["ncf"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -45,7 +45,7 @@ def test_ncf(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_ncf_deep_dive(notebooks, output_notebook, kernel_name): notebook_path = notebooks["ncf_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -59,7 +59,7 @@ def test_ncf_deep_dive(notebooks, output_notebook, kernel_name): @pytest.mark.gpu def test_xdeepfm(notebooks, output_notebook, kernel_name): notebook_path = notebooks["xdeepfm_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -89,7 +89,7 @@ def test_wide_deep(notebooks, output_notebook, kernel_name, tmp): "RATING_METRICS": ["rmse"], "RANKING_METRICS": ["ndcg_at_k"], } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) @@ -106,7 +106,7 @@ def test_wide_deep(notebooks, output_notebook, kernel_name, tmp): "RATING_METRICS": ["rsquared"], "RANKING_METRICS": ["map_at_k"], } - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, parameters=params ) @@ -115,7 +115,7 @@ def test_wide_deep(notebooks, output_notebook, kernel_name, tmp): @pytest.mark.gpu def test_dkn_quickstart(notebooks, output_notebook, kernel_name): notebook_path = notebooks["dkn_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, diff --git a/tests/unit/examples/test_notebooks_pyspark.py b/tests/unit/examples/test_notebooks_pyspark.py index 372fe6f23..991095f75 100644 --- a/tests/unit/examples/test_notebooks_pyspark.py +++ b/tests/unit/examples/test_notebooks_pyspark.py @@ -4,13 +4,13 @@ import sys import pytest -import papermill as pm from recommenders.utils.constants import ( DEFAULT_RATING_COL, DEFAULT_USER_COL, DEFAULT_ITEM_COL, ) +from recommenders.utils.notebook_utils import execute_notebook # This is a flaky test that can fail unexpectedly @@ -22,7 +22,7 @@ ) def test_als_pyspark_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["als_pyspark"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -39,7 +39,7 @@ def test_als_pyspark_runs(notebooks, output_notebook, kernel_name): @pytest.mark.spark def test_data_split_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["data_split"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) # This is a flaky test that can fail unexpectedly @@ -51,7 +51,7 @@ def test_data_split_runs(notebooks, output_notebook, kernel_name): ) def test_als_deep_dive_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["als_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -73,7 +73,7 @@ def test_als_deep_dive_runs(notebooks, output_notebook, kernel_name): ) def test_evaluation_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["evaluation"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) # This is a flaky test that can fail unexpectedly @@ -82,7 +82,7 @@ def test_evaluation_runs(notebooks, output_notebook, kernel_name): @pytest.mark.spark def test_evaluation_diversity_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["evaluation_diversity"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -105,7 +105,7 @@ def test_evaluation_diversity_runs(notebooks, output_notebook, kernel_name): ) def test_spark_tuning(notebooks, output_notebook, kernel_name): notebook_path = notebooks["spark_tuning"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -125,7 +125,7 @@ def test_spark_tuning(notebooks, output_notebook, kernel_name): @pytest.mark.skipif(sys.platform == "win32", reason="Not implemented on Windows") def test_mmlspark_lightgbm_criteo_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["mmlspark_lightgbm_criteo"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, diff --git a/tests/unit/examples/test_notebooks_python.py b/tests/unit/examples/test_notebooks_python.py index ed3d494fd..810721483 100644 --- a/tests/unit/examples/test_notebooks_python.py +++ b/tests/unit/examples/test_notebooks_python.py @@ -4,8 +4,8 @@ import sys import pytest -import papermill as pm -import scrapbook as sb + +from recommenders.utils.notebook_utils import execute_notebook, read_notebook TOL = 0.05 ABS_TOL = 0.05 @@ -14,7 +14,7 @@ @pytest.mark.notebooks def test_template_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["template"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, parameters=dict(PM_VERSION=pm.__version__), @@ -30,25 +30,25 @@ def test_template_runs(notebooks, output_notebook, kernel_name): @pytest.mark.notebooks def test_sar_single_node_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["sar_single_node"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) @pytest.mark.notebooks def test_sar_deep_dive_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["sar_deep_dive"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) @pytest.mark.notebooks def test_baseline_deep_dive_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["baseline_deep_dive"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) @pytest.mark.notebooks def test_surprise_deep_dive_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["surprise_svd_deep_dive"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -59,7 +59,7 @@ def test_surprise_deep_dive_runs(notebooks, output_notebook, kernel_name): @pytest.mark.notebooks def test_lightgbm(notebooks, output_notebook, kernel_name): notebook_path = notebooks["lightgbm_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -77,14 +77,14 @@ def test_lightgbm(notebooks, output_notebook, kernel_name): @pytest.mark.notebooks def test_cornac_deep_dive_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["cornac_bpr_deep_dive"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) @pytest.mark.notebooks @pytest.mark.experimental def test_rlrmc_quickstart_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["rlrmc_quickstart"] - pm.execute_notebook( + execute_notebook( notebook_path, output_notebook, kernel_name=kernel_name, @@ -97,4 +97,4 @@ def test_rlrmc_quickstart_runs(notebooks, output_notebook, kernel_name): @pytest.mark.skip(reason="VW pip package has installation incompatibilities") def test_vw_deep_dive_runs(notebooks, output_notebook, kernel_name): notebook_path = notebooks["vowpal_wabbit_deep_dive"] - pm.execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) + execute_notebook(notebook_path, output_notebook, kernel_name=kernel_name) diff --git a/tests/unit/recommenders/utils/test_notebook_utils.py b/tests/unit/recommenders/utils/test_notebook_utils.py index 755e09267..dc2b9e343 100644 --- a/tests/unit/recommenders/utils/test_notebook_utils.py +++ b/tests/unit/recommenders/utils/test_notebook_utils.py @@ -36,7 +36,7 @@ def test_is_jupyter(notebook_types, output_notebook, kernel_name): assert is_databricks() is False # Test on Jupyter notebook - pm.execute_notebook( + execute_notebook( notebook_types, output_notebook, kernel_name=kernel_name, From 70c068e421e7164624b2db458d2e6e26e26a5ec1 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Tue, 31 Oct 2023 11:02:41 +0100 Subject: [PATCH 07/22] Replace papermill and scrapbook for new internal function Signed-off-by: miguelgfierro --- .../utils/test_notebook_utils.ipynb | 10 ++++------ .../recommenders/utils/test_notebook_utils.py | 18 +++++++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/unit/recommenders/utils/test_notebook_utils.ipynb b/tests/unit/recommenders/utils/test_notebook_utils.ipynb index e9d0d72e4..f32ae0102 100644 --- a/tests/unit/recommenders/utils/test_notebook_utils.ipynb +++ b/tests/unit/recommenders/utils/test_notebook_utils.ipynb @@ -22,11 +22,9 @@ "metadata": {}, "outputs": [], "source": [ - "# set the environment path to find Recommenders\n", - "import sys\n", "\n", - "import scrapbook as sb\n", - "from recommenders.utils.notebook_utils import is_jupyter, is_databricks" + "from recommenders.utils.notebook_utils import is_jupyter, is_databricks\n", + "from recommenders.utils.notebook_utils import store_metadata\n" ] }, { @@ -72,8 +70,8 @@ } ], "source": [ - "sb.glue(\"is_jupyter\", is_jupyter())\n", - "sb.glue(\"is_databricks\", is_databricks())" + "store_metadata(\"is_jupyter\", is_jupyter())\n", + "store_metadata(\"is_databricks\", is_databricks())" ] }, { diff --git a/tests/unit/recommenders/utils/test_notebook_utils.py b/tests/unit/recommenders/utils/test_notebook_utils.py index dc2b9e343..225db115a 100644 --- a/tests/unit/recommenders/utils/test_notebook_utils.py +++ b/tests/unit/recommenders/utils/test_notebook_utils.py @@ -1,8 +1,8 @@ # Copyright (c) Recommenders contributors. # Licensed under the MIT License. -import nbclient import pytest +import nbclient import papermill as pm import scrapbook as sb from pathlib import Path @@ -41,12 +41,10 @@ def test_is_jupyter(notebook_types, output_notebook, kernel_name): output_notebook, kernel_name=kernel_name, ) - nb = sb.read_notebook(output_notebook) - df = nb.scraps.dataframe - result_is_jupyter = df.loc[df["name"] == "is_jupyter", "data"].values[0] - assert result_is_jupyter # is True not allowed - result_is_databricks = df.loc[df["name"] == "is_databricks", "data"].values[0] - assert not result_is_databricks + results = read_notebook(output_notebook) + + assert results["is_jupyter"] + assert not results["is_databricks"] @pytest.mark.spark @@ -56,6 +54,7 @@ def test_is_databricks(): pass +@pytest.mark.notebooks def test_notebook_execution_int(notebook_programmatic, output_notebook, kernel_name): execute_notebook( notebook_programmatic, @@ -68,6 +67,7 @@ def test_notebook_execution_int(notebook_programmatic, output_notebook, kernel_n assert results["response1"] == 8 +@pytest.mark.notebooks def test_notebook_execution_float(notebook_programmatic, output_notebook, kernel_name): execute_notebook( notebook_programmatic, @@ -80,6 +80,7 @@ def test_notebook_execution_float(notebook_programmatic, output_notebook, kernel assert results["response1"] == 3.5 +@pytest.mark.notebooks def test_notebook_execution_letter(notebook_programmatic, output_notebook, kernel_name): execute_notebook( notebook_programmatic, @@ -92,6 +93,7 @@ def test_notebook_execution_letter(notebook_programmatic, output_notebook, kerne assert results["response2"] is True +@pytest.mark.notebooks def test_notebook_execution_other_letter( notebook_programmatic, output_notebook, kernel_name ): @@ -106,6 +108,7 @@ def test_notebook_execution_other_letter( assert results["response2"] == "A" +@pytest.mark.notebooks def test_notebook_execution_value_error_fails( notebook_programmatic, output_notebook, kernel_name ): @@ -118,6 +121,7 @@ def test_notebook_execution_value_error_fails( ) +@pytest.mark.notebooks def test_notebook_execution_int_with_comment( notebook_programmatic, output_notebook, kernel_name ): From 54c2278962eb772e52e282e89b9c3a24cb284846 Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Tue, 31 Oct 2023 11:41:55 +0100 Subject: [PATCH 08/22] Update new programmatic execution code Signed-off-by: miguelgfierro --- examples/00_quick_start/als_movielens.ipynb | 12 +- examples/00_quick_start/dkn_MIND.ipynb | 10 +- .../00_quick_start/fastai_movielens.ipynb | 3 +- .../00_quick_start/geoimc_movielens.ipynb | 8 +- .../00_quick_start/lightgbm_tinycriteo.ipynb | 31 +++- examples/00_quick_start/lstur_MIND.ipynb | 11 +- examples/00_quick_start/naml_MIND.ipynb | 11 +- examples/00_quick_start/npa_MIND.ipynb | 11 +- examples/00_quick_start/nrms_MIND.ipynb | 9 +- examples/00_quick_start/rbm_movielens.ipynb | 11 +- examples/00_quick_start/rlrmc_movielens.ipynb | 50 ++---- examples/00_quick_start/sar_movielens.ipynb | 6 +- examples/00_quick_start/sasrec_amazon.ipynb | 18 +- .../sequential_recsys_amazondataset.ipynb | 22 +-- examples/00_quick_start/tfidf_covid.ipynb | 8 +- .../00_quick_start/wide_deep_movielens.ipynb | 2 +- examples/00_quick_start/xdeepfm_criteo.ipynb | 9 +- .../functional/examples/test_notebooks_gpu.py | 163 ++++++++---------- tests/smoke/examples/test_notebooks_python.py | 12 +- 19 files changed, 210 insertions(+), 197 deletions(-) diff --git a/examples/00_quick_start/als_movielens.ipynb b/examples/00_quick_start/als_movielens.ipynb index dc784f996..2e5b4d7f6 100644 --- a/examples/00_quick_start/als_movielens.ipynb +++ b/examples/00_quick_start/als_movielens.ipynb @@ -43,7 +43,9 @@ } ], "source": [ - "# set the environment path to find Recommenders\n", + "import warnings\n", + "warnings.simplefilter(action='ignore', category=FutureWarning)\n", + "\n", "import sys\n", "import pyspark\n", "from pyspark.ml.recommendation import ALS\n", @@ -51,8 +53,6 @@ "from pyspark.sql import SparkSession\n", "from pyspark.sql.types import StructType, StructField\n", "from pyspark.sql.types import StringType, FloatType, IntegerType, LongType\n", - "import warnings\n", - "warnings.simplefilter(action='ignore', category=FutureWarning)\n", "\n", "from recommenders.utils.timer import Timer\n", "from recommenders.datasets import movielens\n", @@ -60,6 +60,7 @@ "from recommenders.datasets.spark_splitters import spark_random_split\n", "from recommenders.evaluation.spark_evaluation import SparkRatingEvaluation, SparkRankingEvaluation\n", "from recommenders.utils.spark_utils import start_or_get_spark\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Spark version: {}\".format(pyspark.__version__))\n" @@ -768,9 +769,8 @@ } ], "source": [ + "# Record results for tests - ignore this cell\n", "if is_jupyter():\n", - " # Record results with papermill for tests\n", - " import scrapbook as sb\n", " store_metadata(\"map\", rank_eval.map_at_k())\n", " store_metadata(\"ndcg\", rank_eval.ndcg_at_k())\n", " store_metadata(\"precision\", rank_eval.precision_at_k())\n", @@ -815,4 +815,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/dkn_MIND.ipynb b/examples/00_quick_start/dkn_MIND.ipynb index 6ef941e3a..87e2acdaf 100644 --- a/examples/00_quick_start/dkn_MIND.ipynb +++ b/examples/00_quick_start/dkn_MIND.ipynb @@ -94,6 +94,7 @@ "from recommenders.models.deeprec.deeprec_utils import download_deeprec_resources, prepare_hparams\n", "from recommenders.models.deeprec.models.dkn import DKN\n", "from recommenders.models.deeprec.io.dkn_iterator import DKNTextIterator\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(f\"System version: {sys.version}\")\n", "print(f\"Tensorflow version: {tf.__version__}\")" @@ -345,7 +346,12 @@ "metadata": {}, "outputs": [], "source": [ - "store_metadata(\"res\", res)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"auc\", res[\"auc\"])\n", + "store_metadata(\"group_auc\", res[\"group_auc\"])\n", + "store_metadata(\"ndcg@5\", res[\"ndcg@5\"])\n", + "store_metadata(\"ndcg@10\", res[\"ndcg@10\"])\n", + "store_metadata(\"mean_mrr\", res[\"mean_mrr\"])\n" ] }, { @@ -395,4 +401,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/fastai_movielens.ipynb b/examples/00_quick_start/fastai_movielens.ipynb index fd64b1f03..031f8b6be 100644 --- a/examples/00_quick_start/fastai_movielens.ipynb +++ b/examples/00_quick_start/fastai_movielens.ipynb @@ -61,6 +61,7 @@ "from recommenders.models.fastai.fastai_utils import cartesian_product, score\n", "from recommenders.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", "from recommenders.evaluation.python_evaluation import rmse, mae, rsquared, exp_var\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Pandas version: {}\".format(pd.__version__))\n", @@ -914,7 +915,7 @@ } ], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"map\", eval_map)\n", "store_metadata(\"ndcg\", eval_ndcg)\n", "store_metadata(\"precision\", eval_precision)\n", diff --git a/examples/00_quick_start/geoimc_movielens.ipynb b/examples/00_quick_start/geoimc_movielens.ipynb index f8518db12..2bb49fb01 100644 --- a/examples/00_quick_start/geoimc_movielens.ipynb +++ b/examples/00_quick_start/geoimc_movielens.ipynb @@ -31,9 +31,8 @@ "from recommenders.models.geoimc.geoimc_data import ML_100K\n", "from recommenders.models.geoimc.geoimc_algorithm import IMCProblem\n", "from recommenders.models.geoimc.geoimc_predict import Inferer\n", - "from recommenders.evaluation.python_evaluation import (\n", - " rmse, mae\n", - ")" + "from recommenders.evaluation.python_evaluation import rmse, mae\n", + "from recommenders.utils.notebook_utils import store_metadata" ] }, { @@ -287,6 +286,7 @@ "metadata": {}, "outputs": [], "source": [ + "# Record results for tests - ignore this cell\n", "store_metadata(\"rmse\", RMSE)\n", "store_metadata(\"mae\", MAE)" ] @@ -326,4 +326,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/lightgbm_tinycriteo.ipynb b/examples/00_quick_start/lightgbm_tinycriteo.ipynb index a62e7538a..bd0e4e746 100644 --- a/examples/00_quick_start/lightgbm_tinycriteo.ipynb +++ b/examples/00_quick_start/lightgbm_tinycriteo.ipynb @@ -59,6 +59,7 @@ "\n", "import recommenders.datasets.criteo as criteo\n", "import recommenders.models.lightgbm.lightgbm_utils as lgb_utils\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"LightGBM version: {}\".format(lgb.__version__))" @@ -719,8 +720,18 @@ "auc = roc_auc_score(np.asarray(test_y.reshape(-1)), np.asarray(test_preds))\n", "logloss = log_loss(np.asarray(test_y.reshape(-1)), np.asarray(test_preds), eps=1e-12)\n", "res_basic = {\"auc\": auc, \"logloss\": logloss}\n", - "print(res_basic)\n", - "sb.glue(\"res_basic\", res_basic)" + "print(res_basic)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Record results for tests - ignore this cell\n", + "store_metadata(\"auc_basic\", res_basic[\"auc\"])\n", + "store_metadata(\"logloss_basic\", res_basic[\"logloss\"])" ] }, { @@ -897,8 +908,18 @@ "logloss = log_loss(np.asarray(test_y.reshape(-1)), np.asarray(test_preds), eps=1e-12)\n", "res_optim = {\"auc\": auc, \"logloss\": logloss}\n", "\n", - "print(res_optim)\n", - "sb.glue(\"res_optim\", res_optim)" + "print(res_optim)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Record results for tests - ignore this cell\n", + "store_metadata(\"auc_opt\", res_optim[\"auc\"])\n", + "store_metadata(\"logloss_opt\", res_optim[\"logloss\"])" ] }, { @@ -979,4 +1000,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/lstur_MIND.ipynb b/examples/00_quick_start/lstur_MIND.ipynb index daf17b9ee..808d03e40 100644 --- a/examples/00_quick_start/lstur_MIND.ipynb +++ b/examples/00_quick_start/lstur_MIND.ipynb @@ -109,6 +109,7 @@ "from recommenders.models.newsrec.models.lstur import LSTURModel\n", "from recommenders.models.newsrec.io.mind_iterator import MINDIterator\n", "from recommenders.models.newsrec.newsrec_utils import get_mind_data_set\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Tensorflow version: {}\".format(tf.__version__))\n" @@ -136,7 +137,7 @@ "batch_size = 32\n", "\n", "# Options: demo, small, large\n", - "MIND_type = 'demo'" + "MIND_type = \"demo\"" ] }, { @@ -440,7 +441,11 @@ "metadata": {}, "outputs": [], "source": [ - "sb.glue(\"res_syn\", res_syn)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"group_auc\", res_syn['group_auc'])\n", + "store_metadata(\"mean_mrr\", res_syn['mean_mrr'])\n", + "store_metadata(\"ndcg@5\", res_syn['ndcg@5'])\n", + "store_metadata(\"ndcg@10\", res_syn['ndcg@10'])" ] }, { @@ -559,4 +564,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/naml_MIND.ipynb b/examples/00_quick_start/naml_MIND.ipynb index 741803a68..42ed0aaf9 100644 --- a/examples/00_quick_start/naml_MIND.ipynb +++ b/examples/00_quick_start/naml_MIND.ipynb @@ -94,8 +94,8 @@ } ], "source": [ - "import sys\n", "import os\n", + "import sys\n", "import numpy as np\n", "import zipfile\n", "from tqdm import tqdm\n", @@ -109,6 +109,7 @@ "from recommenders.models.newsrec.models.naml import NAMLModel\n", "from recommenders.models.newsrec.io.mind_all_iterator import MINDAllIterator\n", "from recommenders.models.newsrec.newsrec_utils import get_mind_data_set\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Tensorflow version: {}\".format(tf.__version__))\n" @@ -437,7 +438,11 @@ "metadata": {}, "outputs": [], "source": [ - "sb.glue(\"res_syn\", res_syn)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"group_auc\", res_syn['group_auc'])\n", + "store_metadata(\"mean_mrr\", res_syn['mean_mrr'])\n", + "store_metadata(\"ndcg@5\", res_syn['ndcg@5'])\n", + "store_metadata(\"ndcg@10\", res_syn['ndcg@10'])" ] }, { @@ -556,4 +561,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/npa_MIND.ipynb b/examples/00_quick_start/npa_MIND.ipynb index cd68bcdd0..a8cfd7882 100644 --- a/examples/00_quick_start/npa_MIND.ipynb +++ b/examples/00_quick_start/npa_MIND.ipynb @@ -94,8 +94,8 @@ } ], "source": [ - "import sys\n", "import os\n", + "import sys\n", "import numpy as np\n", "import zipfile\n", "from tqdm import tqdm\n", @@ -109,6 +109,7 @@ "from recommenders.models.newsrec.models.npa import NPAModel\n", "from recommenders.models.newsrec.io.mind_iterator import MINDIterator\n", "from recommenders.models.newsrec.newsrec_utils import get_mind_data_set\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Tensorflow version: {}\".format(tf.__version__))" @@ -417,7 +418,11 @@ "metadata": {}, "outputs": [], "source": [ - "sb.glue(\"res_syn\", res_syn)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"group_auc\", res_syn['group_auc'])\n", + "store_metadata(\"mean_mrr\", res_syn['mean_mrr'])\n", + "store_metadata(\"ndcg@5\", res_syn['ndcg@5'])\n", + "store_metadata(\"ndcg@10\", res_syn['ndcg@10'])" ] }, { @@ -534,4 +539,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/nrms_MIND.ipynb b/examples/00_quick_start/nrms_MIND.ipynb index d9a7cc93a..d66564c0d 100644 --- a/examples/00_quick_start/nrms_MIND.ipynb +++ b/examples/00_quick_start/nrms_MIND.ipynb @@ -94,8 +94,8 @@ } ], "source": [ - "import sys\n", "import os\n", + "import sys\n", "import numpy as np\n", "import zipfile\n", "from tqdm import tqdm\n", @@ -109,6 +109,7 @@ "from recommenders.models.newsrec.models.nrms import NRMSModel\n", "from recommenders.models.newsrec.io.mind_iterator import MINDIterator\n", "from recommenders.models.newsrec.newsrec_utils import get_mind_data_set\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Tensorflow version: {}\".format(tf.__version__))\n" @@ -434,7 +435,11 @@ "metadata": {}, "outputs": [], "source": [ - "sb.glue(\"res_syn\", res_syn)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"group_auc\", res_syn['group_auc'])\n", + "store_metadata(\"mean_mrr\", res_syn['mean_mrr'])\n", + "store_metadata(\"ndcg@5\", res_syn['ndcg@5'])\n", + "store_metadata(\"ndcg@10\", res_syn['ndcg@10'])" ] }, { diff --git a/examples/00_quick_start/rbm_movielens.ipynb b/examples/00_quick_start/rbm_movielens.ipynb index 70df21ac3..17e5e2cbe 100644 --- a/examples/00_quick_start/rbm_movielens.ipynb +++ b/examples/00_quick_start/rbm_movielens.ipynb @@ -65,9 +65,7 @@ } ], "source": [ - "# set the environment path to find Recommenders\n", "import sys\n", - "\n", "import pandas as pd\n", "import numpy as np\n", "import scrapbook as sb\n", @@ -81,15 +79,16 @@ "from recommenders.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", "from recommenders.utils.timer import Timer\n", "from recommenders.utils.plot import line_graph\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "#For interactive mode only\n", "%load_ext autoreload\n", "%autoreload 2\n", "%matplotlib inline\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Pandas version: {}\".format(pd.__version__))\n", - "print(\"Tensorflow version: {}\".format(tf.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"Pandas version: {pd.__version__}\")\n", + "print(f\"Tensorflow version: {tf.__version__})" ] }, { @@ -778,7 +777,7 @@ } ], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"map\", eval_100k['MAP'][0])\n", "store_metadata(\"ndcg\", eval_100k['nDCG@k'][0])\n", "store_metadata(\"precision\", eval_100k['Precision@k'][0])\n", diff --git a/examples/00_quick_start/rlrmc_movielens.ipynb b/examples/00_quick_start/rlrmc_movielens.ipynb index ed78974d8..6ec3e6a86 100644 --- a/examples/00_quick_start/rlrmc_movielens.ipynb +++ b/examples/00_quick_start/rlrmc_movielens.ipynb @@ -22,34 +22,6 @@ "This notebook provides an example of how to utilize and evaluate RLRMC implementation in **recommenders**." ] }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import sys\n", - "import time\n", - "import pandas as pd\n", - "\n", - "from recommenders.datasets.python_splitters import python_random_split\n", - "from recommenders.datasets.python_splitters import python_stratified_split\n", - "from recommenders.datasets import movielens\n", - "from recommenders.models.rlrmc.RLRMCdataset import RLRMCdataset \n", - "from recommenders.models.rlrmc.RLRMCalgorithm import RLRMCalgorithm \n", - "# Pymanopt installation is required via\n", - "# pip install pymanopt \n", - "from recommenders.evaluation.python_evaluation import (\n", - " rmse, mae\n", - ")\n", - "\n", - "# import logging\n", - "\n", - "# %load_ext autoreload\n", - "# %autoreload 2" - ] - }, { "cell_type": "code", "execution_count": 2, @@ -66,8 +38,24 @@ } ], "source": [ - "print(\"Pandas version: {}\".format(pd.__version__))\n", - "print(\"System version: {}\".format(sys.version))\n" + "import sys\n", + "import time\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from recommenders.datasets.python_splitters import python_random_split\n", + "from recommenders.datasets.python_splitters import python_stratified_split\n", + "from recommenders.datasets import movielens\n", + "from recommenders.models.rlrmc.RLRMCdataset import RLRMCdataset \n", + "from recommenders.models.rlrmc.RLRMCalgorithm import RLRMCalgorithm \n", + "from recommenders.evaluation.python_evaluation import rmse, mae\n", + "from recommenders.utils.notebook_utils import store_metadata\n", + "\n", + "print(f\"Pandas version: {pd.__version__}\")\n", + "print(f\"System version: {sys.version}\")\n", + "\n", + "%load_ext autoreload\n", + "%autoreload 2" ] }, { @@ -331,4 +319,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/sar_movielens.ipynb b/examples/00_quick_start/sar_movielens.ipynb index 9fe09c6ad..09243e6fd 100644 --- a/examples/00_quick_start/sar_movielens.ipynb +++ b/examples/00_quick_start/sar_movielens.ipynb @@ -60,7 +60,6 @@ "import logging\n", "import numpy as np\n", "import pandas as pd\n", - "import scrapbook as sb\n", "from sklearn.preprocessing import minmax_scale\n", "\n", "from recommenders.utils.timer import Timer\n", @@ -79,6 +78,7 @@ " rsquared,\n", " exp_var\n", ")\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "%load_ext autoreload\n", "%autoreload 2\n", @@ -772,7 +772,7 @@ }, "outputs": [], "source": [ - "# Record results with papermill for tests - ignore this cell\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"map\", eval_map)\n", "store_metadata(\"ndcg\", eval_ndcg)\n", "store_metadata(\"precision\", eval_precision)\n", @@ -804,4 +804,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/sasrec_amazon.ipynb b/examples/00_quick_start/sasrec_amazon.ipynb index 164fb365f..2e44fbe25 100644 --- a/examples/00_quick_start/sasrec_amazon.ipynb +++ b/examples/00_quick_start/sasrec_amazon.ipynb @@ -60,11 +60,9 @@ "import re\n", "import sys\n", "import os\n", - "import scrapbook as sb\n", "from tempfile import TemporaryDirectory\n", "import numpy as np\n", "import pandas as pd \n", - "\n", "from collections import defaultdict\n", "import tensorflow as tf\n", "tf.get_logger().setLevel('ERROR') # only show error messages\n", @@ -72,17 +70,13 @@ "from recommenders.utils.timer import Timer\n", "from recommenders.datasets.amazon_reviews import get_review_data\n", "from recommenders.datasets.split_utils import filter_k_core\n", - "\n", - "# Transformer Based Models\n", "from recommenders.models.sasrec.model import SASREC\n", "from recommenders.models.sasrec.ssept import SSEPT\n", - "\n", - "# Sampler for sequential prediction\n", "from recommenders.models.sasrec.sampler import WarpSampler\n", "from recommenders.models.sasrec.util import SASRecDataSet\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Tensorflow version: {}\".format(tf.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"Tensorflow version: {tf.__version__}\")" ] }, { @@ -438,11 +432,9 @@ } ], "source": [ - "# Record results with papermill for tests - ignore this cell\n", - "# sb.glue(\"res_syn\", res_syn)\n", - "\n", - "sb.glue(\"ndcg@10\", t_test[0])\n", - "sb.glue(\"Hit@10\", t_test[1])" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"ndcg@10\", t_test[0])\n", + "store_metadata(\"Hit@10\", t_test[1])" ] }, { diff --git a/examples/00_quick_start/sequential_recsys_amazondataset.ipynb b/examples/00_quick_start/sequential_recsys_amazondataset.ipynb index 8d22ba461..36efe26ea 100644 --- a/examples/00_quick_start/sequential_recsys_amazondataset.ipynb +++ b/examples/00_quick_start/sequential_recsys_amazondataset.ipynb @@ -61,11 +61,9 @@ } ], "source": [ - "import sys\n", "import os\n", + "import sys\n", "import logging\n", - "import papermill as pm\n", - "import scrapbook as sb\n", "from tempfile import TemporaryDirectory\n", "import numpy as np\n", "import tensorflow.compat.v1 as tf\n", @@ -78,22 +76,19 @@ ")\n", "from recommenders.datasets.amazon_reviews import download_and_extract, data_preprocessing\n", "from recommenders.datasets.download_utils import maybe_download\n", - "\n", - "\n", "from recommenders.models.deeprec.models.sequential.sli_rec import SLI_RECModel as SeqModel\n", "#### to use the other model, use one of the following lines:\n", "# from recommenders.models.deeprec.models.sequential.asvd import A2SVDModel as SeqModel\n", "# from recommenders.models.deeprec.models.sequential.caser import CaserModel as SeqModel\n", "# from recommenders.models.deeprec.models.sequential.gru import GRUModel as SeqModel\n", "# from recommenders.models.deeprec.models.sequential.sum import SUMModel as SeqModel\n", - "\n", "#from recommenders.models.deeprec.models.sequential.nextitnet import NextItNetModel\n", - "\n", "from recommenders.models.deeprec.io.sequential_iterator import SequentialIterator\n", "#from recommenders.models.deeprec.io.nextitnet_iterator import NextItNetIterator\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Tensorflow version: {}\".format(tf.__version__))\n", + "print(f\"System version: {sys.version}\")\n", + "print(f\"Tensorflow version: {tf.__version__}\")\n", "\n" ] }, @@ -402,7 +397,14 @@ "metadata": {}, "outputs": [], "source": [ - "sb.glue(\"res_syn\", res_syn)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"auc\", res_syn[\"auc\"])\n", + "store_metadata(\"logloss\", res_syn[\"logloss\"])\n", + "store_metadata(\"mean_mrr\", res_syn[\"mean_mrr\"])\n", + "store_metadata(\"ndcg@2\", res_syn[\"ndcg@2\"])\n", + "store_metadata(\"ndcg@4\", res_syn[\"ndcg@4\"])\n", + "store_metadata(\"ndcg@6\", res_syn[\"ndcg@6\"])\n", + "store_metadata(\"group_auc\", res_syn[\"group_auc\"])\n" ] }, { diff --git a/examples/00_quick_start/tfidf_covid.ipynb b/examples/00_quick_start/tfidf_covid.ipynb index 5193d5776..35ee16285 100644 --- a/examples/00_quick_start/tfidf_covid.ipynb +++ b/examples/00_quick_start/tfidf_covid.ipynb @@ -37,12 +37,12 @@ ], "source": [ "import sys\n", - "# Import functions\n", + "\n", "from recommenders.datasets import covid_utils\n", "from recommenders.models.tfidf.tfidf_utils import TfidfRecommender\n", "\n", "# Print version\n", - "print(\"System version: {}\".format(sys.version))" + "print(f\"System version: {sys.version}\")" ] }, { @@ -102,7 +102,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -1229,4 +1229,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/00_quick_start/wide_deep_movielens.ipynb b/examples/00_quick_start/wide_deep_movielens.ipynb index bacac0122..119528dc9 100644 --- a/examples/00_quick_start/wide_deep_movielens.ipynb +++ b/examples/00_quick_start/wide_deep_movielens.ipynb @@ -62,7 +62,6 @@ "import itertools\n", "import numpy as np\n", "import pandas as pd\n", - "import scrapbook as sb\n", "import sklearn.preprocessing\n", "from tempfile import TemporaryDirectory\n", "import tensorflow as tf\n", @@ -82,6 +81,7 @@ "from recommenders.datasets.python_splitters import python_random_split\n", "import recommenders.evaluation.python_evaluation as evaluator\n", "import recommenders.models.wide_deep.wide_deep_utils as wide_deep\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(f\"System version: {sys.version}\")\n", "print(f\"Tensorflow version: {tf.__version__}\")\n", diff --git a/examples/00_quick_start/xdeepfm_criteo.ipynb b/examples/00_quick_start/xdeepfm_criteo.ipynb index 9479ab99d..14ac0d923 100644 --- a/examples/00_quick_start/xdeepfm_criteo.ipynb +++ b/examples/00_quick_start/xdeepfm_criteo.ipynb @@ -48,7 +48,6 @@ "source": [ "import os\n", "import sys\n", - "import scrapbook as sb\n", "from tempfile import TemporaryDirectory\n", "import tensorflow as tf\n", "tf.get_logger().setLevel('ERROR') # only show error messages\n", @@ -56,9 +55,10 @@ "from recommenders.models.deeprec.deeprec_utils import download_deeprec_resources, prepare_hparams\n", "from recommenders.models.deeprec.models.xDeepFM import XDeepFMModel\n", "from recommenders.models.deeprec.io.iterator import FFMTextIterator\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Tensorflow version: {}\".format(tf.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"Tensorflow version: {tf.__version__}\"))" ] }, { @@ -331,6 +331,7 @@ } ], "source": [ + "# Record results for tests - ignore this cell\n", "store_metadata(\"auc\", result[\"auc\"])\n", "store_metadata(\"logloss\", result[\"logloss\"])" ] @@ -379,4 +380,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/tests/functional/examples/test_notebooks_gpu.py b/tests/functional/examples/test_notebooks_gpu.py index fbfde453a..50e3f3bdb 100644 --- a/tests/functional/examples/test_notebooks_gpu.py +++ b/tests/functional/examples/test_notebooks_gpu.py @@ -247,7 +247,7 @@ def test_wide_deep_functional( os.path.join("tests", "resources", "deeprec", "slirec"), 10, 400, - {"res_syn": {"auc": 0.7183, "logloss": 0.6045}}, + {"auc": 0.7183, "logloss": 0.6045}, 42, ) ], @@ -277,13 +277,11 @@ def test_slirec_quickstart_functional( ) results = read_notebook(output_notebook) - for key, value in expected_values.items(): - assert results[key]["auc"] == pytest.approx(value["auc"], rel=TOL, abs=ABS_TOL) - - ## disable logloss check, because so far SLi-Rec uses ranking loss, not a point-wise loss - # assert results[key]["logloss"] == pytest.approx( - # value["logloss"], rel=TOL, abs=ABS_TOL - # ) + assert results["auc"] == pytest.approx(expected_values["auc"], rel=TOL, abs=ABS_TOL) + ## disable logloss check, because so far SLi-Rec uses ranking loss, not a point-wise loss + # assert results["logloss"] == pytest.approx( + # expected_values["logloss"], rel=TOL, abs=ABS_TOL + # ) @pytest.mark.gpu @@ -297,12 +295,10 @@ def test_slirec_quickstart_functional( 42, "demo", { - "res_syn": { - "group_auc": 0.6217, - "mean_mrr": 0.2783, - "ndcg@5": 0.3024, - "ndcg@10": 0.3719, - } + "group_auc": 0.6217, + "mean_mrr": 0.2783, + "ndcg@5": 0.3024, + "ndcg@10": 0.3719, }, ) ], @@ -330,19 +326,18 @@ def test_nrms_quickstart_functional( ) results = read_notebook(output_notebook) - for key, value in expected_values.items(): - assert results[key]["group_auc"] == pytest.approx( - value["group_auc"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["mean_mrr"] == pytest.approx( - value["mean_mrr"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@5"] == pytest.approx( - value["ndcg@5"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@10"] == pytest.approx( - value["ndcg@10"], rel=TOL, abs=ABS_TOL - ) + assert results["group_auc"] == pytest.approx( + expected_values["group_auc"], rel=TOL, abs=ABS_TOL + ) + assert results["mean_mrr"] == pytest.approx( + expected_values["mean_mrr"], rel=TOL, abs=ABS_TOL + ) + assert results["ndcg@5"] == pytest.approx( + expected_values["ndcg@5"], rel=TOL, abs=ABS_TOL + ) + assert results["ndcg@10"] == pytest.approx( + expected_values["ndcg@10"], rel=TOL, abs=ABS_TOL + ) @pytest.mark.gpu @@ -356,12 +351,10 @@ def test_nrms_quickstart_functional( 42, "demo", { - "res_syn": { - "group_auc": 0.6436, - "mean_mrr": 0.2990, - "ndcg@5": 0.3297, - "ndcg@10": 0.3933, - } + "group_auc": 0.6436, + "mean_mrr": 0.2990, + "ndcg@5": 0.3297, + "ndcg@10": 0.3933, }, ) ], @@ -389,19 +382,18 @@ def test_naml_quickstart_functional( ) results = read_notebook(output_notebook) - for key, value in expected_values.items(): - assert results[key]["group_auc"] == pytest.approx( - value["group_auc"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["mean_mrr"] == pytest.approx( - value["mean_mrr"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@5"] == pytest.approx( - value["ndcg@5"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@10"] == pytest.approx( - value["ndcg@10"], rel=TOL, abs=ABS_TOL - ) + assert results["group_auc"] == pytest.approx( + expected_values["group_auc"], rel=TOL, abs=ABS_TOL + ) + assert results["mean_mrr"] == pytest.approx( + expected_values["mean_mrr"], rel=TOL, abs=ABS_TOL + ) + assert results["ndcg@5"] == pytest.approx( + expected_values["ndcg@5"], rel=TOL, abs=ABS_TOL + ) + assert results["ndcg@10"] == pytest.approx( + expected_values["ndcg@10"], rel=TOL, abs=ABS_TOL + ) @pytest.mark.gpu @@ -415,12 +407,10 @@ def test_naml_quickstart_functional( 42, "demo", { - "res_syn": { - "group_auc": 0.6444, - "mean_mrr": 0.2983, - "ndcg@5": 0.3287, - "ndcg@10": 0.3938, - } + "group_auc": 0.6444, + "mean_mrr": 0.2983, + "ndcg@5": 0.3287, + "ndcg@10": 0.3938, }, ) ], @@ -448,19 +438,18 @@ def test_lstur_quickstart_functional( ) results = read_notebook(output_notebook) - for key, value in expected_values.items(): - assert results[key]["group_auc"] == pytest.approx( - value["group_auc"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["mean_mrr"] == pytest.approx( - value["mean_mrr"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@5"] == pytest.approx( - value["ndcg@5"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@10"] == pytest.approx( - value["ndcg@10"], rel=TOL, abs=ABS_TOL - ) + assert results["group_auc"] == pytest.approx( + expected_values["group_auc"], rel=TOL, abs=ABS_TOL + ) + assert results["mean_mrr"] == pytest.approx( + expected_values["mean_mrr"], rel=TOL, abs=ABS_TOL + ) + assert expected_values["ndcg@5"] == pytest.approx( + value["ndcg@5"], rel=TOL, abs=ABS_TOL + ) + assert expected_values["ndcg@10"] == pytest.approx( + value["ndcg@10"], rel=TOL, abs=ABS_TOL + ) @pytest.mark.gpu @@ -474,12 +463,10 @@ def test_lstur_quickstart_functional( 42, "demo", { - "res_syn": { - "group_auc": 0.6035, - "mean_mrr": 0.2765, - "ndcg@5": 0.2977, - "ndcg@10": 0.3637, - } + "group_auc": 0.6035, + "mean_mrr": 0.2765, + "ndcg@5": 0.2977, + "ndcg@10": 0.3637, }, ) ], @@ -507,19 +494,18 @@ def test_npa_quickstart_functional( ) results = read_notebook(output_notebook) - for key, value in expected_values.items(): - assert results[key]["group_auc"] == pytest.approx( - value["group_auc"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["mean_mrr"] == pytest.approx( - value["mean_mrr"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@5"] == pytest.approx( - value["ndcg@5"], rel=TOL, abs=ABS_TOL - ) - assert results[key]["ndcg@10"] == pytest.approx( - value["ndcg@10"], rel=TOL, abs=ABS_TOL - ) + assert results["group_auc"] == pytest.approx( + expected_values["group_auc"], rel=TOL, abs=ABS_TOL + ) + assert results["mean_mrr"] == pytest.approx( + expected_values["mean_mrr"], rel=TOL, abs=ABS_TOL + ) + assert results["ndcg@5"] == pytest.approx( + expected_values["ndcg@5"], rel=TOL, abs=ABS_TOL + ) + assert results["ndcg@10"] == pytest.approx( + expected_values["ndcg@10"], rel=TOL, abs=ABS_TOL + ) @pytest.mark.gpu @@ -589,10 +575,10 @@ def test_dkn_quickstart_functional(notebooks, output_notebook, kernel_name): ) results = read_notebook(output_notebook) - assert results["res"]["auc"] == pytest.approx(0.5651, rel=TOL, abs=ABS_TOL) - assert results["res"]["mean_mrr"] == pytest.approx(0.1639, rel=TOL, abs=ABS_TOL) - assert results["res"]["ndcg@5"] == pytest.approx(0.1735, rel=TOL, abs=ABS_TOL) - assert results["res"]["ndcg@10"] == pytest.approx(0.2301, rel=TOL, abs=ABS_TOL) + assert results["auc"] == pytest.approx(0.5651, rel=TOL, abs=ABS_TOL) + assert results["mean_mrr"] == pytest.approx(0.1639, rel=TOL, abs=ABS_TOL) + assert results["ndcg@5"] == pytest.approx(0.1735, rel=TOL, abs=ABS_TOL) + assert results["ndcg@10"] == pytest.approx(0.2301, rel=TOL, abs=ABS_TOL) @pytest.mark.gpu @@ -697,6 +683,7 @@ def test_benchmark_movielens_gpu( parameters=dict(data_sizes=size, algorithms=algos), ) results = read_notebook(output_notebook) + assert len(results["results"]) == 4 for i, value in enumerate(results["results"]): assert results["results"][i] == pytest.approx(value, rel=TOL, abs=ABS_TOL) diff --git a/tests/smoke/examples/test_notebooks_python.py b/tests/smoke/examples/test_notebooks_python.py index a7463d9b1..c7c6df3aa 100644 --- a/tests/smoke/examples/test_notebooks_python.py +++ b/tests/smoke/examples/test_notebooks_python.py @@ -110,14 +110,10 @@ def test_lightgbm_quickstart_smoke(notebooks, output_notebook, kernel_name): ) results = read_notebook(output_notebook) - assert results["res_basic"]["auc"] == pytest.approx(0.7674, rel=TOL, abs=ABS_TOL) - assert results["res_basic"]["logloss"] == pytest.approx( - 0.4669, rel=TOL, abs=ABS_TOL - ) - assert results["res_optim"]["auc"] == pytest.approx(0.7757, rel=TOL, abs=ABS_TOL) - assert results["res_optim"]["logloss"] == pytest.approx( - 0.4607, rel=TOL, abs=ABS_TOL - ) + assert results["auc_basic"] == pytest.approx(0.7674, rel=TOL, abs=ABS_TOL) + assert results["logloss_basic"] == pytest.approx(0.4669, rel=TOL, abs=ABS_TOL) + assert results["auc_opt"] == pytest.approx(0.7757, rel=TOL, abs=ABS_TOL) + assert results["logloss_opt"] == pytest.approx(0.4607, rel=TOL, abs=ABS_TOL) @pytest.mark.notebooks From 055e5f059cd77e7fbc6a414bb63eef171723e8bc Mon Sep 17 00:00:00 2001 From: miguelgfierro Date: Tue, 31 Oct 2023 12:21:56 +0100 Subject: [PATCH 09/22] Update new programmatic execution code Signed-off-by: miguelgfierro --- examples/00_quick_start/als_movielens.ipynb | 2 +- .../als_deep_dive.ipynb | 32 +- .../baseline_deep_dive.ipynb | 37 +- .../cornac_bivae_deep_dive.ipynb | 23 +- .../cornac_bpr_deep_dive.ipynb | 14 +- .../lightgcn_deep_dive.ipynb | 12 +- .../multi_vae_deep_dive.ipynb | 15 +- .../ncf_deep_dive.ipynb | 2 +- .../rbm_deep_dive.ipynb | 12 +- .../sar_deep_dive.ipynb | 5 +- .../surprise_svd_deep_dive.ipynb | 94 +- .../dkn_deep_dive.ipynb | 16 +- .../mmlspark_lightgbm_criteo.ipynb | 13 +- .../vowpal_wabbit_deep_dive.ipynb | 50 +- examples/02_model_hybrid/fm_deep_dive.ipynb | 1094 ++--------------- .../02_model_hybrid/lightfm_deep_dive.ipynb | 40 +- .../als_movielens_diversity_metrics.ipynb | 27 +- .../azureml_hyperdrive_surprise_svd.ipynb | 3 +- .../azureml_hyperdrive_wide_and_deep.ipynb | 3 +- .../nni_ncf.ipynb | 3 +- .../nni_surprise_svd.ipynb | 3 +- .../tuning_spark_als.ipynb | 518 ++++---- examples/06_benchmarks/movielens.ipynb | 19 +- 23 files changed, 556 insertions(+), 1481 deletions(-) diff --git a/examples/00_quick_start/als_movielens.ipynb b/examples/00_quick_start/als_movielens.ipynb index 2e5b4d7f6..e485a216d 100644 --- a/examples/00_quick_start/als_movielens.ipynb +++ b/examples/00_quick_start/als_movielens.ipynb @@ -62,7 +62,7 @@ "from recommenders.utils.spark_utils import start_or_get_spark\n", "from recommenders.utils.notebook_utils import store_metadata\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", + "print(f\"System version: {sys.version}\")\n", "print(\"Spark version: {}\".format(pyspark.__version__))\n" ] }, diff --git a/examples/02_model_collaborative_filtering/als_deep_dive.ipynb b/examples/02_model_collaborative_filtering/als_deep_dive.ipynb index 81d6f81d7..dceff16a6 100644 --- a/examples/02_model_collaborative_filtering/als_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/als_deep_dive.ipynb @@ -102,16 +102,14 @@ } ], "source": [ - "# set the environment path to find Recommenders\n", + "import warnings\n", + "warnings.simplefilter(action='ignore', category=FutureWarning)\n", + "\n", "import sys\n", - "import pandas as pd\n", "from matplotlib import pyplot as plt\n", "import numpy as np\n", - "import seaborn as sns\n", - "import sys\n", "import pandas as pd\n", - "import warnings\n", - "warnings.simplefilter(action='ignore', category=FutureWarning)\n", + "import seaborn as sns\n", "\n", "import pyspark\n", "from pyspark.sql import SparkSession\n", @@ -128,9 +126,9 @@ "from recommenders.tuning.parameter_sweep import generate_param_grid\n", "from recommenders.datasets.spark_splitters import spark_random_split\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Pandas version: {}\".format(pd.__version__))\n", - "print(\"PySpark version: {}\".format(pyspark.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"Pandas version: {pd.__version__}\")\n", + "print(f\"PySpark version: {pyspark.__version__}\")" ] }, { @@ -458,9 +456,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "\r", - "[Stage 481:> (0 + 1) / 1]\r", - "\r", + "\r\n", + "[Stage 481:> (0 + 1) / 1]\r\n", + "\r\n", " \r" ] } @@ -511,7 +509,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\r", + "\r\n", " \r" ] } @@ -659,7 +657,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -750,7 +748,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\r", + "\r\n", " \r" ] } @@ -807,7 +805,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\r", + "\r\n", " \r" ] } @@ -886,4 +884,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb b/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb index 4df1b961c..f8b627a54 100644 --- a/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/baseline_deep_dive.ipynb @@ -67,22 +67,26 @@ ], "source": [ "import sys\n", - "\n", "import itertools\n", "import pandas as pd\n", - "import scrapbook as sb\n", "\n", - "from recommenders.utils.notebook_utils import is_jupyter\n", "from recommenders.datasets import movielens\n", "from recommenders.datasets.python_splitters import python_random_split\n", "from recommenders.datasets.pandas_df_utils import filter_by\n", "from recommenders.evaluation.python_evaluation import (\n", - " rmse, mae, rsquared, exp_var,\n", - " map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", + " rmse,\n", + " mae,\n", + " rsquared,\n", + " exp_var,\n", + " map_at_k,\n", + " ndcg_at_k,\n", + " precision_at_k,\n", + " recall_at_k,\n", ")\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(f\"System version: {sys.version}\")\n", - "print(f\"Pandas version: {pd.__version__}\")" + "print(f\"Pandas version: {pd.__version__}\")\n" ] }, { @@ -861,16 +865,15 @@ } ], "source": [ - "if is_jupyter():\n", - " # Record results with papermill and scrapbook for tests\n", - " store_metadata(\"map\", eval_map)\n", - " store_metadata(\"ndcg\", eval_ndcg)\n", - " store_metadata(\"precision\", eval_precision)\n", - " store_metadata(\"recall\", eval_recall)\n", - " store_metadata(\"rmse\", eval_rmse)\n", - " store_metadata(\"mae\", eval_mae)\n", - " store_metadata(\"exp_var\", eval_exp_var)\n", - " store_metadata(\"rsquared\", eval_rsquared)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"map\", eval_map)\n", + "store_metadata(\"ndcg\", eval_ndcg)\n", + "store_metadata(\"precision\", eval_precision)\n", + "store_metadata(\"recall\", eval_recall)\n", + "store_metadata(\"rmse\", eval_rmse)\n", + "store_metadata(\"mae\", eval_mae)\n", + "store_metadata(\"exp_var\", eval_exp_var)\n", + "store_metadata(\"rsquared\", eval_rsquared)" ] }, { @@ -906,4 +909,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb b/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb index 8d177b07a..c2ff20337 100644 --- a/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/cornac_bivae_deep_dive.ipynb @@ -44,23 +44,28 @@ } ], "source": [ - "import sys\n", "import os\n", + "import sys\n", "import torch\n", "import cornac\n", - "import papermill as pm\n", - "import scrapbook as sb\n", "import pandas as pd\n", + "\n", "from recommenders.datasets import movielens\n", "from recommenders.datasets.python_splitters import python_random_split\n", - "from recommenders.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", "from recommenders.models.cornac.cornac_utils import predict_ranking\n", "from recommenders.utils.timer import Timer\n", "from recommenders.utils.constants import SEED\n", + "from recommenders.evaluation.python_evaluation import (\n", + " map_at_k,\n", + " ndcg_at_k,\n", + " precision_at_k,\n", + " recall_at_k,\n", + ")\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"PyTorch version: {}\".format(torch.__version__))\n", - "print(\"Cornac version: {}\".format(cornac.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"PyTorch version: {torch.__version__}\")\n", + "print(f\"Cornac version: {cornac.__version__}\")\n" ] }, { @@ -593,7 +598,7 @@ } ], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"map\", eval_map)\n", "store_metadata(\"ndcg\", eval_ndcg)\n", "store_metadata(\"precision\", eval_precision)\n", @@ -647,4 +652,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb b/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb index 50666e82d..fb0253a2f 100644 --- a/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/cornac_bpr_deep_dive.ipynb @@ -42,21 +42,21 @@ } ], "source": [ - "import sys\n", "import os\n", + "import sys\n", "import cornac\n", - "import papermill as pm\n", - "import scrapbook as sb\n", "import pandas as pd\n", + "\n", "from recommenders.datasets import movielens\n", "from recommenders.datasets.python_splitters import python_random_split\n", "from recommenders.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", "from recommenders.models.cornac.cornac_utils import predict_ranking\n", "from recommenders.utils.timer import Timer\n", "from recommenders.utils.constants import SEED\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Cornac version: {}\".format(cornac.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"Cornac version: {cornac.__version__}\")" ] }, { @@ -574,7 +574,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"map\", eval_map)\n", "store_metadata(\"ndcg\", eval_ndcg)\n", "store_metadata(\"precision\", eval_precision)\n", @@ -615,4 +615,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb b/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb index bcc9992e4..63fca3805 100644 --- a/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/lightgcn_deep_dive.ipynb @@ -52,7 +52,6 @@ "source": [ "import sys\n", "import os\n", - "import scrapbook as sb\n", "import pandas as pd\n", "import numpy as np\n", "import tensorflow as tf\n", @@ -66,10 +65,11 @@ "from recommenders.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", "from recommenders.utils.constants import SEED as DEFAULT_SEED\n", "from recommenders.models.deeprec.deeprec_utils import prepare_hparams\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Pandas version: {}\".format(pd.__version__))\n", - "print(\"Tensorflow version: {}\".format(tf.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"Pandas version: {pd.__version__}\")\n", + "print(f\"Tensorflow version: {tf.__version__}\")" ] }, { @@ -732,7 +732,7 @@ } ], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"map\", eval_map)\n", "store_metadata(\"ndcg\", eval_ndcg)\n", "store_metadata(\"precision\", eval_precision)\n", @@ -822,4 +822,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/multi_vae_deep_dive.ipynb b/examples/02_model_collaborative_filtering/multi_vae_deep_dive.ipynb index 7f7dcee6d..0c6d33d26 100644 --- a/examples/02_model_collaborative_filtering/multi_vae_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/multi_vae_deep_dive.ipynb @@ -74,30 +74,27 @@ "import os\n", "import numpy as np\n", "import pandas as pd\n", - "import papermill as pm\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "import seaborn as sns\n", "sns.set()\n", "import tensorflow as tf\n", "import keras\n", + "from tempfile import TemporaryDirectory\n", "\n", "from recommenders.utils.timer import Timer\n", "from recommenders.datasets import movielens\n", "from recommenders.datasets.split_utils import min_rating_filter_pandas\n", "from recommenders.datasets.python_splitters import numpy_stratified_split\n", "from recommenders.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", - "\n", "from recommenders.datasets.sparse import AffinityMatrix\n", "from recommenders.utils.python_utils import binarize\n", "from recommenders.models.vae.multinomial_vae import Mult_VAE\n", "\n", - "from tempfile import TemporaryDirectory\n", - "\n", - "print(\"System version: {}\".format(sys.version))\n", - "print(\"Pandas version: {}\".format(pd.__version__))\n", - "print(\"Tensorflow version: {}\".format(tf.__version__))\n", - "print(\"Keras version: {}\".format(keras.__version__))" + "print(f\"System version: {sys.version}\")\n", + "print(f\"Pandas version: {pd.__version__}\")\n", + "print(f\"Tensorflow version: {tf.__version__}\")\n", + "print(f\"Keras version: {keras.__version__}\")" ] }, { @@ -1869,4 +1866,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb b/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb index 93ee20717..55c2a27d2 100644 --- a/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/ncf_deep_dive.ipynb @@ -1069,7 +1069,7 @@ } ], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"map\", eval_map)\n", "store_metadata(\"ndcg\", eval_ndcg)\n", "store_metadata(\"precision\", eval_precision)\n", diff --git a/examples/02_model_collaborative_filtering/rbm_deep_dive.ipynb b/examples/02_model_collaborative_filtering/rbm_deep_dive.ipynb index 67495e628..9484ef0df 100644 --- a/examples/02_model_collaborative_filtering/rbm_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/rbm_deep_dive.ipynb @@ -74,13 +74,7 @@ } ], "source": [ - "from __future__ import print_function\n", - "from __future__ import absolute_import\n", - "from __future__ import division\n", - "\n", - "# set the environment path to find Recommenders\n", "import sys\n", - "\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline \n", @@ -90,16 +84,12 @@ "import tensorflow as tf\n", "tf.get_logger().setLevel(logging.ERROR)\n", "\n", - "#RBM \n", "from recommenders.models.rbm.rbm import RBM\n", "from recommenders.datasets.python_splitters import numpy_stratified_split\n", "from recommenders.datasets.sparse import AffinityMatrix\n", "from recommenders.utils.timer import Timer\n", "from recommenders.utils.plot import line_graph\n", - "\n", - "#Evaluation libraries\n", "from recommenders.datasets import movielens \n", - "\n", "from recommenders.evaluation.python_evaluation import (\n", " map_at_k,\n", " ndcg_at_k,\n", @@ -1154,4 +1144,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb b/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb index 3d89f31d0..d25baf4b4 100644 --- a/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/sar_deep_dive.ipynb @@ -109,18 +109,17 @@ "metadata": {}, "outputs": [], "source": [ - "# set the environment path to find Recommenders\n", "import sys\n", "import logging\n", "import scipy\n", "import numpy as np\n", "import pandas as pd\n", - "import scrapbook as sb\n", "\n", "from recommenders.datasets import movielens\n", "from recommenders.datasets.python_splitters import python_stratified_split\n", "from recommenders.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k\n", "from recommenders.models.sar import SAR\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(f\"System version: {sys.version}\")\n", "print(f\"Pandas version: {pd.__version__}\")\n", @@ -553,4 +552,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb b/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb index 7c35b3d00..005d0f8ab 100644 --- a/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb +++ b/examples/02_model_collaborative_filtering/surprise_svd_deep_dive.ipynb @@ -101,18 +101,31 @@ "import os\n", "import sys\n", "import surprise\n", - "import scrapbook as sb\n", "import pandas as pd\n", "\n", "from recommenders.utils.timer import Timer\n", "from recommenders.datasets import movielens\n", "from recommenders.datasets.python_splitters import python_random_split\n", - "from recommenders.evaluation.python_evaluation import (rmse, mae, rsquared, exp_var, map_at_k, ndcg_at_k, precision_at_k, \n", - " recall_at_k, get_top_k_items)\n", - "from recommenders.models.surprise.surprise_utils import predict, compute_ranking_predictions\n", + "from recommenders.evaluation.python_evaluation import (\n", + " rmse,\n", + " mae,\n", + " rsquared,\n", + " exp_var,\n", + " map_at_k,\n", + " ndcg_at_k,\n", + " precision_at_k,\n", + " recall_at_k,\n", + " get_top_k_items,\n", + ")\n", + "from recommenders.models.surprise.surprise_utils import (\n", + " predict,\n", + " compute_ranking_predictions,\n", + ")\n", + "from recommenders.utils.notebook_utils import store_metadata\n", + "\n", "\n", "print(f\"System version: {sys.version}\")\n", - "print(f\"Surprise version: {surprise.__version__}\")" + "print(f\"Surprise version: {surprise.__version__}\")\n" ] }, { @@ -129,7 +142,7 @@ "TOP_K = 10\n", "\n", "# Select MovieLens data size: 100k, 1m, 10m, or 20m\n", - "MOVIELENS_DATA_SIZE = '100k'" + "MOVIELENS_DATA_SIZE = \"100k\"\n" ] }, { @@ -228,11 +241,10 @@ ], "source": [ "data = movielens.load_pandas_df(\n", - " size=MOVIELENS_DATA_SIZE,\n", - " header=[\"userID\", \"itemID\", \"rating\"]\n", + " size=MOVIELENS_DATA_SIZE, header=[\"userID\", \"itemID\", \"rating\"]\n", ")\n", "\n", - "data.head()" + "data.head()\n" ] }, { @@ -252,7 +264,7 @@ "metadata": {}, "outputs": [], "source": [ - "train, test = python_random_split(data, 0.75)" + "train, test = python_random_split(data, 0.75)\n" ] }, { @@ -284,8 +296,10 @@ "# 'reader' is being used to get rating scale (for MovieLens, the scale is [1, 5]).\n", "# 'rating_scale' parameter can be used instead for the later version of surprise lib:\n", "# https://github.com/NicolasHug/Surprise/blob/master/surprise/dataset.py\n", - "train_set = surprise.Dataset.load_from_df(train, reader=surprise.Reader('ml-100k')).build_full_trainset()\n", - "train_set" + "train_set = surprise.Dataset.load_from_df(\n", + " train, reader=surprise.Reader(\"ml-100k\")\n", + ").build_full_trainset()\n", + "train_set\n" ] }, { @@ -351,7 +365,7 @@ "with Timer() as train_time:\n", " svd.fit(train_set)\n", "\n", - "print(f\"Took {train_time.interval} seconds for training.\")" + "print(f\"Took {train_time.interval} seconds for training.\")\n" ] }, { @@ -444,8 +458,8 @@ } ], "source": [ - "predictions = predict(svd, test, usercol='userID', itemcol='itemID')\n", - "predictions.head()" + "predictions = predict(svd, test, usercol=\"userID\", itemcol=\"itemID\")\n", + "predictions.head()\n" ] }, { @@ -472,9 +486,11 @@ ], "source": [ "with Timer() as test_time:\n", - " all_predictions = compute_ranking_predictions(svd, train, usercol='userID', itemcol='itemID', remove_seen=True)\n", - " \n", - "print(f\"Took {test_time.interval} seconds for prediction.\")" + " all_predictions = compute_ranking_predictions(\n", + " svd, train, usercol=\"userID\", itemcol=\"itemID\", remove_seen=True\n", + " )\n", + "\n", + "print(f\"Took {test_time.interval} seconds for prediction.\")\n" ] }, { @@ -558,7 +574,7 @@ } ], "source": [ - "all_predictions.head()" + "all_predictions.head()\n" ] }, { @@ -603,23 +619,31 @@ "eval_rsquared = rsquared(test, predictions)\n", "eval_exp_var = exp_var(test, predictions)\n", "\n", - "eval_map = map_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)\n", - "eval_ndcg = ndcg_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)\n", - "eval_precision = precision_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)\n", - "eval_recall = recall_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)\n", + "eval_map = map_at_k(test, all_predictions, col_prediction=\"prediction\", k=TOP_K)\n", + "eval_ndcg = ndcg_at_k(test, all_predictions, col_prediction=\"prediction\", k=TOP_K)\n", + "eval_precision = precision_at_k(\n", + " test, all_predictions, col_prediction=\"prediction\", k=TOP_K\n", + ")\n", + "eval_recall = recall_at_k(test, all_predictions, col_prediction=\"prediction\", k=TOP_K)\n", "\n", "\n", - "print(\"RMSE:\\t\\t%f\" % eval_rmse,\n", - " \"MAE:\\t\\t%f\" % eval_mae,\n", - " \"rsquared:\\t%f\" % eval_rsquared,\n", - " \"exp var:\\t%f\" % eval_exp_var, sep='\\n')\n", + "print(\n", + " \"RMSE:\\t\\t%f\" % eval_rmse,\n", + " \"MAE:\\t\\t%f\" % eval_mae,\n", + " \"rsquared:\\t%f\" % eval_rsquared,\n", + " \"exp var:\\t%f\" % eval_exp_var,\n", + " sep=\"\\n\",\n", + ")\n", "\n", - "print('----')\n", + "print(\"----\")\n", "\n", - "print(\"MAP:\\t\\t%f\" % eval_map,\n", - " \"NDCG:\\t\\t%f\" % eval_ndcg,\n", - " \"Precision@K:\\t%f\" % eval_precision,\n", - " \"Recall@K:\\t%f\" % eval_recall, sep='\\n')" + "print(\n", + " \"MAP:\\t\\t%f\" % eval_map,\n", + " \"NDCG:\\t\\t%f\" % eval_ndcg,\n", + " \"Precision@K:\\t%f\" % eval_precision,\n", + " \"Recall@K:\\t%f\" % eval_recall,\n", + " sep=\"\\n\",\n", + ")\n" ] }, { @@ -809,7 +833,7 @@ } ], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"rmse\", eval_rmse)\n", "store_metadata(\"mae\", eval_mae)\n", "store_metadata(\"rsquared\", eval_rsquared)\n", @@ -819,7 +843,7 @@ "store_metadata(\"precision\", eval_precision)\n", "store_metadata(\"recall\", eval_recall)\n", "store_metadata(\"train_time\", train_time.interval)\n", - "store_metadata(\"test_time\", test_time.interval)" + "store_metadata(\"test_time\", test_time.interval)\n" ] }, { @@ -856,4 +880,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_content_based_filtering/dkn_deep_dive.ipynb b/examples/02_model_content_based_filtering/dkn_deep_dive.ipynb index ff9d397e5..98442c0bd 100644 --- a/examples/02_model_content_based_filtering/dkn_deep_dive.ipynb +++ b/examples/02_model_content_based_filtering/dkn_deep_dive.ipynb @@ -136,13 +136,11 @@ } ], "source": [ - "import sys\n", - "\n", "import os\n", + "import sys\n", "from tempfile import TemporaryDirectory\n", "import logging\n", - "import papermill as pm\n", - "import scrapbook as sb\n", + "\n", "import tensorflow as tf\n", "tf.get_logger().setLevel('ERROR') # only show error messages\n", "\n", @@ -158,6 +156,7 @@ "from recommenders.models.deeprec.deeprec_utils import prepare_hparams\n", "from recommenders.models.deeprec.models.dkn import DKN\n", "from recommenders.models.deeprec.io.dkn_iterator import DKNTextIterator\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(f\"System version: {sys.version}\")\n", "print(f\"Tensorflow version: {tf.__version__}\")" @@ -466,7 +465,12 @@ } ], "source": [ - "sb.glue(\"res\", res)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"auc\", res[\"auc\"])\n", + "store_metadata(\"group_auc\", res[\"group_auc\"])\n", + "store_metadata(\"ndcg@5\", res[\"ndcg@5\"])\n", + "store_metadata(\"ndcg@10\", res[\"ndcg@10\"])\n", + "store_metadata(\"mean_mrr\", res[\"mean_mrr\"])" ] }, { @@ -558,4 +562,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb b/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb index 92ae786ee..ed42529ff 100644 --- a/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb +++ b/examples/02_model_content_based_filtering/mmlspark_lightgbm_criteo.ipynb @@ -60,25 +60,20 @@ } ], "source": [ + "import warnings\n", + "warnings.simplefilter(action='ignore', category=FutureWarning)\n", + "\n", "import os\n", "import sys\n", "\n", - "\n", "import pyspark\n", "from pyspark.ml import PipelineModel\n", "from pyspark.ml.feature import FeatureHasher\n", - "import warnings\n", - "warnings.simplefilter(action='ignore', category=FutureWarning)\n", - "import papermill as pm\n", - "import scrapbook as sb\n", - "\n", "\n", "from recommenders.utils.notebook_utils import is_databricks\n", "from recommenders.utils.spark_utils import start_or_get_spark\n", "from recommenders.datasets.criteo import load_spark_df\n", "from recommenders.datasets.spark_splitters import spark_random_split\n", - "\n", - "# Setup MML Spark\n", "from recommenders.utils.spark_utils import MMLSPARK_REPO, MMLSPARK_PACKAGE\n", "\n", "# On Spark >3.0.0,<3.2.0, the following should be set:\n", @@ -440,7 +435,7 @@ } ], "source": [ - "# Record results with papermill for tests\n", + "# Record results for tests - ignore this cell\n", "store_metadata(\"auc\", auc)" ] }, diff --git a/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb b/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb index 441c33876..26540bcdb 100644 --- a/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb +++ b/examples/02_model_content_based_filtering/vowpal_wabbit_deep_dive.ipynb @@ -75,25 +75,30 @@ } ], "source": [ - "import sys\n", - "\n", "import os\n", + "import sys\n", + "import pandas as pd\n", "from subprocess import run\n", "from tempfile import TemporaryDirectory\n", "from time import process_time\n", "\n", - "import pandas as pd\n", - "import papermill as pm\n", - "import scrapbook as sb\n", - "\n", - "from recommenders.utils.notebook_utils import is_jupyter\n", "from recommenders.datasets.movielens import load_pandas_df\n", "from recommenders.datasets.python_splitters import python_random_split\n", - "from recommenders.evaluation.python_evaluation import (rmse, mae, exp_var, rsquared, get_top_k_items,\n", - " map_at_k, ndcg_at_k, precision_at_k, recall_at_k)\n", + "from recommenders.evaluation.python_evaluation import (\n", + " rmse,\n", + " mae,\n", + " exp_var,\n", + " rsquared,\n", + " get_top_k_items,\n", + " map_at_k,\n", + " ndcg_at_k,\n", + " precision_at_k,\n", + " recall_at_k,\n", + ")\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", - "print(\"Pandas version: {}\".format(pd.__version__))" + "print(\"Pandas version: {}\".format(pd.__version__))\n" ] }, { @@ -1316,18 +1321,17 @@ } ], "source": [ - "# record results for testing\n", - "if is_jupyter():\n", - " store_metadata('rmse', saved_result['RMSE'])\n", - " store_metadata('mae', saved_result['MAE'])\n", - " store_metadata('rsquared', saved_result['R2'])\n", - " store_metadata('exp_var', saved_result['Explained Variance'])\n", - " store_metadata(\"train_time\", saved_result['Train Time (ms)'])\n", - " store_metadata(\"test_time\", test_time)\n", - " store_metadata('map', rank_metrics['MAP'])\n", - " store_metadata('ndcg', rank_metrics['NDCG'])\n", - " store_metadata('precision', rank_metrics['Precision'])\n", - " store_metadata('recall', rank_metrics['Recall'])" + "# Record results for tests - ignore this cell\n", + "store_metadata('rmse', saved_result['RMSE'])\n", + "store_metadata('mae', saved_result['MAE'])\n", + "store_metadata('rsquared', saved_result['R2'])\n", + "store_metadata('exp_var', saved_result['Explained Variance'])\n", + "store_metadata(\"train_time\", saved_result['Train Time (ms)'])\n", + "store_metadata(\"test_time\", test_time)\n", + "store_metadata('map', rank_metrics['MAP'])\n", + "store_metadata('ndcg', rank_metrics['NDCG'])\n", + "store_metadata('precision', rank_metrics['Precision'])\n", + "store_metadata('recall', rank_metrics['Recall'])" ] }, { @@ -1374,4 +1378,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_hybrid/fm_deep_dive.ipynb b/examples/02_model_hybrid/fm_deep_dive.ipynb index 5920b021f..04d782e5a 100644 --- a/examples/02_model_hybrid/fm_deep_dive.ipynb +++ b/examples/02_model_hybrid/fm_deep_dive.ipynb @@ -238,10 +238,8 @@ } ], "source": [ - "import sys\n", "import os\n", - "import papermill as pm\n", - "import scrapbook as sb\n", + "import sys\n", "from tempfile import TemporaryDirectory\n", "import xlearn as xl\n", "from sklearn.metrics import roc_auc_score\n", @@ -256,6 +254,7 @@ "from recommenders.datasets.download_utils import maybe_download, unzip_file\n", "from recommenders.tuning.parameter_sweep import generate_param_grid\n", "from recommenders.datasets.pandas_df_utils import LibffmConverter\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Xlearn version: {}\".format(xl.__version__))" @@ -370,17 +369,19 @@ } ], "source": [ - "df_feature_original = pd.DataFrame({\n", - " 'rating': [1, 0, 0, 1, 1],\n", - " 'field1': ['xxx1', 'xxx2', 'xxx4', 'xxx4', 'xxx4'],\n", - " 'field2': [3, 4, 5, 6, 7],\n", - " 'field3': [1.0, 2.0, 3.0, 4.0, 5.0],\n", - " 'field4': ['1', '2', '3', '4', '5']\n", - "})\n", + "df_feature_original = pd.DataFrame(\n", + " {\n", + " \"rating\": [1, 0, 0, 1, 1],\n", + " \"field1\": [\"xxx1\", \"xxx2\", \"xxx4\", \"xxx4\", \"xxx4\"],\n", + " \"field2\": [3, 4, 5, 6, 7],\n", + " \"field3\": [1.0, 2.0, 3.0, 4.0, 5.0],\n", + " \"field4\": [\"1\", \"2\", \"3\", \"4\", \"5\"],\n", + " }\n", + ")\n", "\n", - "converter = LibffmConverter().fit(df_feature_original, col_rating='rating')\n", + "converter = LibffmConverter().fit(df_feature_original, col_rating=\"rating\")\n", "df_out = converter.transform(df_feature_original)\n", - "df_out" + "df_out\n" ] }, { @@ -397,7 +398,11 @@ } ], "source": [ - "print('There are in total {0} fields and {1} features.'.format(converter.field_count, converter.feature_count))" + "print(\n", + " \"There are in total {0} fields and {1} features.\".format(\n", + " converter.field_count, converter.feature_count\n", + " )\n", + ")\n" ] }, { @@ -421,11 +426,11 @@ "LEARNING_RATE = 0.2\n", "LAMBDA = 0.002\n", "EPOCH = 10\n", - "OPT_METHOD = \"sgd\" # options are \"sgd\", \"adagrad\" and \"ftrl\"\n", + "OPT_METHOD = \"sgd\" # options are \"sgd\", \"adagrad\" and \"ftrl\"\n", "\n", "# The metrics for binary classification options are \"acc\", \"prec\", \"f1\" and \"auc\"\n", "# for regression, options are \"rmse\", \"mae\", \"mape\"\n", - "METRIC = \"auc\" \n" + "METRIC = \"auc\"\n" ] }, { @@ -460,9 +465,11 @@ "model_file = os.path.join(data_path, MODEL_FILE_NAME)\n", "output_file = os.path.join(data_path, OUTPUT_FILE_NAME)\n", "\n", - "assets_url = \"https://recodatasets.z20.web.core.windows.net/deeprec/xdeepfmresources.zip\"\n", + "assets_url = (\n", + " \"https://recodatasets.z20.web.core.windows.net/deeprec/xdeepfmresources.zip\"\n", + ")\n", "assets_file = maybe_download(assets_url, work_directory=data_path)\n", - "unzip_file(assets_file, data_path)" + "unzip_file(assets_file, data_path)\n" ] }, { @@ -494,8 +501,8 @@ ], "source": [ "# Training task\n", - "ffm_model = xl.create_ffm() # Use field-aware factorization machine (ffm)\n", - "ffm_model.setTrain(train_file) # Set the path of training dataset\n", + "ffm_model = xl.create_ffm() # Use field-aware factorization machine (ffm)\n", + "ffm_model.setTrain(train_file) # Set the path of training dataset\n", "ffm_model.setValidate(valid_file) # Set the path of validation dataset\n", "\n", "# Parameters:\n", @@ -505,19 +512,20 @@ "# 3. evaluation metric: auc\n", "# 4. number of epochs: 10\n", "# 5. optimization method: sgd\n", - "param = {\"task\":\"binary\", \n", - " \"lr\": LEARNING_RATE, \n", - " \"lambda\": LAMBDA, \n", - " \"metric\": METRIC,\n", - " \"epoch\": EPOCH,\n", - " \"opt\": OPT_METHOD\n", - " }\n", + "param = {\n", + " \"task\": \"binary\",\n", + " \"lr\": LEARNING_RATE,\n", + " \"lambda\": LAMBDA,\n", + " \"metric\": METRIC,\n", + " \"epoch\": EPOCH,\n", + " \"opt\": OPT_METHOD,\n", + "}\n", "\n", "# Start to train\n", "# The trained model will be stored in model.out\n", "with Timer() as time_train:\n", " ffm_model.fit(param, model_file)\n", - "print(f\"Training time: {time_train}\")" + "print(f\"Training time: {time_train}\")\n" ] }, { @@ -536,13 +544,13 @@ "source": [ "# Prediction task\n", "ffm_model.setTest(test_file) # Set the path of test dataset\n", - "ffm_model.setSigmoid() # Convert output to 0-1\n", + "ffm_model.setSigmoid() # Convert output to 0-1\n", "\n", "# Start to predict\n", "# The output result will be stored in output.txt\n", "with Timer() as time_predict:\n", " ffm_model.predict(model_file, output_file)\n", - "print(f\"Prediction time: {time_predict}\")" + "print(f\"Prediction time: {time_predict}\")\n" ] }, { @@ -552,24 +560,6 @@ "The output are the predicted labels (i.e., 1 or 0) for the testing data set. AUC score is calculated to evaluate the model performance." ] }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "with open(output_file) as f:\n", - " predictions = f.readlines()\n", - "\n", - "with open(test_file) as f:\n", - " truths = f.readlines()\n", - "\n", - "truths = np.array([float(truth.split(' ')[0]) for truth in truths])\n", - "predictions = np.array([float(prediction.strip('')) for prediction in predictions])\n", - "\n", - "auc_score = roc_auc_score(truths, predictions)" - ] - }, { "cell_type": "code", "execution_count": 14, @@ -587,7 +577,18 @@ } ], "source": [ - "auc_score" + "with open(output_file) as f:\n", + " predictions = f.readlines()\n", + "\n", + "with open(test_file) as f:\n", + " truths = f.readlines()\n", + "\n", + "truths = np.array([float(truth.split(\" \")[0]) for truth in truths])\n", + "predictions = np.array([float(prediction.strip(\"\")) for prediction in predictions])\n", + "\n", + "auc_score = roc_auc_score(truths, predictions)\n", + "\n", + "print(auc_score)\n" ] }, { @@ -606,7 +607,7 @@ } ], "source": [ - "store_metadata('auc_score', auc_score)" + "store_metadata(\"auc_score\", auc_score)\n" ] }, { @@ -651,12 +652,9 @@ "metadata": {}, "outputs": [], "source": [ - "param_dict = {\n", - " \"lr\": [0.0001, 0.001, 0.01],\n", - " \"lambda\": [0.001, 0.01, 0.1]\n", - "}\n", + "param_dict = {\"lr\": [0.0001, 0.001, 0.01], \"lambda\": [0.001, 0.01, 0.1]}\n", "\n", - "param_grid = generate_param_grid(param_dict)" + "param_grid = generate_param_grid(param_dict)\n" ] }, { @@ -669,13 +667,13 @@ "\n", "with Timer() as time_tune:\n", " for param in param_grid:\n", - " ffm_model = xl.create_ffm() \n", - " ffm_model.setTrain(train_file) \n", + " ffm_model = xl.create_ffm()\n", + " ffm_model.setTrain(train_file)\n", " ffm_model.setValidate(valid_file)\n", " ffm_model.fit(param, model_file)\n", "\n", - " ffm_model.setTest(test_file) \n", - " ffm_model.setSigmoid() \n", + " ffm_model.setTest(test_file)\n", + " ffm_model.setSigmoid()\n", " ffm_model.predict(model_file, output_file)\n", "\n", " with open(output_file) as f:\n", @@ -684,10 +682,12 @@ " with open(test_file) as f:\n", " truths = f.readlines()\n", "\n", - " truths = np.array([float(truth.split(' ')[0]) for truth in truths])\n", - " predictions = np.array([float(prediction.strip('')) for prediction in predictions])\n", + " truths = np.array([float(truth.split(\" \")[0]) for truth in truths])\n", + " predictions = np.array(\n", + " [float(prediction.strip(\"\")) for prediction in predictions]\n", + " )\n", "\n", - " auc_scores.append(roc_auc_score(truths, predictions))" + " auc_scores.append(roc_auc_score(truths, predictions))\n" ] }, { @@ -704,7 +704,7 @@ } ], "source": [ - "print('Tuning by grid search takes {0:.2} min'.format(time_tune.interval / 60))" + "print(\"Tuning by grid search takes {0:.2} min\".format(time_tune.interval / 60))\n" ] }, { @@ -781,15 +781,17 @@ } ], "source": [ - "auc_scores = [float('%.4f' % x) for x in auc_scores]\n", - "auc_scores_array = np.reshape(auc_scores, (len(param_dict[\"lr\"]), len(param_dict[\"lambda\"]))) \n", + "auc_scores = [float(\"%.4f\" % x) for x in auc_scores]\n", + "auc_scores_array = np.reshape(\n", + " auc_scores, (len(param_dict[\"lr\"]), len(param_dict[\"lambda\"]))\n", + ")\n", "\n", "auc_df = pd.DataFrame(\n", - " data=auc_scores_array, \n", - " index=pd.Index(param_dict[\"lr\"], name=\"LR\"), \n", - " columns=pd.Index(param_dict[\"lambda\"], name=\"Lambda\")\n", + " data=auc_scores_array,\n", + " index=pd.Index(param_dict[\"lr\"], name=\"LR\"),\n", + " columns=pd.Index(param_dict[\"lambda\"], name=\"Lambda\"),\n", ")\n", - "auc_df" + "auc_df\n" ] }, { @@ -799,955 +801,7 @@ "outputs": [ { "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute('style', 'box-sizing: content-box;');\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'width: ' + width + 'px; height: ' + height + 'px;'\n", - " );\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - "\n", - " rubberband_canvas.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband_canvas.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " var cursor = msg['cursor'];\n", - " switch (cursor) {\n", - " case 0:\n", - " cursor = 'pointer';\n", - " break;\n", - " case 1:\n", - " cursor = 'default';\n", - " break;\n", - " case 2:\n", - " cursor = 'crosshair';\n", - " break;\n", - " case 3:\n", - " cursor = 'move';\n", - " break;\n", - " }\n", - " fig.rubberband_canvas.style.cursor = cursor;\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " evt.data.type = 'image/png';\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " evt.data\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", - "mpl.findpos = function (e) {\n", - " //this section is from http://www.quirksmode.org/js/events_properties.html\n", - " var targ;\n", - " if (!e) {\n", - " e = window.event;\n", - " }\n", - " if (e.target) {\n", - " targ = e.target;\n", - " } else if (e.srcElement) {\n", - " targ = e.srcElement;\n", - " }\n", - " if (targ.nodeType === 3) {\n", - " // defeat Safari bug\n", - " targ = targ.parentNode;\n", - " }\n", - "\n", - " // pageX,Y are the mouse positions relative to the document\n", - " var boundingRect = targ.getBoundingClientRect();\n", - " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", - " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", - "\n", - " return { x: x, y: y };\n", - "};\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " var canvas_pos = mpl.findpos(event);\n", - "\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " var x = canvas_pos.x * this.ratio;\n", - " var y = canvas_pos.y * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We want\n", - " * to control all of the cursor setting manually through the\n", - " * 'cursor' event from matplotlib */\n", - " event.preventDefault();\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.which === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.which;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.which !== 17) {\n", - " value += 'ctrl+';\n", - " }\n", - " if (event.altKey && event.which !== 18) {\n", - " value += 'alt+';\n", - " }\n", - " if (event.shiftKey && event.which !== 16) {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k';\n", - " value += event.which.toString();\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(msg['content']['data']);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager) {\n", - " manager = IPython.keyboard_manager;\n", - " }\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute('style', 'box-sizing: content-box;');\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n canvas.setAttribute(\n 'style',\n 'width: ' + width + 'px; height: ' + height + 'px;'\n );\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n\n rubberband_canvas.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n rubberband_canvas.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband_canvas.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n rubberband_canvas.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n rubberband_canvas.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n var cursor = msg['cursor'];\n switch (cursor) {\n case 0:\n cursor = 'pointer';\n break;\n case 1:\n cursor = 'default';\n break;\n case 2:\n cursor = 'crosshair';\n break;\n case 3:\n cursor = 'move';\n break;\n }\n fig.rubberband_canvas.style.cursor = cursor;\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n evt.data.type = 'image/png';\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n evt.data\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\n// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\nmpl.findpos = function (e) {\n //this section is from http://www.quirksmode.org/js/events_properties.html\n var targ;\n if (!e) {\n e = window.event;\n }\n if (e.target) {\n targ = e.target;\n } else if (e.srcElement) {\n targ = e.srcElement;\n }\n if (targ.nodeType === 3) {\n // defeat Safari bug\n targ = targ.parentNode;\n }\n\n // pageX,Y are the mouse positions relative to the document\n var boundingRect = targ.getBoundingClientRect();\n var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n\n return { x: x, y: y };\n};\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * http://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n var canvas_pos = mpl.findpos(event);\n\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n var x = canvas_pos.x * this.ratio;\n var y = canvas_pos.y * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n guiEvent: simpleKeys(event),\n });\n\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We want\n * to control all of the cursor setting manually through the\n * 'cursor' event from matplotlib */\n event.preventDefault();\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.which === this._key) {\n return;\n } else {\n this._key = event.which;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.which !== 17) {\n value += 'ctrl+';\n }\n if (event.altKey && event.which !== 18) {\n value += 'alt+';\n }\n if (event.shiftKey && event.which !== 16) {\n value += 'shift+';\n }\n\n value += 'k';\n value += event.which.toString();\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(msg['content']['data']);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager) {\n manager = IPython.keyboard_manager;\n }\n\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n", "text/plain": [ "" ] @@ -1780,7 +834,7 @@ ], "source": [ "fig, ax = plt.subplots()\n", - "sns.heatmap(auc_df, cbar=False, annot=True, fmt=\".4g\")" + "sns.heatmap(auc_df, cbar=False, annot=True, fmt=\".4g\")\n" ] }, { @@ -1806,7 +860,7 @@ "metadata": {}, "outputs": [], "source": [ - "tmpdir.cleanup()" + "tmpdir.cleanup()\n" ] }, { @@ -1855,4 +909,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/02_model_hybrid/lightfm_deep_dive.ipynb b/examples/02_model_hybrid/lightfm_deep_dive.ipynb index 5555ac19e..5ce4b7915 100755 --- a/examples/02_model_hybrid/lightfm_deep_dive.ipynb +++ b/examples/02_model_hybrid/lightfm_deep_dive.ipynb @@ -131,36 +131,36 @@ } ], "source": [ - "import sys\n", "import os\n", - "\n", + "import sys\n", "import itertools\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", - "import scrapbook as sb\n", "\n", "import lightfm\n", "from lightfm import LightFM\n", "from lightfm.data import Dataset\n", "from lightfm import cross_validation\n", - "\n", - "# Import LightFM's evaluation metrics\n", "from lightfm.evaluation import precision_at_k as lightfm_prec_at_k\n", "from lightfm.evaluation import recall_at_k as lightfm_recall_at_k\n", "\n", - "# Import repo's evaluation metrics\n", "from recommenders.evaluation.python_evaluation import precision_at_k, recall_at_k\n", - "\n", "from recommenders.utils.timer import Timer\n", "from recommenders.datasets import movielens\n", "from recommenders.models.lightfm.lightfm_utils import (\n", - " track_model_metrics, prepare_test_df, prepare_all_predictions,\n", - " compare_metric, similar_users, similar_items)\n", + " track_model_metrics,\n", + " prepare_test_df,\n", + " prepare_all_predictions,\n", + " compare_metric,\n", + " similar_users,\n", + " similar_items,\n", + ")\n", + "from recommenders.utils.notebook_utils import store_metadata\n", "\n", "print(\"System version: {}\".format(sys.version))\n", - "print(\"LightFM version: {}\".format(lightfm.__version__))" + "print(\"LightFM version: {}\".format(lightfm.__version__))\n" ] }, { @@ -1412,7 +1412,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -1446,7 +1446,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -1496,7 +1496,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -1508,7 +1508,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -1889,11 +1889,11 @@ "metadata": {}, "outputs": [], "source": [ - "# Record results for tests\n", - "store_metadata('eval_precision', eval_precision)\n", - "store_metadata('eval_recall', eval_recall)\n", - "store_metadata('eval_precision2', eval_precision2)\n", - "store_metadata('eval_recall2', eval_recall2)" + "# Record results for tests - ignore this cell\n", + "store_metadata(\"eval_precision\", eval_precision)\n", + "store_metadata(\"eval_recall\", eval_recall)\n", + "store_metadata(\"eval_precision2\", eval_precision2)\n", + "store_metadata(\"eval_recall2\", eval_recall2)\n" ] }, { @@ -1953,4 +1953,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/03_evaluate/als_movielens_diversity_metrics.ipynb b/examples/03_evaluate/als_movielens_diversity_metrics.ipynb index de82ddd68..517f97afd 100644 --- a/examples/03_evaluate/als_movielens_diversity_metrics.ipynb +++ b/examples/03_evaluate/als_movielens_diversity_metrics.ipynb @@ -156,20 +156,20 @@ } ], "source": [ - "# set the environment path to find Recommenders\n", - "%load_ext autoreload\n", - "%autoreload 2\n", + "import warnings\n", + "warnings.simplefilter(action='ignore', category=FutureWarning)\n", "\n", "import sys\n", + "import numpy as np\n", + "import pandas as pd\n", "\n", "import pyspark\n", - "from pyspark.ml.recommendation import ALS\n", "import pyspark.sql.functions as F\n", + "from pyspark.sql.window import Window\n", "from pyspark.sql.types import FloatType, IntegerType, LongType, StructType, StructField\n", "from pyspark.ml.feature import Tokenizer, StopWordsRemover\n", "from pyspark.ml.feature import HashingTF, CountVectorizer, VectorAssembler\n", - "import warnings\n", - "warnings.simplefilter(action='ignore', category=FutureWarning)\n", + "from pyspark.ml.recommendation import ALS\n", "\n", "from recommenders.utils.timer import Timer\n", "from recommenders.datasets import movielens\n", @@ -177,11 +177,8 @@ "from recommenders.evaluation.spark_evaluation import SparkRankingEvaluation, SparkDiversityEvaluation\n", "from recommenders.utils.spark_utils import start_or_get_spark\n", "\n", - "from pyspark.sql.window import Window\n", - "import pyspark.sql.functions as F\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", + "%load_ext autoreload\n", + "%autoreload 2\n", "\n", "print(\"System version: {}\".format(sys.version))\n", "print(\"Spark version: {}\".format(pyspark.__version__))\n" @@ -354,7 +351,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\r", + "\r\n", " \r" ] } @@ -498,7 +495,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\r", + "\r\n", " \r" ] } @@ -954,7 +951,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\r", + "\r\n", " \r" ] } @@ -1116,4 +1113,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} \ No newline at end of file +} diff --git a/examples/04_model_select_and_optimize/azureml_hyperdrive_surprise_svd.ipynb b/examples/04_model_select_and_optimize/azureml_hyperdrive_surprise_svd.ipynb index 259ea4be4..03d600dc6 100644 --- a/examples/04_model_select_and_optimize/azureml_hyperdrive_surprise_svd.ipynb +++ b/examples/04_model_select_and_optimize/azureml_hyperdrive_surprise_svd.ipynb @@ -54,7 +54,6 @@ "import sys\n", "import os\n", "import surprise\n", - "import papermill as pm\n", "import pandas as pd\n", "import shutil\n", "from tempfile import TemporaryDirectory\n", @@ -783,4 +782,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/04_model_select_and_optimize/azureml_hyperdrive_wide_and_deep.ipynb b/examples/04_model_select_and_optimize/azureml_hyperdrive_wide_and_deep.ipynb index 3dde0284e..e608257a4 100644 --- a/examples/04_model_select_and_optimize/azureml_hyperdrive_wide_and_deep.ipynb +++ b/examples/04_model_select_and_optimize/azureml_hyperdrive_wide_and_deep.ipynb @@ -75,7 +75,6 @@ "\n", "from IPython.display import clear_output\n", "import numpy as np\n", - "import papermill as pm\n", "import pandas as pd\n", "import sklearn.preprocessing\n", "import tensorflow as tf\n", @@ -1198,4 +1197,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/04_model_select_and_optimize/nni_ncf.ipynb b/examples/04_model_select_and_optimize/nni_ncf.ipynb index 500f6656c..17f6a3f7a 100644 --- a/examples/04_model_select_and_optimize/nni_ncf.ipynb +++ b/examples/04_model_select_and_optimize/nni_ncf.ipynb @@ -55,7 +55,6 @@ "import json\n", "import os\n", "import surprise\n", - "import papermill as pm\n", "import pandas as pd\n", "import shutil\n", "import subprocess\n", @@ -700,4 +699,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/04_model_select_and_optimize/nni_surprise_svd.ipynb b/examples/04_model_select_and_optimize/nni_surprise_svd.ipynb index aae442dcd..3f24ba895 100644 --- a/examples/04_model_select_and_optimize/nni_surprise_svd.ipynb +++ b/examples/04_model_select_and_optimize/nni_surprise_svd.ipynb @@ -51,7 +51,6 @@ "import json\n", "import os\n", "import surprise\n", - "import papermill as pm\n", "import pandas as pd\n", "import shutil\n", "import subprocess\n", @@ -1081,4 +1080,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/04_model_select_and_optimize/tuning_spark_als.ipynb b/examples/04_model_select_and_optimize/tuning_spark_als.ipynb index 5f1c19286..2aba7b791 100644 --- a/examples/04_model_select_and_optimize/tuning_spark_als.ipynb +++ b/examples/04_model_select_and_optimize/tuning_spark_als.ipynb @@ -2,22 +2,23 @@ "cells": [ { "cell_type": "markdown", + "metadata": {}, "source": [ "Copyright (c) Recommenders contributors.\n", "\n", "Licensed under the MIT License." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Hyperparameter tuning (Spark based recommender)" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Hyperparameter tuning for Spark based recommender algorithm is important to select a model with the optimal performance. This notebook introduces good practices in performing hyperparameter tuning for building recommender models with the utility functions provided in the [Microsoft/Recommenders](https://github.com/recommenders-team/recommenders.git) repository.\n", "\n", @@ -25,27 +26,40 @@ "* Spark native/custom constructs (`ParamGridBuilder`, `TrainValidationSplit`).\n", "* `hyperopt` package with Tree of Parzen Estimator algorithm. \n", "* Brute-force random search of parameter values sampled with pre-defined space. " - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## 0 Global settings and import" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System version: 3.5.5 |Anaconda custom (64-bit)| (default, May 13 2018, 21:12:35) \n", + "[GCC 7.2.0]\n", + "Pandas version: 0.23.0\n", + "PySpark version: 2.3.1\n" + ] + } + ], "source": [ - "# set the environment path to find Recommenders\n", - "%matplotlib notebook\n", "\n", - "import matplotlib.pyplot as plt\n", "import sys\n", - "import pandas as pd\n", "import numpy as np\n", + "import pandas as pd\n", + "from hyperopt import fmin, tpe, hp, STATUS_OK, Trials\n", + "from hyperopt.pyll.stochastic import sample\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib notebook\n", "\n", "import pyspark\n", "import pyspark.sql.functions as F\n", @@ -59,9 +73,6 @@ "from pyspark.mllib.evaluation import RankingMetrics\n", "from pyspark.sql.types import ArrayType, IntegerType\n", "\n", - "from hyperopt import fmin, tpe, hp, STATUS_OK, Trials\n", - "from hyperopt.pyll.stochastic import sample\n", - "\n", "from recommenders.utils.timer import Timer\n", "from recommenders.utils.spark_utils import start_or_get_spark\n", "from recommenders.evaluation.spark_evaluation import SparkRankingEvaluation, SparkRatingEvaluation\n", @@ -71,24 +82,17 @@ "print(\"System version: {}\".format(sys.version))\n", "print(\"Pandas version: {}\".format(pd.__version__))\n", "print(\"PySpark version: {}\".format(pyspark.__version__))" - ], - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "System version: 3.5.5 |Anaconda custom (64-bit)| (default, May 13 2018, 21:12:35) \n", - "[GCC 7.2.0]\n", - "Pandas version: 0.23.0\n", - "PySpark version: 2.3.1\n" - ] - } - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 2, + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], "source": [ "MOVIELENS_DATA_SIZE = \"100k\"\n", "\n", @@ -118,142 +122,138 @@ "\n", "RANK = [10, 15, 20, 30, 40]\n", "REG = [ 0.1, 0.01, 0.001, 0.0001, 0.00001]" - ], - "outputs": [], - "metadata": { - "tags": [ - "parameters" - ] - } + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## 1 Data preparation" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "A Spark session is created. Note in this case, to study the running time for different approaches, the Spark session in local mode uses only one core for running. This eliminates the impact of parallelization of parameter tuning. " - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 3, + "metadata": {}, + "outputs": [], "source": [ "spark = start_or_get_spark(url=\"local[{}]\".format(NUMBER_CORES))" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "MovieLens 100k dataset is used for running the demonstration." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 4, - "source": [ - "data = load_spark_df(spark, size=MOVIELENS_DATA_SIZE, header=(COL_USER, COL_ITEM, COL_RATING))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "100%|██████████| 4.81k/4.81k [00:01<00:00, 2.47kKB/s]\n" ] } ], - "metadata": {} + "source": [ + "data = load_spark_df(spark, size=MOVIELENS_DATA_SIZE, header=(COL_USER, COL_ITEM, COL_RATING))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "To reduce time spent on the comparitive study, 50% of the data is used for the experimentation below." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 5, + "metadata": {}, + "outputs": [], "source": [ "data, _ = spark_random_split(data, ratio=SUBSET_RATIO)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The dataset is split into 3 subsets randomly with a given split ratio. The hyperparameter tuning is performed on the training and the validating data, and then the optimal recommender selected is evaluated on the testing dataset." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 6, + "metadata": {}, + "outputs": [], "source": [ "train, valid, test = spark_random_split(data, ratio=[3, 1, 1])" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## 2 Hyper parameter tuning with Azure Machine Learning Services" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The `hyperdrive` module in the [Azure Machine Learning Services](https://azure.microsoft.com/en-us/services/machine-learning-service/) runs [hyperparameter tuning and optimizing for machine learning model selection](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-tune-hyperparameters). At the moment, the service supports running hyperparameter tuning on heterogenous computing targets such as cluster of commodity compute nodes with or without GPU devices (see detailed documentation [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets)). It is feasible to run parameter tuning on a cluster of VM nodes. In this case, the service containerizes individual and independent Spark session on each node of the cluster to run the parameter tuning job in parallel, instead of inside a single Spark session where the training is executed in a distributed manner. \n", "\n", "Detailed instructions of tuning hyperparameter of non-Spark workloads by using Azure Machine Learning Services can be found in [this](./hypertune_aml_wide_and_deep_quickstart.ipynb) notebook. " - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## 3 Hyper parameter tuning with Spark ML constructs" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### 3.1 Spark native construct" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Spark ML lib implements modules such as `CrossValidator` and `TrainValidationSplit` for tuning hyperparameters (see [here](https://spark.apache.org/docs/2.2.0/ml-tuning.html)). However, by default, it does not support custom machine learning algorithms, data splitting methods, and evaluation metrics, like what are offered as utility functions in the Recommenders repository. \n", "\n", "For example, the Spark native constuct can be used for tuning a recommender against the `rmse` metric which is one of the available regression metrics in Spark." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Firstly, a Spark ALS object needs to be created. In this case, for illustration purpose, it is an ALS model object." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 7, + "metadata": {}, + "outputs": [], "source": [ "# NOTE the parameters of interest, rank and regParam, are left unset, \n", "# because their values will be assigned in the parameter grid builder.\n", @@ -265,41 +265,41 @@ " nonnegative=False,\n", " **HEADER_ALS\n", ")" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Then, a parameter grid can be defined as follows. Without loss of generity, only `rank` and `regParam` are considered." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 8, + "metadata": {}, + "outputs": [], "source": [ "paramGrid = ParamGridBuilder() \\\n", " .addGrid(als.rank, RANK) \\\n", " .addGrid(als.regParam, REG) \\\n", " .build()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Given the settings above, a `TrainValidationSplit` constructor can be created for fitting the best model in the given parameter range. In this case, the `RegressionEvaluator` is using `RMSE`, by default, as an evaluation metric. \n", "\n", "Since the data splitter is embedded in the `TrainValidationSplit` object, to make sure the splitting ratio is consistent across different approaches, the split ratio is set to be 0.75 and in the model training the training dataset and validating dataset are combined. " - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 9, + "metadata": {}, + "outputs": [], "source": [ "tvs = TrainValidationSplit(\n", " estimator=als,\n", @@ -311,44 +311,36 @@ " # are therefore not available here. \n", " trainRatio=0.75\n", ")" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 10, + "metadata": {}, + "outputs": [], "source": [ "with Timer() as time_spark:\n", " # Run TrainValidationSplit, and choose the best set of parameters.\n", " # NOTE train and valid is union because in Spark TrainValidationSplit does splitting by itself.\n", " model = tvs.fit(train.union(valid))\n", "\n" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The model parameters in the grid and the best metrics can be then returned. " - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 11, - "source": [ - "for idx, item in enumerate(model.getEstimatorParamMaps()):\n", - " print('Run {}:'.format(idx))\n", - " print('\\tValidation Metric: {}'.format(model.validationMetrics[idx]))\n", - " for key, value in item.items():\n", - " print('\\t{0}: {1}'.format(repr(key), value))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Run 0:\n", "\tValidation Metric: 1.0505385750367227\n", @@ -453,17 +445,20 @@ ] } ], - "metadata": {} + "source": [ + "for idx, item in enumerate(model.getEstimatorParamMaps()):\n", + " print('Run {}:'.format(idx))\n", + " print('\\tValidation Metric: {}'.format(model.validationMetrics[idx]))\n", + " for key, value in item.items():\n", + " print('\\t{0}: {1}'.format(repr(key), value))" + ] }, { "cell_type": "code", "execution_count": 12, - "source": [ - "model.validationMetrics" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "[1.0505385750367227,\n", @@ -493,49 +488,54 @@ " 4.426604995574413]" ] }, + "execution_count": 12, "metadata": {}, - "execution_count": 12 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "model.validationMetrics" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "To get the best model, just do" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 13, + "metadata": {}, + "outputs": [], "source": [ "model_best_spark = model.bestModel" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### 3.2 Custom `Estimator`, `Transformer`, and `Evaluator` for Spark ALS\n", "\n", "One can also customize Spark modules to allow tuning hyperparameters for a desired model and evaluation metric, given that the native Spark ALS does not allow tuning hyperparameters for ranking metrics such as precision@k, recall@k, etc. This can be done by creating custom `Estimator`, `Transformer` and `Evaluator`. The benefit is that, after the customization, the tuning process can make use of `trainValidSplit` directly, which distributes the tuning in a Spark session." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "#### Customized `Estimator` and `Transformer` for top k recommender based on Spark ALS\n", "\n", "The following shows how to implement a PySpark `Estimator` and `Transfomer` for recommending top k items from ALS model. The latter generates top k recommendations from the model object. Both of the two are designed by following the protocol of Spark APIs, to make sure that they can be run with the hyperparameter tuning constructs in Spark." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 14, + "metadata": {}, + "outputs": [], "source": [ "class ALSTopK(\n", " ALS,\n", @@ -690,22 +690,22 @@ " )\n", " \n", " return topk_recommendation_all.select(self.userCol, labelCol, predictionCol)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "#### Customized precision@k evaluation metric\n", "\n", "In addition to the custom `Estimator` and `Transformer`, it may also be desired to customize an `Evaluator` to allow \"beyond-rating\" metrics. The codes as following illustrates a precision@k evaluator. Other types of evaluators can be developed in a similar way." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 15, + "metadata": {}, + "outputs": [], "source": [ "# Define a custom Evaulator. Here precision@k is used.\n", "class PrecisionAtKEvaluator(Evaluator):\n", @@ -727,20 +727,20 @@ "\n", " def isLargerBetter(self):\n", " return True" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Then a new ALS top-k recommender can be created, and the Spark native construct, `TrainValidationSplit` module, can be used to find the optimal model w.r.t the precision@k metric." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 16, + "metadata": {}, + "outputs": [], "source": [ "alstopk = ALSTopK(\n", " userCol=COL_USER,\n", @@ -765,23 +765,14 @@ " # are therefore not available here. \n", " trainRatio=0.75\n", ")" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 17, - "source": [ - "# Run TrainValidationSplit, and choose the best set of parameters.\n", - "# NOTE train and valid is union because in Spark TrainValidationSplit does splitting by itself.\n", - "model_precision = tvs.fit(train.union(valid))\n", - "\n", - "model_precision.getEstimatorParamMaps()" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "[{Param(parent='ALSTopK_4f48b7cc6cf2badfcea7', name='rank', doc='rank of the factorization'): 10,\n", @@ -794,15 +785,24 @@ " Param(parent='ALSTopK_4f48b7cc6cf2badfcea7', name='regParam', doc='regularization parameter (>= 0).'): 0.01}]" ] }, + "execution_count": 17, "metadata": {}, - "execution_count": 17 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "# Run TrainValidationSplit, and choose the best set of parameters.\n", + "# NOTE train and valid is union because in Spark TrainValidationSplit does splitting by itself.\n", + "model_precision = tvs.fit(train.union(valid))\n", + "\n", + "model_precision.getEstimatorParamMaps()" + ] }, { "cell_type": "code", "execution_count": 18, + "metadata": {}, + "outputs": [], "source": [ "def best_param(model, is_larger_better=True):\n", " if is_larger_better:\n", @@ -813,35 +813,25 @@ " parameters = model.getEstimatorParamMaps()[model.validationMetrics.index(best_metric)]\n", " \n", " return list(parameters.values())" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 19, + "metadata": {}, + "outputs": [], "source": [ "params = best_param(model_precision)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 20, - "source": [ - "model_precision.bestModel.transform(valid).limit(5).show()\n", - "\n", - "for idx, item in enumerate(model_precision.getEstimatorParamMaps()):\n", - " print('Run {}:'.format(idx))\n", - " print('\\tValidation Metric: {}'.format(model_precision.validationMetrics[idx]))\n", - " for key, value in item.items():\n", - " print('\\t{0}: {1}'.format(repr(key), value))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "+------+--------------------+--------------------+\n", "|userID| label| prediction|\n", @@ -872,29 +862,39 @@ ] } ], - "metadata": {} + "source": [ + "model_precision.bestModel.transform(valid).limit(5).show()\n", + "\n", + "for idx, item in enumerate(model_precision.getEstimatorParamMaps()):\n", + " print('Run {}:'.format(idx))\n", + " print('\\tValidation Metric: {}'.format(model_precision.validationMetrics[idx]))\n", + " for key, value in item.items():\n", + " print('\\t{0}: {1}'.format(repr(key), value))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## 4 Hyperparameter tuning with `hyperopt`" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "`hyperopt` is an open source Python package that is designed for tuning parameters for generic function with any pre-defined loss. More information about `hyperopt` can be found [here](https://github.com/hyperopt/hyperopt). `hyperopt` supports parallelization on MongoDB but not Spark. In our case, the tuning is performed in a sequential mode on a local computer.\n", "\n", "In `hyperopt`, an *objective* function is defined for optimizing the hyper parameters. In this case, the objective is similar to that in the Spark native construct situation, which is *to the RMSE metric for an ALS recommender*. Parameters of `rank` and `regParam` are used as hyperparameters. \n", "\n", "The objective function shown below demonstrates a RMSE loss for an ALS recommender. " - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 21, + "metadata": {}, + "outputs": [], "source": [ "# Customize an objective function\n", "def objective(params):\n", @@ -940,12 +940,11 @@ " 'status': STATUS_OK,\n", " 'eval_time': time_run_start.interval\n", " }" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "A search space is usually defined for hyperparameter exploration. Design of search space is empirical, and depends on the understanding of how distribution of parameter of interest affects the model performance measured by the loss function. \n", "\n", @@ -954,12 +953,13 @@ "* The reg parameter prevents overfitting in certain way. \n", "\n", "Therefore, in this case, a uniform distribution and a lognormal distribution sampling spaces are used for rank and reg, respectively. A narrow search space is used for illustration purpose, that is, the range of rank is from 10 to 20, while that of reg is from $e^{-5}$ to $e^{-1}$. Together with the randomly sampled hyper parameters, other parameters use for building / evaluating the recommender, like `k`, column names, data, etc., are kept as constants." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 22, + "metadata": {}, + "outputs": [], "source": [ "# define a search space\n", "space = {\n", @@ -974,31 +974,31 @@ " 'k': 10,\n", " 'relevancy_method': \"top_k\"\n", "}" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### 4.1 Hyperparameter tuning with TPE" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "`fmin` of `hyperopt` is used for running the trials for searching optimal hyper parameters. In `hyperopt`, there are different strategies for intelligently optimize hyper parameters. For example, `hyperopt` avails [Tree of Parzen Estimators (TPE) method](https://papers.nips.cc/paper/4443-algorithms-for-hyper-parameter-optimization.pdf) for searching optimal parameters. \n", "\n", "The TPE method models a surface response of $p(x|y)$ by transforming a generative process, replacing the distributions of the configuration prior with non-parametric densities, where $p$ is the probability of configuration space $x$ given the loss $y$. For different configuration space, the TPE method does different replacements. That is, uniform $\\to$ truncated Gaussian mixture, log-uniform $\\to$ exponentiated truncated Gaussian mixture, categorical $\\to$ re-weighted categorical, etc. Using different observations ${x(1), ..., x(k)}$ in the non-parametric densities, these substitutions represent a learning algorithm that can produce a variety of densities over the configuration space $X$. By maintaining sorted lists of observed variables in $H$, the runtime of each iteration of the TPE algorithm can scale linearly in $|H|$ and linearly in the number of variables (dimensions) being optimized. In a nutshell, the algorithm recognizes the irrelevant variables in the configuration space, and thus reduces iterations in searching for the optimal ones. Details of the TPE algorithm can be found in the reference paper.\n", "\n", "The following runs the trials with the pre-defined objective function and search space. TPE is used as the optimization method. Totally there will be 10 evaluations run for searching the best parameters." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 23, + "metadata": {}, + "outputs": [], "source": [ "with Timer() as time_hyperopt:\n", " # Trials for recording each iteration of the hyperparameter searching.\n", @@ -1012,19 +1012,14 @@ " max_evals=NUMBER_ITERATIONS\n", " )\n", " \n" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 24, - "source": [ - "trials.best_trial" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "{'book_time': datetime.datetime(2019, 7, 17, 12, 28, 19, 108000),\n", @@ -1045,41 +1040,31 @@ " 'version': 0}" ] }, + "execution_count": 24, "metadata": {}, - "execution_count": 24 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "trials.best_trial" + ] }, { "cell_type": "code", "execution_count": 25, - "source": [ - "parameters = ['rank', 'reg']\n", - "cols = len(parameters)\n", - "f, axes = plt.subplots(nrows=1, ncols=cols, figsize=(15,5))\n", - "cmap = plt.cm.jet\n", - "for i, val in enumerate(parameters):\n", - " xs = np.array([t['misc']['vals'][val] for t in trials.trials]).ravel()\n", - " ys = [t['result']['loss'] for t in trials.trials]\n", - " xs, ys = zip(*sorted(zip(xs, ys)))\n", - " ys = np.array(ys)\n", - " axes[i].scatter(xs, ys, s=20, linewidth=0.01, alpha=0.75, c=cmap(float(i)/len(parameters)))\n", - " axes[i].set_title(val)" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n fig.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('