{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Marvel Character Probability Model: Gradio Inference Demo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The objective is simple: Thwart adverserial attacks from DC fans who may want to abuse the wonderful [Marvel Character classifier](https://notebookse.jarvislabs.ai/jY5fsv-S9jKoQQrgd1dsoJuCDt6pTg6ZjBpNK9afxLIGInQv4OlHVuTMHqOPh2LU/) in hopes of having DC characters classified as part of the Marvel universe (the unspoken obession of every DC fan).\n", "\n", "**Gradio** allows us to create a web application for our ML model that can be used directly or embedded in another application (e.g., Hugging Face Spaces).\n", "\n", "Two resources were fundamental is helping me figure out how to make this critical model available to the world via Gradio and Hugging Face Spaces. The are:\n", "\n", "1. [\"Gradio + HuggingFace Spaces: A Tutorial\"](https://tmabraham.github.io/blog/gradio_hf_spaces_tutorial) by Tanishq Abraham\n", "2. [\"Food Image Classifier\"](https://huggingface.co/spaces/suvash/food-101-resnet50) by Suvash\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/wgilliam/miniconda3/envs/fastexamples/lib/python3.9/site-packages/paramiko/transport.py:236: CryptographyDeprecationWarning: Blowfish has been deprecated\n", " \"class\": algorithms.Blowfish,\n" ] } ], "source": [ "from fastai.vision.all import *\n", "from fastcore.all import *\n", "import gradio as gr\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup/Configuration" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "data_path = Path(\"../data\")\n", "models_path = Path(\"../models\")\n", "examples_path = Path(\"./examples\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Utilities" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "def is_marvel(img):\n", " return 1.0 if img.parent.name.lower().startswith(\"marvel\") else 0.0\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Inference" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We start by loading our exported learner from the [training notebook](train.ipynb) via `load_learner`. \n", "\n", "`load_learner` returns a `Learner` that knows all about our data transformations and training bits, allowing us to use it for item or batch inference without any additional code." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "inf_learn = load_learner(models_path / \"export.pkl\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll modify our `predict` method here so that it returns the probability of the image being a Marvel character as a string" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [], "source": [ "def predict(img):\n", " pred, _, _ = inf_learn.predict(img)\n", " return f\"{pred[0]*100:.2f}%\"\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... and we'll test things to ensure our predictions look good" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Marvel character probability: 41.55%\n" ] }, { "data": { "image/png": "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", "text/plain": [ "" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "test_img = PILImage.create(data_path / \"_marvel_example.jpg\")\n", "res = predict(test_img)\n", "\n", "print(f\"Marvel character probability: {res}\")\n", "test_img.to_thumb(256, 256)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Gradio\n", "\n", "See Tanishq's article [\"Gradio + HuggingFace Spaces: A Tutorial\"](https://tmabraham.github.io/blog/gradio_hf_spaces_tutorial) for more detail on how Gradio can be configured to demo just about any ML model imaginable. I present here a bare minimum explanation of how it works and of the settings used in this particular demo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll start by including a markdown file with information about our demo (e.g., objective, dataset, training procedure, results, etc...). This information will appear at the bottom of your gradio demo (assed to the `article` parameter of `gradio.Interface()`)" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "with open(\"../gradio_article.md\") as f:\n", " article = f.read()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The UI is ***defined*** via a call to `gradio.Interface()`. \n", "\n", "Here's a description of the parameters used in this demo:\n", "\n", "- `title` (str): The title of your demo (appears at the top)\n", "- `description` (str): The description of your demo (appears beneath the title and is markup/HTML friendly)\n", "- `article` (markdown file): Markdown with explanatory information about your demo (appears at the bottom)\n", "- `examples` (str/list): Location of pre-defined examples users can use in your demo\n", "- `interpretation` (callabel/str): A function that returns an interpretation for the prediction (options: \"unalighed\", \"horizontal\", \"vertical\")\n", "- `layout` (str): You can specify either \"horizontal\" or \"vertical\"\n", "- `allow_flagging` (str): Controls if/how users can flag predictions (options: \"never\", \"auto\", \"manual\") \n", "\n", "See the [docs](https://gradio.app/docs/#interface) for more info.\n", "\n", "Given the below, we can see/use our demo straight from out notebook!" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "interface_config = {\n", " \"title\": \"Is it a Marvel Character?\",\n", " \"description\": \"For those wanting to make sure they are rooting on the right heroes. Based on Jeremy Howards ['Is it a bird? Creating a model from your own data'](https://www.kaggle.com/code/jhoward/is-it-a-bird-creating-a-model-from-your-own-data)\",\n", " \"article\": article,\n", " \"examples\": [f\"{examples_path}/{f.name}\" for f in examples_path.iterdir()],\n", " \"interpretation\": None,\n", " \"layout\": \"horizontal\",\n", " \"allow_flagging\": \"never\",\n", "}\n", "\n", "demo = gr.Interface(\n", " fn=predict,\n", " inputs=gr.inputs.Image(shape=(512, 512)),\n", " outputs=gr.outputs.Textbox(label=\"Marvel character probability\"),\n", " **interface_config,\n", ")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The UI is ***launched*** via a call to `gradio.Interface` instance's `launch()` method. \n", "\n", "Here's a description of the parameters used in this demo:\n", "\n", "- `inline` (bool): If `True`, will display the interface in your Juypter notebook\n", "- `inbrowser` (bool): If `True`, will launch the demo in a new browser tab\n", "- `share` (bool): If `True`, will create a shareable link you can use to access your demo on the web\n", "- `show_error` (bool): If `True`, errors in the interface will be included in the browser's console log\n", "- `enable_queue` (bool): Controls how requests are processed (**Note: Set to `True` for request that will take a long time**)\n", "\n", "See the [docs](https://gradio.app/docs/#launch) for more info." ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running on local URL: http://127.0.0.1:7863/\n", "Running on public URL: https://55030.gradio.app\n", "\n", "This share link expires in 72 hours. For free permanent hosting, check out Spaces (https://huggingface.co/spaces)\n" ] }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "(,\n", " 'http://127.0.0.1:7863/',\n", " 'https://55030.gradio.app')" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "demo_config = {\n", " \"inline\": True,\n", " \"inbrowser\": False,\n", " \"share\": True,\n", " \"show_error\": True,\n", " \"enable_queue\": True,\n", "}\n", "\n", "demo.launch(**demo_config)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once we got things working, we need to put all the above into an `app.py` file like so:\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Deploy\n", "\n", "We'll be deploying our Gradio demo to [HuggingFace Spaces](https://huggingface.co/spaces), which Tanishq desrbies like this:\n", "\n", "> ... a free-to-use platform for hosting machine learning demos and apps [providing] a CPU environment with 16 GB RAM and 8 cores [and support for both] Gradio and Streamlit platforms.\n", "\n", "Here's how its done ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create your \"Space\"\n", "\n", "We start by creating a new space on the aptly named [\"Create a new Space\"](https://huggingface.co/new-space) page.\n", "\n", "I'll be using my favorite \"License\", wtfpl and setting this up for use on the fastai organization hosted on Hugging Face. I'm naming the space so folks will know it derives from lessons learned in the first session of the 2022 fastai course.\n", "\n", "![](hf_space_create.png)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set up your git repo\n", "\n", "After creating your space, you'll be offered up some instructions to configure the git repo managed by HF. Here's the approach I found easiest:\n", "\n", "1. Locally go a `git clone https://huggingface.co/spaces/{your_username}/{your_space_name}`\n", "\n", "2. `cd` into your `{your_space_name}` directory and copy/move all your example(s), models, etc... into it\n", "\n", "3. Install `git lfs` on your system (on my Ubuntu 16.04 box it was as simple as `sudo apt-get install git-lfs`). See [the docs](https://git-lfs.github.com/) for more details.\n", "\n", "4. Configure `git lfs` for your repo by running `git lfs install` and then `git lfs track \"*.pkl\"` to ensure it is handling the BIG files (in this case our `export.pkl`)\n", "\n", "5. You may want to update your `.gitignore` to remove training data, etc... that *should not* be included in the repo.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define your `app.py`\n", "\n", "Spaces are organized as a git repo that contains an `app.py` file with all your inference and interface configuration information. This contents of this file look like this:\n", "\n", "```python\n", "from fastai.vision.all import *\n", "from fastcore.all import *\n", "import gradio as gr\n", "\n", "data_path = Path(\"./data\")\n", "models_path = Path(\"./models\")\n", "examples_path = Path(\"./examples\")\n", "\n", "\n", "def is_marvel(img):\n", " return 1.0 if img.parent.name.lower().startswith(\"marvel\") else 0.0\n", "\n", "\n", "inf_learn = load_learner(models_path / \"export.pkl\")\n", "\n", "\n", "def predict(img):\n", " pred, _, _ = inf_learn.predict(img)\n", " return f\"{pred[0]*100:.2f}%\"\n", "\n", "\n", "with open(\"gradio_article.md\") as f:\n", " article = f.read()\n", "\n", "interface_config = {\n", " \"title\": \"Is it a Marvel Character?\",\n", " \"description\": \"For those wanting to make sure they are rooting on the right heroes. Based on Jeremy Howards ['Is it a bird? Creating a model from your own data'](https://www.kaggle.com/code/jhoward/is-it-a-bird-creating-a-model-from-your-own-data)\",\n", " \"article\": article,\n", " \"examples\": [f\"{examples_path}/{f.name}\" for f in examples_path.iterdir()],\n", " \"interpretation\": None,\n", " \"layout\": \"horizontal\",\n", " \"allow_flagging\": \"never\",\n", "}\n", "\n", "demo = gr.Interface(\n", " fn=predict,\n", " inputs=gr.inputs.Image(shape=(512, 512)),\n", " outputs=gr.outputs.Textbox(label=\"Marvel character probability\"),\n", " **interface_config,\n", ")\n", "\n", "demo_config = {\n", " \"inline\": True,\n", " \"inbrowser\": False,\n", " \"share\": True,\n", " \"show_error\": True,\n", " \"enable_queue\": True,\n", "}\n", "\n", "demo.launch()\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define a `requirements.txt`\n", "\n", "We'll also include a `requirements.txt` file with the libraries required for our demo like so:\n", "\n", "```\n", "fastai\n", "gradio\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Commit and Push\n", "\n", "Simply ...\n", "\n", "```\n", "git add .\n", "git commit -am 'initial commit'\n", "git push\n", "```\n", "\n", "... all your code will be properly pushed to your HF Space's repo and your application will be spun up for you. And voila, you now have a ***free*** web application you can use to show off your machine learning prowess just like this one!\n", "\n", "![](hf_space_app.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] } ], "metadata": { "interpreter": { "hash": "8d7a979f659379f6312caf3993ac2ab4b165620bf8bd7cd5a3069321dc3c91fd" }, "kernelspec": { "display_name": "Python 3.9.12 ('fastexamples')", "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.9.12" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }