{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Reduce inference time by distilling your model\n", "\n", "In this tutorial, we will show a method to improve the inference time (the time it takes to predict new data) for an already trained model. We will achieve this via model distillation.\n", "\n", "In distillation, an existing, bigger model (the teacher model) is used to train a new, smaller model (the student model). The student model generally achieves better quality results using this distillation technique than can be achieved by simply training the student model on the available dataset. In some sense, the teacher model uses its higher understanding of language in general to guide the student on how to solve the task.\n", "\n", "Distillation is usually done with the help of a transfer dataset in addition to the normal dataset used for training. The transfer dataset does not have to be labeled, but should ideally be as close to the target domain as possible.\n", "For example: When the goal is to distill a model that is used to classify customer reviews of mobile phones from an online shop, using reviews instead of general English text is better. Even better are reviews of mobile phones and ideally the transfer dataset should be reviews of mobile phones from the specific online shop the model will be used for (if enough data is available).\n", "Transfer datasets with a size of multiple hundred megabytes are common.\n", "\n", "Be aware that distillation can take many hours, and up to multiple days, depending on the size of the transfer datasets, the teacher, and the student model. Therefore, this method is more suited for models which will be put into day to day production use, where a lot of data has to be processed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Teachers and Students\n", "\n", "As a general rule of thumb, the teacher should be the best and biggest model one can train, and the student should be the smallest model which still gives acceptable results. Even though the student will perform better on the task than if it were simply trained directly, some degradation in prediction quality is usually unavoidable.\n", "\n", "For this tutorial, we will use the previously trained `bert-base-uncased` model, which was trained for the second tutorial (\"Train a model to label reviews from the GooglePlay store\"), as a teacher. Generally, a bigger model than that would be used as a teacher (e.g. a `roberta-base` or `roberta-large`) but since we already trained this teacher in a previous tutorial, we will re-use it.\n", "\n", "As a student, we will use an `albert-base-v2#cnn` model. CNN models were introduced specifically for distillation purposes and are much faster (depending on the used hardware and the dataset, a speedup of 20x). The `albert-base-v2` part before the `#cnn` specifies that the token embeddings of `albert-base-v2` should be used for the CNN model." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "%load_ext tensorboard\n", "\n", "!pip install gdown -q\n", "!pip install pandas -q\n", "!pip install google-play-scraper -q\n", "\n", "import autonlu\n", "from autonlu import Model\n", "import pandas as pd\n", "import numpy as np\n", "import gdown\n", "import pickle\n", "from http.client import RemoteDisconnected\n", "from time import sleep\n", "\n", "from google_play_scraper import reviews\n", "from tqdm.auto import tqdm" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "User name/Email: admin\n", "Password: ········\n" ] } ], "source": [ "autonlu.login()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will continue with the model from tutorial 02 (training a label task) as a teacher. So you will have to have run this tutorial to have the model available for loading." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model googleplay_labeling loaded from local path successfully.\n" ] } ], "source": [ "teacher = Model(\"googleplay_labeling\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Getting labeled data" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Downloading...\n", "From: https://drive.google.com/uc?id=1S6qMioqPJjyBLpLVz4gmRTnJHnjitnuV\n", "To: /home/paethon/git/autonlu/tutorials/.cache/data/googleplay/apps.csv\n", "100%|██████████| 134k/134k [00:00<00:00, 2.61MB/s]\n", "Downloading...\n", "From: https://drive.google.com/uc?id=1zdmewp7ayS4js4VtrJEHzAheSW-5NBZv\n", "To: /home/paethon/git/autonlu/tutorials/.cache/data/googleplay/reviews.csv\n", "7.17MB [00:00, 22.5MB/s]\n" ] } ], "source": [ "gdown.download(\"https://drive.google.com/uc?id=1S6qMioqPJjyBLpLVz4gmRTnJHnjitnuV\", \".cache/data/googleplay/\")\n", "gdown.download(\"https://drive.google.com/uc?id=1zdmewp7ayS4js4VtrJEHzAheSW-5NBZv\", \".cache/data/googleplay/\")\n", "\n", "\n", "df = pd.read_csv(\".cache/data/googleplay/reviews.csv\")\n", "\n", "def to_label(score):\n", " return \"negative\" if score <= 2 else \\\n", " \"neutral\" if score == 3 else \"positive\"\n", "\n", "X = [x for x in df.content]\n", "Y = [to_label(score) for score in df.score]\n", "\n", "#X, Y, valX, valY = autonlu.split_dataset(X, Y, split_at=0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Getting unlabeled data\n", "\n", "Let's start to download unlabeled data that can be used as a transfer dataset. This is rather slow, so for demonstration purposes, the transfer dataset will be quite small. In practice, much larger transfer datasets should generally be used if possible." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "apps=[\n", " \"com.instagram.android\", \n", " \"com.facebook.katana\",\n", " \"com.whatsapp\",\n", " \"com.king.candycrush4\",\n", " \"com.android.chrome\",\n", " \"com.google.android.apps.wallpaper\",\n", " \"com.linkedin.android\",\n", " \"com.twitter.android\",\n", " \"com.wolfram.android.alpha\",\n", " \"com.microsoft.math\",\n", " \"com.spotify.music\",\n", " \"com.android.chrome\",\n", " 'com.chrome.beta',\n", " 'org.mozilla.firefox',\n", " 'com.opera.browser',\n", " 'org.adblockplus.browser',\n", " 'tower.defense.game',\n", " ]\n", "\n", "transfer_dataset = []\n", "\n", "for app in tqdm(apps):\n", " try:\n", " result, continuation_token = reviews(app, count=50000, lang=\"en\")\n", " except RemoteDisconnected:\n", " print(\"Remote Disconnected. Sleeping and continuing to next app\")\n", " sleep(10)\n", " for r in result:\n", " if r[\"content\"] is not None and len(r[\"content\"]) > 30:\n", " transfer_dataset.append(r[\"content\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To be able to follow the training, we will start a tensorboard instance" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%tensorboard --logdir tensorboard_logs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Distill into a CNN model with albert-base-v2 token embeddings\n", "\n", "One note: Depending on the used transfer dataset, it might happen that the model accuracy actually goes down when this transfer dataset is used. This usually means that it is not close enough to the training dataset to be of much use. Depending on the downloaded reviews, this might be the case in this tutorial and is exacerbated by having a rather small transfer dataset. This does not negatively impact the end-state of the distilled model, it just means that in this case the transfer dataset is not further improving the model and could also be left out to speed up training. Ideally, a better transfer dataset should be used." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model albert-base-v2 loaded from Huggingface successfully.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/home/paethon/git/py39env/lib/python3.9/site-packages/torch/cuda/memory.py:373: FutureWarning: torch.cuda.memory_cached has been renamed to torch.cuda.memory_reserved\n", " warnings.warn(\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "620a1e71530e48438a3915ad269c077a", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(FloatProgress(value=0.0, description='Processing Chunks', max=177.0, style=ProgressStyle(descri…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "633af506308f4d098678397251465621", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(FloatProgress(value=0.0, description='Processing Chunks', max=177.0, style=ProgressStyle(descri…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "student = teacher.distill(\"albert-base-v2#cnn\", X=X, Y=Y, unlabelledX=transfer_dataset)\n", "student.save(\"googleplay_labeling_distill_cnn\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test the resulting model\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "During training, one can generally achieve ~86% accuracy with the new CNN model." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['positive', 'neutral', 'negative']\n" ] } ], "source": [ "ret = student.predict([\n", " \"This app is really cool and helpful.\", \n", " \"The app is quite ok, some things could be improved.\", \n", " \"The app does not work at all.\"])\n", "print(ret)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "556 ms ± 4.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%timeit student.predict(X)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "7.18 s ± 27.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%timeit teacher.predict(X)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, we created an almost **13x faster model** with an accuracy of **~86%** instead of **87.62%** of the original teacher model!" ] } ], "metadata": { "@webio": { "lastCommId": null, "lastKernelId": null }, "kernelspec": { "display_name": "Python 3", "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.5" } }, "nbformat": 4, "nbformat_minor": 2 }