{ "cells": [ { "cell_type": "raw", "id": "57603ef6", "metadata": {}, "source": [ "Run in Google Colab" ] }, { "cell_type": "markdown", "id": "60626155", "metadata": {}, "source": [ "# SciKeras Benchmarks\n", "\n", "SciKeras wraps Keras Models, but does not alter their performance since all of the heavy lifting still happens within Keras/Tensorflow. In this notebook, we compare the performance and accuracy of a pure-Keras Model to the same model wrapped in SciKeras.\n", "\n", "## Table of contents\n", "\n", "* [1. Setup](#1.-Setup)\n", "* [2. Dataset](#2.-Dataset)\n", "* [3. Define Keras Model](#3.-Define-Keras-Model)\n", "* [4. Keras benchmarks](#4.-Keras-benchmarks)\n", "* [5. SciKeras benchmark](#5.-SciKeras-benchmark)\n", "\n", "## 1. Setup" ] }, { "cell_type": "code", "execution_count": 1, "id": "36d192af", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:02.026050Z", "iopub.status.busy": "2024-04-11T22:21:02.025288Z", "iopub.status.idle": "2024-04-11T22:21:10.362562Z", "shell.execute_reply": "2024-04-11T22:21:10.361685Z" } }, "outputs": [], "source": [ "try:\n", " import scikeras\n", "except ImportError:\n", " !python -m pip install scikeras[tensorflow]" ] }, { "cell_type": "markdown", "id": "1a90a93e", "metadata": {}, "source": [ "Silence TensorFlow logging to keep output succinct." ] }, { "cell_type": "code", "execution_count": 2, "id": "c44c9ff7", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:10.366592Z", "iopub.status.busy": "2024-04-11T22:21:10.366075Z", "iopub.status.idle": "2024-04-11T22:21:10.370563Z", "shell.execute_reply": "2024-04-11T22:21:10.370012Z" } }, "outputs": [], "source": [ "import warnings\n", "from tensorflow import get_logger\n", "get_logger().setLevel('ERROR')\n", "warnings.filterwarnings(\"ignore\", message=\"Setting the random state for TF\")" ] }, { "cell_type": "code", "execution_count": 3, "id": "158b696c", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:10.373652Z", "iopub.status.busy": "2024-04-11T22:21:10.373305Z", "iopub.status.idle": "2024-04-11T22:21:11.899814Z", "shell.execute_reply": "2024-04-11T22:21:11.899078Z" } }, "outputs": [], "source": [ "import numpy as np\n", "from scikeras.wrappers import KerasClassifier, KerasRegressor\n", "import keras" ] }, { "cell_type": "markdown", "id": "1746ef22", "metadata": {}, "source": [ "## 2. Dataset\n", "\n", "We will be using the MNIST dataset available within Keras." ] }, { "cell_type": "code", "execution_count": 4, "id": "76570d83", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:11.903966Z", "iopub.status.busy": "2024-04-11T22:21:11.903218Z", "iopub.status.idle": "2024-04-11T22:21:12.456858Z", "shell.execute_reply": "2024-04-11T22:21:12.456122Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "\u001b[1m 0/11490434\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m0s\u001b[0m 0s/step" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "\u001b[1m 2531328/11490434\u001b[0m \u001b[32m━━━━\u001b[0m\u001b[37m━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m0s\u001b[0m 0us/step" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "\u001b[1m11490434/11490434\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 0us/step\n" ] } ], "source": [ "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n", "# Scale images to the [0, 1] range\n", "x_train = x_train.astype(\"float32\") / 255\n", "x_test = x_test.astype(\"float32\") / 255\n", "# Make sure images have shape (28, 28, 1)\n", "x_train = np.expand_dims(x_train, -1)\n", "x_test = np.expand_dims(x_test, -1)\n", "# Reduce dataset size for faster benchmarks\n", "x_train, y_train = x_train[:2000], y_train[:2000]\n", "x_test, y_test = x_test[:500], y_test[:500]" ] }, { "cell_type": "markdown", "id": "2dde1aeb", "metadata": {}, "source": [ "## 3. Define Keras Model\n", "\n", "Next we will define our Keras model (adapted from [keras.io](https://keras.io/examples/vision/mnist_convnet/)):" ] }, { "cell_type": "code", "execution_count": 5, "id": "34683a7d", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:12.460184Z", "iopub.status.busy": "2024-04-11T22:21:12.459654Z", "iopub.status.idle": "2024-04-11T22:21:12.466210Z", "shell.execute_reply": "2024-04-11T22:21:12.465592Z" } }, "outputs": [], "source": [ "num_classes = 10\n", "input_shape = (28, 28, 1)\n", "\n", "\n", "def get_model():\n", " model = keras.Sequential(\n", " [\n", " keras.Input(input_shape),\n", " keras.layers.Conv2D(32, kernel_size=(3, 3), activation=\"relu\"),\n", " keras.layers.MaxPooling2D(pool_size=(2, 2)),\n", " keras.layers.Conv2D(64, kernel_size=(3, 3), activation=\"relu\"),\n", " keras.layers.MaxPooling2D(pool_size=(2, 2)),\n", " keras.layers.Flatten(),\n", " keras.layers.Dropout(0.5),\n", " keras.layers.Dense(num_classes, activation=\"softmax\"),\n", " ]\n", " )\n", " model.compile(\n", " loss=\"sparse_categorical_crossentropy\", optimizer=\"adam\"\n", " )\n", " return model" ] }, { "cell_type": "markdown", "id": "9a6ae8d0", "metadata": {}, "source": [ "## 4. Keras benchmarks" ] }, { "cell_type": "code", "execution_count": 6, "id": "7e63cfa9", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:12.468940Z", "iopub.status.busy": "2024-04-11T22:21:12.468468Z", "iopub.status.idle": "2024-04-11T22:21:12.472041Z", "shell.execute_reply": "2024-04-11T22:21:12.471395Z" } }, "outputs": [], "source": [ "fit_kwargs = {\"batch_size\": 128, \"validation_split\": 0.1, \"verbose\": 0, \"epochs\": 5}" ] }, { "cell_type": "code", "execution_count": 7, "id": "cfeb66d1", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:12.474460Z", "iopub.status.busy": "2024-04-11T22:21:12.474176Z", "iopub.status.idle": "2024-04-11T22:21:12.476977Z", "shell.execute_reply": "2024-04-11T22:21:12.476427Z" } }, "outputs": [], "source": [ "from sklearn.metrics import accuracy_score\n", "from scikeras.utils.random_state import tensorflow_random_state" ] }, { "cell_type": "code", "execution_count": 8, "id": "ed4166d2", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:12.480751Z", "iopub.status.busy": "2024-04-11T22:21:12.480157Z", "iopub.status.idle": "2024-04-11T22:21:17.618750Z", "shell.execute_reply": "2024-04-11T22:21:17.618072Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training time: 4.83\n", "\r", "\u001b[1m 1/16\u001b[0m \u001b[32m━\u001b[0m\u001b[37m━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m1s\u001b[0m 74ms/step" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "\u001b[1m15/16\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m━━\u001b[0m \u001b[1m0s\u001b[0m 4ms/step " ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "\u001b[1m16/16\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 7ms/step\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Accuracy: 0.89\n" ] } ], "source": [ "from time import time\n", "\n", "with tensorflow_random_state(seed=0): # we force a TF random state to be able to compare accuracy\n", " model = get_model()\n", " start = time()\n", " model.fit(x_train, y_train, **fit_kwargs)\n", " print(f\"Training time: {time()-start:.2f}\")\n", " y_pred = np.argmax(model.predict(x_test), axis=1)\n", "print(f\"Accuracy: {accuracy_score(y_test, y_pred)}\")" ] }, { "cell_type": "markdown", "id": "a6ddfcbb", "metadata": {}, "source": [ "## 5. SciKeras benchmark" ] }, { "cell_type": "code", "execution_count": 9, "id": "715b72e5", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:17.621524Z", "iopub.status.busy": "2024-04-11T22:21:17.621090Z", "iopub.status.idle": "2024-04-11T22:21:17.629330Z", "shell.execute_reply": "2024-04-11T22:21:17.624188Z" } }, "outputs": [], "source": [ "clf = KerasClassifier(\n", " model=get_model,\n", " random_state=0,\n", " **fit_kwargs\n", ")" ] }, { "cell_type": "code", "execution_count": 10, "id": "4c11437d", "metadata": { "execution": { "iopub.execute_input": "2024-04-11T22:21:17.631731Z", "iopub.status.busy": "2024-04-11T22:21:17.631474Z", "iopub.status.idle": "2024-04-11T22:21:23.628001Z", "shell.execute_reply": "2024-04-11T22:21:23.626291Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training time: 5.74\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Accuracy: 0.89\n" ] } ], "source": [ "start = time()\n", "clf.fit(x_train, y_train)\n", "print(f\"Training time: {time()-start:.2f}\")\n", "y_pred = clf.predict(x_test)\n", "print(f\"Accuracy: {accuracy_score(y_test, y_pred)}\")" ] }, { "cell_type": "markdown", "id": "f38395b8", "metadata": {}, "source": [ "As you can see, the overhead for SciKeras is <1 sec, and the accuracy is identical." ] } ], "metadata": { "jupytext": { "formats": "ipynb,md" }, "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.12.2" } }, "nbformat": 4, "nbformat_minor": 5 }