diff --git a/README.md b/README.md index f002986..d74d67e 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,11 @@ own GPUs. ## Quickstart -Run a server on your laptop (CPU works; it uses the GPU if there is one): +[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/VectorInstitute/tabctx/blob/main/examples/quickstart.ipynb) + +Run the whole quickstart in your browser with the +[notebook](examples/quickstart.ipynb), or on your laptop (CPU works; it +uses the GPU if there is one): ```bash pip install "tabctx[standalone,tabicl,client]" diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index f619dc5..45cbcfa 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,8 +1,12 @@ # Quickstart +[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/VectorInstitute/tabctx/blob/main/examples/quickstart.ipynb) + Run a tabctx server on your laptop, fit a real dataset and predict against it. A CPU is enough; the server uses a GPU if it finds one. -tabctx needs Python 3.12 or newer. +tabctx needs Python 3.12 or newer. Prefer a notebook? The +[quickstart notebook](https://github.com/VectorInstitute/tabctx/blob/main/examples/quickstart.ipynb) +runs this whole page end to end. ## 1. Install and start a server diff --git a/examples/quickstart.ipynb b/examples/quickstart.ipynb new file mode 100644 index 0000000..d0b4e50 --- /dev/null +++ b/examples/quickstart.ipynb @@ -0,0 +1,457 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cfe05809", + "metadata": {}, + "source": [ + "# tabctx quickstart\n", + "\n", + "Fit a table once, keep it as a cached *context* on a server, and predict against it with a scikit-learn-style API. This notebook runs the whole flow end to end: install, start a server (CPU is fine), classify, regress with prediction quantiles, and manage contexts." + ] + }, + { + "cell_type": "markdown", + "id": "4eb6943b", + "metadata": {}, + "source": [ + "## 1. Install" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ac7b3069", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:32:10.461627Z", + "iopub.status.busy": "2026-11-11T22:32:10.461448Z", + "iopub.status.idle": "2026-11-11T22:32:14.363213Z", + "shell.execute_reply": "2026-11-11T22:32:14.362009Z" + } + }, + "outputs": [], + "source": [ + "!pip install -q \"tabctx[standalone,tabicl,client]\"" + ] + }, + { + "cell_type": "markdown", + "id": "409f9924", + "metadata": {}, + "source": [ + "## 2. Start a server\n", + "\n", + "The server hosts the TabICL model. The first start downloads the TabICLv2 weights (BSD-3, no account needed). We start it in the background and wait until it answers." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "069dd645", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:32:14.366317Z", + "iopub.status.busy": "2026-11-11T22:32:14.366064Z", + "iopub.status.idle": "2026-11-11T22:33:44.642958Z", + "shell.execute_reply": "2026-11-11T22:33:44.642469Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'data': [{'id': 'tabicl-v2', 'object': 'model', 'default': True}]}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import json\n", + "import os\n", + "import subprocess\n", + "import sys\n", + "import time\n", + "import urllib.request\n", + "\n", + "env = {**os.environ, \"TABCTX_BACKEND\": \"tabicl\"}\n", + "server = subprocess.Popen(\n", + " [sys.executable, \"-m\", \"tabctx.serve\"],\n", + " env=env,\n", + " stdout=subprocess.DEVNULL,\n", + " stderr=subprocess.DEVNULL,\n", + ")\n", + "\n", + "models = None\n", + "for _ in range(180):\n", + " try:\n", + " with urllib.request.urlopen(\"http://127.0.0.1:8000/v1/models\", timeout=2) as resp:\n", + " models = json.load(resp)\n", + " break\n", + " except Exception:\n", + " time.sleep(5)\n", + "\n", + "assert models is not None, \"server did not come up\"\n", + "models" + ] + }, + { + "cell_type": "markdown", + "id": "2b5d87c9", + "metadata": {}, + "source": [ + "## 3. Fit once, predict many (classification)\n", + "\n", + "`fit` sends the table to the server, which encodes it into a cached context. Every later `predict` / `predict_proba` reuses that context." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f2c406ce", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:33:44.728917Z", + "iopub.status.busy": "2026-11-11T22:33:44.728571Z", + "iopub.status.idle": "2026-11-11T22:34:44.680054Z", + "shell.execute_reply": "2026-11-11T22:34:44.678987Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "context id: sklearn-93906881b2b76a20dd1a638b8bf37862\n" + ] + } + ], + "source": [ + "from sklearn.datasets import fetch_openml\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "from tabctx import TabctxClassifier, TabctxClient\n", + "\n", + "X, y = fetch_openml(\"credit-g\", version=1, as_frame=True, return_X_y=True)\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)\n", + "\n", + "client = TabctxClient(\"http://127.0.0.1:8000\")\n", + "clf = TabctxClassifier(client=client)\n", + "clf.fit(X_train, y_train)\n", + "print(\"context id:\", clf.dataset_id_)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fde60009", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:34:44.699083Z", + "iopub.status.busy": "2026-11-11T22:34:44.698816Z", + "iopub.status.idle": "2026-11-11T22:34:52.212283Z", + "shell.execute_reply": "2026-11-11T22:34:52.210403Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "accuracy: 0.772\n" + ] + }, + { + "data": { + "text/plain": [ + "array([[0.7023325 , 0.29766747],\n", + " [0.05054151, 0.94945848],\n", + " [0.04760469, 0.95239532],\n", + " [0.26294938, 0.73705065],\n", + " [0.39968097, 0.60031903]])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"accuracy:\", clf.score(X_test, y_test))\n", + "clf.predict_proba(X_test[:5])" + ] + }, + { + "cell_type": "markdown", + "id": "9d7973c2", + "metadata": {}, + "source": [ + "## 4. Regression with prediction quantiles\n", + "\n", + "`TabctxRegressor.predict_quantiles` returns predictive quantiles for each row, not just a point estimate. (The diabetes dataset ships with scikit-learn, so this cell needs no download.)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "06969f36", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:34:52.215051Z", + "iopub.status.busy": "2026-11-11T22:34:52.214816Z", + "iopub.status.idle": "2026-11-11T22:35:02.351567Z", + "shell.execute_reply": "2026-11-11T22:35:02.350755Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "context id: sklearn-aea702693a87d2751cc473edb459f246\n" + ] + }, + { + "data": { + "text/plain": [ + "array([164.15979004, 143.35258484, 152.58087158, 130.20314026,\n", + " 195.56369019])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from sklearn.datasets import load_diabetes\n", + "\n", + "from tabctx import TabctxRegressor\n", + "\n", + "data = load_diabetes(as_frame=True)\n", + "Xr, yr = data.data, data.target\n", + "Xr_train, Xr_test = Xr.iloc[:-100], Xr.iloc[-100:]\n", + "yr_train, yr_test = yr.iloc[:-100], yr.iloc[-100:]\n", + "\n", + "reg = TabctxRegressor(client=client)\n", + "reg.fit(Xr_train, yr_train)\n", + "print(\"context id:\", reg.dataset_id_)\n", + "reg.predict(Xr_test[:5])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a01752e5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:35:02.353893Z", + "iopub.status.busy": "2026-11-11T22:35:02.353630Z", + "iopub.status.idle": "2026-11-11T22:35:02.958799Z", + "shell.execute_reply": "2026-11-11T22:35:02.957944Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
q5q50q95
069.302078160.332001269.507080
155.275356138.298859248.152664
262.262737149.525345253.321518
352.836693122.921402230.515656
494.553528195.324875295.893463
\n", + "
" + ], + "text/plain": [ + " q5 q50 q95\n", + "0 69.302078 160.332001 269.507080\n", + "1 55.275356 138.298859 248.152664\n", + "2 62.262737 149.525345 253.321518\n", + "3 52.836693 122.921402 230.515656\n", + "4 94.553528 195.324875 295.893463" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "quantiles = (0.05, 0.5, 0.95)\n", + "q = reg.predict_quantiles(Xr_test[:5], quantiles=quantiles)\n", + "pd.DataFrame(q, columns=[f\"q{int(q_ * 100)}\" for q_ in quantiles])" + ] + }, + { + "cell_type": "markdown", + "id": "9cca4507", + "metadata": {}, + "source": [ + "## 5. List and delete contexts\n", + "\n", + "Contexts live on the server until they are evicted or deleted. `client.datasets()` lists them; `client.delete(...)` removes one." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "594bd733", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:35:02.961580Z", + "iopub.status.busy": "2026-11-11T22:35:02.961337Z", + "iopub.status.idle": "2026-11-11T22:35:03.012973Z", + "shell.execute_reply": "2026-11-11T22:35:03.011921Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['sklearn-93906881b2b76a20dd1a638b8bf37862',\n", + " 'sklearn-aea702693a87d2751cc473edb459f246']" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[d[\"dataset_id\"] for d in client.datasets()]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3d377819", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:35:03.015000Z", + "iopub.status.busy": "2026-11-11T22:35:03.014784Z", + "iopub.status.idle": "2026-11-11T22:35:03.112360Z", + "shell.execute_reply": "2026-11-11T22:35:03.110732Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.delete(clf.dataset_id_)\n", + "client.delete(reg.dataset_id_)\n", + "client.datasets()" + ] + }, + { + "cell_type": "markdown", + "id": "bcdb5901", + "metadata": {}, + "source": [ + "## 6. Shut the server down" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "14664151", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-11T22:35:03.114866Z", + "iopub.status.busy": "2026-11-11T22:35:03.114610Z", + "iopub.status.idle": "2026-11-11T22:35:03.209491Z", + "shell.execute_reply": "2026-11-11T22:35:03.200252Z" + } + }, + "outputs": [], + "source": [ + "server.terminate()" + ] + } + ], + "metadata": { + "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.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}