{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "70e9d507",
   "metadata": {},
   "source": [
    "# Day 8 — Machines That Read 💬\n",
    "\n",
    "> **Mission briefing:** Your Studio can *see*. Today it learns to *read* — and to *write*. We crack open the machinery behind ChatGPT-class systems: **transformers**, powered by one absurdly simple idea repeated a trillion times — *predict the next word*.\n",
    "\n",
    "**Previously in the Lab:** On Day 7 you fine-tuned a giant with transfer learning and unlocked 🪨📄✂️ **RPS Arena**.\n",
    "\n",
    "**Today you will:**\n",
    "- See how text becomes **tokens** and then **embeddings** (words as arrows in space)\n",
    "- Watch a language model **predict the next word**, step by step, and learn what *temperature* does\n",
    "- Tour four **pipelines**: mood, fill-in-the-blank, topic-sorting, and story writing\n",
    "- Peek inside **attention** — every word choosing which other words to look at\n",
    "- Learn why models produce *plausible* text, not *true* text\n",
    "\n",
    "**Today you unlock:** 🔓 **Language Lab** — four language superpowers in your Studio, running on the transformer models you explore today.\n",
    "\n",
    "<details data-resource-precision=\"8\"><summary>A precise reading</summary><p>GPT-2 predicts the next token, which may be a word fragment. Encoder models such as DistilBERT can use both sides of a masked token. Tokenization can make letter counting harder but does not make it impossible. An attention heatmap shows model weights, not proof of grammatical reference or a complete explanation of a prediction. A lower temperature changes sampling, not factual reliability.</p></details>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e576b63",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:56:56.457263Z",
     "iopub.status.busy": "2026-08-17T12:56:56.457111Z",
     "iopub.status.idle": "2026-08-17T12:56:56.560625Z",
     "shell.execute_reply": "2026-08-17T12:56:56.559470Z"
    }
   },
   "outputs": [],
   "source": [
    "# ╔═══════════════════════════════════════════════════════════════════╗\n",
    "# ║ SETUP — run me first! (identical in every Lab notebook)           ║\n",
    "# ╚═══════════════════════════════════════════════════════════════════╝\n",
    "import os, sys, pathlib\n",
    "\n",
    "IN_COLAB = \"google.colab\" in sys.modules\n",
    "SMOKE = os.environ.get(\"SMOKE_TEST\", \"0\") == \"1\"   # tiny fast run for automated tests\n",
    "\n",
    "REPO_URL = \"https://github.com/A-Halimi/ai-studio-internship.git\"  # instructor: set once\n",
    "\n",
    "if IN_COLAB:\n",
    "    if not pathlib.Path(\"/content/ai-studio-internship\").exists():\n",
    "        !git clone -q {REPO_URL} /content/ai-studio-internship\n",
    "    %cd /content/ai-studio-internship/notebooks/day08\n",
    "    %pip -q install transformers==5.13.0 gradio==6.19.0\n",
    "\n",
    "HERE = pathlib.Path.cwd()\n",
    "REPO = HERE.parents[1]                       # .../notebooks/dayNN -> repo root\n",
    "DATA_DIR = pathlib.Path(os.environ.get(\"COURSE_DATA_DIR\", REPO / \"data\"))\n",
    "SAMPLES_DIR = REPO / \"data_samples\"          # small datasets shipped with the repo\n",
    "MODELS_DIR = REPO / \"ai_studio\" / \"models\"   # where your Studio modules unlock\n",
    "for p in (REPO / \"data\", MODELS_DIR):\n",
    "    p.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "import random\n",
    "import numpy as np\n",
    "SEED = 42\n",
    "random.seed(SEED); np.random.seed(SEED)\n",
    "print(f\"✅ Setup done | Colab: {IN_COLAB} | Smoke test: {SMOKE}\")\n",
    "print(f\"   data: {DATA_DIR}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4b07eea",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:56:56.562646Z",
     "iopub.status.busy": "2026-08-17T12:56:56.562460Z",
     "iopub.status.idle": "2026-08-17T12:56:57.793484Z",
     "shell.execute_reply": "2026-08-17T12:56:57.792296Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "torch.manual_seed(SEED)\n",
    "DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "if DEVICE.type == \"cuda\":\n",
    "    print(f\"⚡ Running on GPU: {torch.cuda.get_device_name(0)}\")\n",
    "else:\n",
    "    print(\"🐢 No GPU found — running on CPU. Everything today still works, just a little slower.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0ff3490",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:56:57.795226Z",
     "iopub.status.busy": "2026-08-17T12:56:57.795015Z",
     "iopub.status.idle": "2026-08-17T12:56:57.798442Z",
     "shell.execute_reply": "2026-08-17T12:56:57.797371Z"
    }
   },
   "outputs": [],
   "source": [
    "# ── CONFIG — the Lab's control panel ───────────────────────────────\n",
    "GEN_TOKENS = 20 if SMOKE else 60    # how many new words the story writer adds\n",
    "\n",
    "# First run downloads a few small models from Hugging Face (~1.5 GB total:\n",
    "# gpt2 + three distilbert models). They're pre-baked in the course Docker image;\n",
    "# on a laptop or Colab they download once and then cache. Grab a coffee. ☕"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd37efad",
   "metadata": {},
   "source": [
    "## 1. The idea that changed everything ⚡\n",
    "\n",
    "In **2017** a paper called *\"Attention Is All You Need\"* introduced the **transformer**. Within a few years it powered GPT, translation, search, and the chatbots you use today. The wild part: under all the hype sits one tiny training goal — **look at some text, predict the next token, and repeat.** Do that on a big chunk of the internet and the model soaks up grammar, facts, styles, and a surprising amount of reasoning.\n",
    "\n",
    "Today we don't just *use* these models — we **look inside** one. We'll use **GPT-2**, the small, open ancestor of ChatGPT (124 million parameters — tiny by today's standards, perfect for learning)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b535d38c",
   "metadata": {},
   "source": [
    "## 2. Step 1 — text becomes tokens 🔤\n",
    "\n",
    "GPT-2 receives token IDs rather than a separate input for every letter. First it chops text into **tokens** — common chunks that are often whole words, sometimes word-pieces. GPT-2 knows exactly **50,257** of them. Every token maps to a number, and that's all the model ever sees: a list of numbers.\n",
    "\n",
    "Run the demo — notice the little `Ġ` marks a space in front of a token."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75ee56be",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:56:57.799965Z",
     "iopub.status.busy": "2026-08-17T12:56:57.799819Z",
     "iopub.status.idle": "2026-08-17T12:57:05.654767Z",
     "shell.execute_reply": "2026-08-17T12:57:05.653563Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import AutoTokenizer\n",
    "\n",
    "tok = AutoTokenizer.from_pretrained(\"gpt2\")\n",
    "pieces = tok.tokenize(\"The penguins in the lab love matrix multiplication\")\n",
    "print(pieces)\n",
    "print(f\"\\n{len(pieces)} tokens for that sentence.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f75e2cf2",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 1 — Find a word that shatters\n",
    "\n",
    "Some words are a single token; rarer ones get split into pieces. Below, several words are tokenized for you. **Collect the ones that split into 3 or more pieces.**\n",
    "\n",
    "- `tok.tokenize(word)` returns the list of pieces\n",
    "- keep the words where `len(pieces) >= 3`\n",
    "\n",
    "You should find that long or unusual words (like a full name) shatter the most."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4bd8b1b4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:05.656706Z",
     "iopub.status.busy": "2026-08-17T12:57:05.656376Z",
     "iopub.status.idle": "2026-08-17T12:57:05.662103Z",
     "shell.execute_reply": "2026-08-17T12:57:05.660967Z"
    }
   },
   "outputs": [],
   "source": [
    "candidates = [\"supercomputer\", \"Abdelghafour\", \"strawberry\", \"penguin\", \"robot\", \"cat\"]\n",
    "splits = {w: tok.tokenize(w) for w in candidates}          # given: tokenize each word\n",
    "for w, t in splits.items():\n",
    "    print(f\"{w!r:16} -> {len(t)} piece(s)  {t}\")\n",
    "\n",
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: [w for w, t in splits.items() if len(t) >= 3]\n",
    "...\n",
    "print(\"\\n3+ pieces:\", three_plus)\n",
    "assert any(len(t) >= 3 for t in splits.values()), \"Expected at least one word to split into 3+ pieces.\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0c3ab62",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 2 — The strawberry problem 🍓\n",
    "\n",
    "Ever seen a chatbot miscount the letters in a word? Here's why. Tokenize **`\"strawberry\"`** and count the pieces. The model never sees ten individual letters — it sees a couple of chunks. Counting the *r*'s means counting something it can't even look at.\n",
    "\n",
    "Set `n_pieces` to how many tokens `\"strawberry\"` becomes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ff46c97",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:05.664328Z",
     "iopub.status.busy": "2026-08-17T12:57:05.664115Z",
     "iopub.status.idle": "2026-08-17T12:57:05.668185Z",
     "shell.execute_reply": "2026-08-17T12:57:05.667284Z"
    }
   },
   "outputs": [],
   "source": [
    "pieces = tok.tokenize(\"strawberry\")\n",
    "print(\"strawberry ->\", pieces)\n",
    "\n",
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: n_pieces = len(pieces)\n",
    "...\n",
    "print(f\"The model sees {n_pieces} chunk(s), not 10 letters — so 'how many r's?' is genuinely hard for it.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7dac047c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:05.669867Z",
     "iopub.status.busy": "2026-08-17T12:57:05.669732Z",
     "iopub.status.idle": "2026-08-17T12:57:05.673417Z",
     "shell.execute_reply": "2026-08-17T12:57:05.672468Z"
    }
   },
   "outputs": [],
   "source": [
    "# Every token has an ID; encode -> IDs, decode -> back to text (a perfect round-trip).\n",
    "print(\"Vocabulary size:\", tok.vocab_size)               # 50257\n",
    "ids = tok.encode(\"Matrix multiplication is everywhere.\")\n",
    "print(\"IDs:    \", ids)\n",
    "print(\"Decoded:\", tok.decode(ids))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db52caeb",
   "metadata": {},
   "source": [
    "## 3. Step 2 — tokens become arrows 🏹\n",
    "\n",
    "A number like `token 1820` means nothing on its own. So the model gives every token an **embedding**: a list of **768 numbers** — think of it as an arrow pointing to a spot in a 768-dimensional space. The magic is that **meaning becomes geometry**: words used in similar ways end up near each other.\n",
    "\n",
    "We can't picture 768 dimensions, so we **squish** them down to 2 with PCA and plot. Watch related words cluster."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a9b326ac",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:05.675412Z",
     "iopub.status.busy": "2026-08-17T12:57:05.675279Z",
     "iopub.status.idle": "2026-08-17T12:57:08.482496Z",
     "shell.execute_reply": "2026-08-17T12:57:08.481220Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import AutoModelForCausalLM\n",
    "from sklearn.decomposition import PCA\n",
    "\n",
    "gpt2 = AutoModelForCausalLM.from_pretrained(\"gpt2\").to(DEVICE).eval()\n",
    "emb = gpt2.transformer.wte.weight        # (50257, 768): one 768-D arrow per token\n",
    "\n",
    "# GPT-2 tokens usually carry a leading space, so we write \" king\" not \"king\".\n",
    "words = [\" king\", \" queen\", \" man\", \" woman\", \" boy\", \" girl\",\n",
    "         \" cat\", \" dog\", \" pizza\", \" bread\", \" fire\", \" water\",\n",
    "         \" science\", \" music\", \" happy\", \" sad\", \" summer\", \" winter\"]\n",
    "words = [w for w in words if len(tok.encode(w)) == 1]        # keep true single-token words\n",
    "vecs = torch.stack([emb[tok.encode(w)[0]] for w in words]).detach().cpu().numpy()\n",
    "\n",
    "xy = PCA(n_components=2, random_state=SEED).fit_transform(vecs)\n",
    "import matplotlib.pyplot as plt\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.scatter(xy[:, 0], xy[:, 1])\n",
    "for (x, y), w in zip(xy, words):\n",
    "    plt.annotate(w.strip(), (x, y), fontsize=11)\n",
    "plt.title(\"GPT-2 word embeddings (768-D) squished to 2-D\")\n",
    "plt.xlabel(\"PCA dimension 1\"); plt.ylabel(\"PCA dimension 2\")\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43d51b80",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 3 — Put your own words on the map\n",
    "\n",
    "Add **three of your own words** to `my_words`. Two rules so nothing breaks:\n",
    "\n",
    "- start each with a **space** (e.g. `\" ocean\"`), and\n",
    "- pick words likely to be a **single token** (short, common words are safest — the code quietly drops any that aren't).\n",
    "\n",
    "Re-run and see where they land. Do the neighbors make sense? **Meaning = geometry:** each word is really 768 numbers, and closeness on the plot hints at closeness in meaning."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91e18c52",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:08.484411Z",
     "iopub.status.busy": "2026-08-17T12:57:08.484097Z",
     "iopub.status.idle": "2026-08-17T12:57:08.564406Z",
     "shell.execute_reply": "2026-08-17T12:57:08.563134Z"
    }
   },
   "outputs": [],
   "source": [
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: three single-token words, each starting with a space, e.g. \" ocean\"\n",
    "...\n",
    "\n",
    "all_words = words + [w for w in my_words if len(tok.encode(w)) == 1]\n",
    "vecs2 = torch.stack([emb[tok.encode(w)[0]] for w in all_words]).detach().cpu().numpy()\n",
    "xy2 = PCA(n_components=2, random_state=SEED).fit_transform(vecs2)\n",
    "\n",
    "new_set = {w for w in my_words if len(tok.encode(w)) == 1}\n",
    "plt.figure(figsize=(8, 6))\n",
    "for (x, y), w in zip(xy2, all_words):\n",
    "    is_new = w in new_set\n",
    "    plt.scatter([x], [y], color=\"crimson\" if is_new else \"steelblue\")\n",
    "    plt.annotate(w.strip(), (x, y), fontsize=11,\n",
    "                 color=\"crimson\" if is_new else \"black\")\n",
    "plt.title(\"Your words (red) among the neighbors\")\n",
    "plt.xlabel(\"PCA dimension 1\"); plt.ylabel(\"PCA dimension 2\")\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71ddcf01",
   "metadata": {},
   "source": [
    "## 4. ★ The whole secret — predict the next word 🔮\n",
    "\n",
    "Here it is, the entire engine. Given some text, GPT-2 outputs a **probability for every one of its 50,257 tokens** — how likely each is to come next. Pick one, stick it on the end, and ask again. That loop is *all* that \"generating text\" means.\n",
    "\n",
    "Let's watch it. The helper below returns the model's **top-5 guesses** for what comes after a prompt.\n",
    "\n",
    "<details><summary>🔢 <b>Math Corner</b> — how much computation is one word? (optional)</summary>\n",
    "\n",
    "Each next-word guess flows through GPT-2's **12 transformer blocks**, each with an **attention** step and a small **MLP** — hundreds of matrix multiplications across **124 million** parameters, *per token*. Generating a sentence does this dozens of times. Making those multiplications scream on a GPU is the whole field of high-performance computing.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e0842ad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:08.566206Z",
     "iopub.status.busy": "2026-08-17T12:57:08.566050Z",
     "iopub.status.idle": "2026-08-17T12:57:09.301251Z",
     "shell.execute_reply": "2026-08-17T12:57:09.300000Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch.nn.functional as F\n",
    "\n",
    "@torch.no_grad()\n",
    "def next_token_probs(prompt, k=5):\n",
    "    \"\"\"Return the model's top-k (token, probability) guesses for what comes next.\"\"\"\n",
    "    ids = tok.encode(prompt, return_tensors=\"pt\").to(DEVICE)\n",
    "    logits = gpt2(ids).logits[0, -1]          # scores for the token AFTER the prompt\n",
    "    probs = F.softmax(logits, dim=0)          # turn scores into probabilities\n",
    "    top = torch.topk(probs, k)\n",
    "    return [(tok.decode([int(i)]), float(p)) for p, i in zip(top.values, top.indices)]\n",
    "\n",
    "print(\"Prompt: 'The best thing about science is'\\n\")\n",
    "for tk, p in next_token_probs(\"The best thing about science is\"):\n",
    "    print(f\"{p:6.1%}   {tk!r}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ac8616f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:09.303047Z",
     "iopub.status.busy": "2026-08-17T12:57:09.302896Z",
     "iopub.status.idle": "2026-08-17T12:57:09.360696Z",
     "shell.execute_reply": "2026-08-17T12:57:09.359493Z"
    }
   },
   "outputs": [],
   "source": [
    "# Greedily glue on the single most likely token, five times. Watch the sentence assemble.\n",
    "text = \"The best thing about science is\"\n",
    "for step in range(5):\n",
    "    top = next_token_probs(text, k=5)\n",
    "    nxt = top[0][0]                                        # greedy = always pick #1\n",
    "    options = \", \".join(f\"{t.strip()!r}:{p:.0%}\" for t, p in top)\n",
    "    print(f\"step {step + 1}: ...{text[-40:]!r}\")\n",
    "    print(f\"        choices -> {options}\\n\")\n",
    "    text += nxt\n",
    "print(\"Final sentence:\", repr(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "943e80de",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 4 — Predict from your own prompt\n",
    "\n",
    "Set `my_prompt` to any sentence-start you like and look at the model's top-5 next-word guesses. Try something factual (`\"The capital of France is\"`) and something silly — the shape of the probabilities changes with how \"sure\" the model is."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "559f5fc1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:09.362505Z",
     "iopub.status.busy": "2026-08-17T12:57:09.362346Z",
     "iopub.status.idle": "2026-08-17T12:57:09.375518Z",
     "shell.execute_reply": "2026-08-17T12:57:09.374516Z"
    }
   },
   "outputs": [],
   "source": [
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: any string, e.g. \"In the year 3000, computers will\"\n",
    "...\n",
    "\n",
    "for tk, p in next_token_probs(my_prompt):\n",
    "    print(f\"{p:6.1%}   {tk!r}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1409cd3",
   "metadata": {},
   "source": [
    "## 5. Four superpowers — the pipeline tour 🧰\n",
    "\n",
    "Writing model code by hand is a lot. Hugging Face **pipelines** wrap a model + tokenizer into a single call. We'll tour four, each a different transformer trained for a different job. First up: **text-classification** — reading the *mood* (sentiment) of a sentence."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7a1b74c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:09.377251Z",
     "iopub.status.busy": "2026-08-17T12:57:09.377110Z",
     "iopub.status.idle": "2026-08-17T12:57:11.784015Z",
     "shell.execute_reply": "2026-08-17T12:57:11.782822Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import pipeline\n",
    "\n",
    "# device=DEVICE puts it on the GPU when there is one; plain CPU works fine too.\n",
    "clf = pipeline(\"text-classification\",\n",
    "               model=\"distilbert-base-uncased-finetuned-sst-2-english\", device=DEVICE)\n",
    "\n",
    "for s in [\"This course is the best thing ever!\",\n",
    "          \"I am so bored and tired today.\",\n",
    "          \"The GPU cluster caught fire again.\"]:\n",
    "    r = clf(s)[0]\n",
    "    print(f\"{r['label']:8} {r['score']:.0%}   <- {s}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42c699d6",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 5 — Fool the mood detector 😏\n",
    "\n",
    "These models read surface words, not deep meaning. Write a **sarcastic** sentence — cheerful words, sour meaning — that gets labeled **POSITIVE** even though a human hears the opposite. (Something like *\"Oh wonderful, another bug.\"*)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "524b93d1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:11.785741Z",
     "iopub.status.busy": "2026-08-17T12:57:11.785572Z",
     "iopub.status.idle": "2026-08-17T12:57:11.794181Z",
     "shell.execute_reply": "2026-08-17T12:57:11.793234Z"
    }
   },
   "outputs": [],
   "source": [
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: sarcasm works well — happy words, unhappy meaning\n",
    "...\n",
    "\n",
    "r = clf(tricky)[0]\n",
    "print(f\"{r['label']} ({r['score']:.0%})  <- {tricky}\")\n",
    "print(\"Did it read the sarcasm, or just the happy words?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0faeac3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:11.795546Z",
     "iopub.status.busy": "2026-08-17T12:57:11.795412Z",
     "iopub.status.idle": "2026-08-17T12:57:14.046917Z",
     "shell.execute_reply": "2026-08-17T12:57:14.045307Z"
    }
   },
   "outputs": [],
   "source": [
    "# Superpower 2 — fill-mask: guess a hidden word. Write the blank as ___ and we swap in\n",
    "# the model's special mask token (that replace trick comes straight from the Studio module).\n",
    "fm = pipeline(\"fill-mask\", model=\"distilbert-base-uncased\", device=DEVICE)\n",
    "\n",
    "sentence = \"Students in the lab love to ___ every day.\"\n",
    "masked = sentence.replace(\"___\", fm.tokenizer.mask_token)\n",
    "print(sentence, \"\\n\")\n",
    "for r in fm(masked)[:5]:\n",
    "    print(f\"{r['score']:.1%}   {r['token_str'].strip()!r}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e40d6156",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:14.049935Z",
     "iopub.status.busy": "2026-08-17T12:57:14.049779Z",
     "iopub.status.idle": "2026-08-17T12:57:16.005621Z",
     "shell.execute_reply": "2026-08-17T12:57:16.004456Z"
    }
   },
   "outputs": [],
   "source": [
    "# Superpower 3 — zero-shot: sort text into ANY labels you invent, with no extra training.\n",
    "zs = pipeline(\"zero-shot-classification\",\n",
    "              model=\"typeform/distilbert-base-uncased-mnli\", device=DEVICE)\n",
    "\n",
    "res = zs(\"The team trained a neural network on the GPU cluster overnight.\",\n",
    "         candidate_labels=[\"science\", \"sports\", \"cooking\", \"gossip\"])\n",
    "for lab, score in zip(res[\"labels\"], res[\"scores\"]):\n",
    "    print(f\"{score:5.0%}   {lab}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47053456",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 6 — Two temperatures 🌡️\n",
    "\n",
    "Superpower 4 — **text-generation**: the next-word loop from section 4, automated. The knob that controls it is **temperature**: how adventurously the model picks from its list of likely next words.\n",
    "\n",
    "- **Low** (0.2) → almost always the top pick: safe, repetitive, sometimes boring\n",
    "- **High** (1.2) → happily grabs unlikely words: creative, surprising, sometimes nonsense\n",
    "\n",
    "Generate the same story starter **twice** — once at `temperature=0.2`, once at `temperature=1.2` — and read the difference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c52727f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:16.007501Z",
     "iopub.status.busy": "2026-08-17T12:57:16.007355Z",
     "iopub.status.idle": "2026-08-17T12:57:19.683278Z",
     "shell.execute_reply": "2026-08-17T12:57:19.681979Z"
    }
   },
   "outputs": [],
   "source": [
    "gen = pipeline(\"text-generation\", model=\"gpt2\", device=DEVICE)\n",
    "prompt = \"Late one night in the lab, a small robot woke up and said\"\n",
    "torch.manual_seed(SEED)          # seed so the demo is the same for everyone\n",
    "\n",
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: call gen(...) twice — temperature=0.2 for `cool`, temperature=1.2 for `wild`\n",
    "...\n",
    "\n",
    "print(\"🧊 temperature 0.2 (plays it safe):\\n\", cool, \"\\n\")\n",
    "print(\"🔥 temperature 1.2 (takes big risks):\\n\", wild)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0fc5b710",
   "metadata": {},
   "source": [
    "## 6. Peeking inside — attention 👀\n",
    "\n",
    "How does a model know that \"it\" refers to the penguin and not the fish? **Attention.** At every layer, each word gets to **look at every other word** and decide how much to borrow from each. That table of \"who looks at whom\" is literally a grid of numbers — and computing it is (you guessed it) **matrix multiplication**, the same operation HPC engineers make fly on the GPU.\n",
    "\n",
    "Let's visualize it. We run a small model (**DistilBERT**) with `output_attentions=True` and draw one attention head as a heatmap: bright square at row *it*, column *penguin* means \"*it* is paying attention to *penguin*.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6d5dbe3a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:19.685359Z",
     "iopub.status.busy": "2026-08-17T12:57:19.685139Z",
     "iopub.status.idle": "2026-08-17T12:57:21.160239Z",
     "shell.execute_reply": "2026-08-17T12:57:21.159048Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import AutoModel\n",
    "\n",
    "dtok = AutoTokenizer.from_pretrained(\"distilbert-base-uncased\")\n",
    "dmodel = AutoModel.from_pretrained(\"distilbert-base-uncased\",\n",
    "                                   output_attentions=True).to(DEVICE).eval()\n",
    "\n",
    "def show_attention(sentence, layer=4, head=0, title=None):\n",
    "    enc = dtok(sentence, return_tensors=\"pt\").to(DEVICE)\n",
    "    with torch.no_grad():\n",
    "        out = dmodel(**enc)\n",
    "    grid = out.attentions[layer][0, head].cpu().numpy()      # (tokens, tokens)\n",
    "    toks = dtok.convert_ids_to_tokens(enc[\"input_ids\"][0])\n",
    "    plt.figure(figsize=(6.5, 5.5))\n",
    "    plt.imshow(grid, cmap=\"viridis\")\n",
    "    plt.xticks(range(len(toks)), toks, rotation=90)\n",
    "    plt.yticks(range(len(toks)), toks)\n",
    "    plt.xlabel(\"attending TO\"); plt.ylabel(\"word doing the looking (FROM)\")\n",
    "    plt.title(title or f\"Attention — layer {layer + 1}, head {head + 1}\")\n",
    "    plt.colorbar(label=\"attention weight\"); plt.tight_layout(); plt.show()\n",
    "\n",
    "show_attention(\"The penguin ate the fish because it was hungry\",\n",
    "               title=\"'it' should point at the PENGUIN (it was hungry)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1227e89b",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 7 — Follow the pronoun\n",
    "\n",
    "Change the ending so **\"it\" now means the fish** instead of the penguin — for example, *\"...because it was **delicious**.\"* Re-draw the heatmap and compare: does the bright spot in the *it* row shift toward *fish*?\n",
    "\n",
    "(Attention is subtle and no single head is perfect — but you'll often see the pattern move. Set `sentence2` below.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54031c98",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:21.162009Z",
     "iopub.status.busy": "2026-08-17T12:57:21.161846Z",
     "iopub.status.idle": "2026-08-17T12:57:21.222426Z",
     "shell.execute_reply": "2026-08-17T12:57:21.221436Z"
    }
   },
   "outputs": [],
   "source": [
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: keep the start, change the last word so \"it\" = the fish (e.g. \"delicious\")\n",
    "...\n",
    "\n",
    "show_attention(sentence2, title=\"Now where does 'it' look?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f810153b",
   "metadata": {},
   "source": [
    "## 7. Reality check ⚠️\n",
    "\n",
    "One minute of honesty before you unlock the module. A language model predicts **plausible** text — words that *sound* right following the prompt. That is **not** the same as **true** text. Watch GPT-2 answer something it knows, then something it can't possibly know — and say both with the exact same confidence."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "277521c0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:21.223927Z",
     "iopub.status.busy": "2026-08-17T12:57:21.223787Z",
     "iopub.status.idle": "2026-08-17T12:57:22.256820Z",
     "shell.execute_reply": "2026-08-17T12:57:22.255719Z"
    }
   },
   "outputs": [],
   "source": [
    "for prompt in [\"The largest planet in our Solar System is\",\n",
    "               \"In the year 3000, computers will be able to\"]:\n",
    "    out = gen(prompt, max_new_tokens=GEN_TOKENS, do_sample=False,\n",
    "              pad_token_id=50256)[0][\"generated_text\"]\n",
    "    print(out)\n",
    "    print(\"-\" * 65)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c30dd51",
   "metadata": {},
   "source": [
    "The first answer is probably fine (that fact appeared all over GPT-2's training text). The second is **invented** — GPT-2 never saw 2026, so it confidently makes something up. This is called a **hallucination**, and it's the single biggest gotcha when using these tools: *always verify facts.*\n",
    "\n",
    "So how do modern systems (ChatGPT, Claude) get so much more reliable and helpful? They add an extra ingredient on top of next-word prediction: **training from human feedback** — rewarding good answers, penalizing bad ones. **Rewards.** Which is *exactly* tomorrow's topic. 🐕"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5d2d8d3",
   "metadata": {},
   "source": [
    "## 🔓 Unlock your Studio module\n",
    "\n",
    "The **Language Lab** module bundles all four superpowers into your Studio. The transformer models load themselves from the cache — all you ship is a small **settings token** that personalizes it. Fill in your name and (optionally) rewrite the story starters, then run the cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d507974",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:22.258611Z",
     "iopub.status.busy": "2026-08-17T12:57:22.258468Z",
     "iopub.status.idle": "2026-08-17T12:57:22.270193Z",
     "shell.execute_reply": "2026-08-17T12:57:22.269117Z"
    }
   },
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "MADE_BY = \"an AI explorer at the Lab\"     # ← put YOUR name here\n",
    "DEFAULT_TEMPERATURE = 0.9                       # the Story Writer's starting creativity\n",
    "STORY_STARTERS = [                              # write at least 3 — make them yours!\n",
    "    \"Late one night in the lab, a small robot woke up and said\",\n",
    "    \"The last penguin on the iceberg looked at the satellite and\",\n",
    "    \"In the year 3000, the fastest supercomputer on Earth whispered\",\n",
    "]\n",
    "\n",
    "cfg = {\"made_by\": MADE_BY,\n",
    "       \"default_temperature\": DEFAULT_TEMPERATURE,\n",
    "       \"story_starters\": STORY_STARTERS}\n",
    "(MODELS_DIR / \"language_lab.json\").write_text(json.dumps(cfg, indent=2), encoding=\"utf-8\")\n",
    "\n",
    "REQUIRED_FILES = [\"language_lab.json\"]\n",
    "missing = [f for f in REQUIRED_FILES if not (MODELS_DIR / f).exists()]\n",
    "assert not missing, f\"Something didn't save: {missing}\"\n",
    "assert len(STORY_STARTERS) >= 3, \"Write at least 3 story starters before unlocking.\"\n",
    "print(\"🔓 UNLOCKED: Language Lab! Run your Studio and try all four tabs:\")\n",
    "print(\"   python ai_studio/app.py   (from the repo root)   ->   💬 Language Lab\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "262fb60d",
   "metadata": {},
   "source": [
    "## 🏁 Checkpoint\n",
    "\n",
    "Today you looked *inside* a language model. You watched text become **tokens** and then **embeddings** (meaning as geometry), saw the one trick behind it all — **predict the next token** — and learned that **temperature** tunes how wild the guessing gets. You toured four pipelines, peeked at **attention** (every word choosing what to look at — matrix multiplication again), and learned the crucial caveat: models produce *plausible*, not *true*, text.\n",
    "\n",
    "**Studio unlocked:** 💬 Language Lab — mood, fill-in-the-blank, topic-sorting, and a story writer, all in your app.\n",
    "\n",
    "Seeing ✓. Reading ✓. But the Studio still can't **act**. Tomorrow: **machines that learn by trial, error, and reward** — like training a puppy with treats. Welcome to reinforcement learning. 🐕"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97f8dba6",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "<div align=\"center\" style=\"background:#14161E;border-radius:12px;padding:18px 12px 14px;margin-top:6px\">\n",
    "  <a href=\"https://ahalimi.com\" target=\"_blank\">\n",
    "    <img src=\"data:image/webp;base64,UklGRoIqAABXRUJQVlA4WAoAAAAQAAAA5gAAWQAAQUxQSKYTAAAB/yckSPD/eGtEpO4TjtpIkqSI2v1F8Sc8N4OI/k8At3/I//6Tj6f3BqhueU5ce/Oq9iC/k11117+ixBmggCowS0wzwOxAAFWHaKfZAwNzmOJmctNzUHdXrZiivM7bEA+hDdu2ZXNa7Tiv637H44K7U1ghxd3d3akbXku3NtToqru3q7i7FHcLaXFL0KQ4xSU6M+/73NeP53neSYBv8v2MiAng/6EytWjXycESqBzQytuvMaFLg+8++3wCXEseBrbtzx8aCGqf/POeveBLGIKuz02PiP6Zz740TyOWXnm1iHjy66ORLUmoYOcHI+ZdePhqHYOUtdR2pz4T8dxnCC05iN5dN4zXf7wmgAmIDIz6/GMRV67AEuT4TxJ/XgHMTUFZuEPv19+PSSstOYyZyEvXgBtDlMOkaUwYi5YEAnp45WncWIRKdJ8e/Xvhwz/jvUEWvoNYxA6/ig82xoZ7sv++wLJdBItcxnkxe4LZMM+5hWWXodYM8OTuKsndPVnf/XEWPrwzrTln9GRUs4jFOu/knfDhHeexJ7Vi8lrQ2P+oww7//MpILPPZA/c/6KBO58S4120450we3H3Nwiokf/ke3A9eGHHmBJM08scRcZSbdT0du+LDujPi2yEqnY1z/1ok/tR6vQ+jfG9xPcI5Lq7Fhm9i/Ft5a3lV4hcRU3Gb44MNBeDxrs03R4x7a3ANbNjm7BtPJKlCdDwZ+RFvZccVAJGdwME4P44jDdsSP82/IlHp7DD7F1FsWZCQqA5yKenYfDk2bDOuiwPxqsRpF42aH3+DBm0GUXI2jZd60DBNpKdiTayK9OyhXByvTSBBSinAco0Y9WZrNWzYNvqdd0agCmfHN0Zrxxz7qkHzjVaT8iCUEP+KzfBhmrH04EzaOP9adXQ/no8OY8wZv/9dQYgNsQrjithlGLdsMR1RFt1vHgmcEpst9WakDbbYojBn/giocM6MfYZtYIoaZ9eYNf2Mg/ZdEF/ZLmYb1dftkalOiOF7q9lHtXP+TdtMuWDmO5EfH/nf17plEJ5BFcFE5g/bggXzJ3QhQHS/shfABudFTP7pnAYKyNmpVPbleJsYpsH8NyZOxADXPm+PsIaLvtfyb9Z6oQcBQRdRwfiV57w6bAsvZvh6eIlLLseBBmfGy71/Wr6mk0qxTu9DbzNsF9NiGxKInjf2Vcm1d8SBq4+vAKvbKqZHag3XMtO0V0PCddg7vQgQfc/na6hUSESp0J66leF7MOPpT2wlx7njeoyy67T8wSpmAPJGIPC86Sbv/otiGCNPVZEXCc534jzcWav4MamKP0ZMwQFj+fdjGsg5O87GGR67LQpjmXcXrIOtPDMeWUUCjH0HI+YdiJCNviIW5l90GuvMKzYfzogVv/TF6OixWPDcdLBFgHNKXIutdchmO69KSWy995ab7Lw9IMbuMnmDrffpMa6LyzCGsda1b9bM07p71t5s5F9WwxaBbOSTcTyL2Tkx5q2HD2eA27e5+XdAzzfjnX0xQCDV4ezYWrAVneaqMXdzq5CbWSdbz4/v4AxrzW7yEcmLWPDDr8W8Sbg5Em07P4gXVyexyBPrvhw3JdcwQG2oQhVuJW4iWl5gskfjOgEJJlo7crsqZq5KY1El1p4ds5bFqDVrL9qKmmgrPmwCBAhQVUAAQbA4jRsJgJyYmheu2rnx1OvHXTdwjUk1SKPuiue3xGxRmLPlS/HK+hgfXYEAAWojSlEK1Nbilrl7crPU6Gh4SuYdHfg791GpEsG9W3WuM/uz42edddMTV5to0xh1Xcw/FtyGYg5fmMs8ELXG5geFFc1mq2gVrWazOdgabBVFqyiKVlEUuSiazVZRFDlyGQg+zI3uzq6uRmdnZ3d3b193Z2fq6Oju6elqePKUGt5w95RcZp7MTFQ+Pgm1995IX+nB30xtfouG0RZGx18irtoAlExRISXBOudHvPQO7SZ+dAqLN2cZ5YiIXOSistVqNRf2Dww0m62iaBWDzebChQP9A4P9/QsXDg4MtqrUNbK3u6e7u7e7u6e7q6u3u6O3u7OrM3V0JDczc3OXycxYpFEBnUFL3fH+SGPIBkf+Jwb/tiGADIgMrPObubHg1Ddp2/n+D+oico4cBBGRyZEjRw6XyUzmbgKUc7SK3BpslZvFYHOw2WwO9A9EkQMZ0WpFVCxWl5l7Ssk8eaPRSKnRSJ5SZ6PxMkQ7FqvBi5Be7sU0FGRM/OnciLu/ucmIuZS71jru+oGIyzaEaEsst0nDilaz1SyKVtEqiogcubLIkXOOCCAg+CgKAWorCIIPZ64gNov5s8n2Sh8CGwI4rPmbNyLiledem1P0TVx9jYjihl3Bg4+4SgJUJ4iKKAVEG4tcbahGbSiKqihZ9G7PdRB6uQchhi6HiYee9nRQ/f6tUyeDGUOWtRNDiEUTUfr4l2Wyp5ZafIc3fqcwXuiRYHnQEMAc6Fp/rWW7vfnOC08DcobJKhib5xcD0P0jXjyRVPQ3noHElA/+vghA7rSpZAyXZaOODTbbt3vkeruM+9UyuKUTI/YenbgiHpM0NEAmlSIYRosVD987Onq8/9+fewgMeg474ojPrAcrTVkfY8nUxRKs3KsiqPTkLlCyJYsPsSR9jEj6KEltySEA4TUyQSCIyG2YR42rwtxdQ7JkAJZMVSZQldzM2hJqywQBqB13ym5tGIpcJSNyyRQlKQ9JRFvtq6J9WVElPoxyoGfsKKdeQb1YvAqGbAaMW34MYKpZ3FJ74m8/RzWK/Xfot75GYuC5TUEg9tmlSWdnLprvz7zveRBgbHrOFdZIEOZXrowZnzz34ouu+N+xUhsGq33/zn9Pe+DJO6eu3wFY3vzkzlbnb8dI0tgfX3vV1VduilU5B90/ElUpdjhW9HRkCH++D4HB5mc/fO8d9z15xsZggHTU/hR+zpWWoee4jQbiN49ZHLV/qzGiw/Tff90BasdYI5rLy6qw2ybtzP8+2z1i8ylx0/IY+O0TD+aaOzqK71/+RtxxOBhkf+D2/Xnglx2Furbab1WU/bHphxz6yX+8T9QZE/5U3Hf0KmOX2+aXH8S5iGz33bTvgVsfLQV675LJ++5z9cOWq8ymxRdIVeF3z/oWt379ayee9PXbYkUMY6UL4vodxnYtdeiMOGciBuLya7bn9Ju8APrPumKrvWdY2OUXHMxZj3aO3O1Xcd4IqQ3npxHfI1WFzTnrqHx+zJ378KzXY3qniTTvwks44yqA3pMjblk+G0S+9u0x91xKeco4IBcXvVlc9qIFtcYaT8aPqR5732yAyBe9WlyHFxDp+cuLWec0g0pnh2g96qqC1hUFn6by75tgxrovxneo1Hnx2LIY2Px//orTFziAvfWPKdcCWnDuxZx2L3DS7+JcrE50z5yb/9MnVQAJjX7Xwh/8dSt2xnEiMTqlVnLYYW48PgYjaOA9nloo/n4/GbCWI+rNxs+M8/AkyTpY+d4uBPigpyIoK7zooNY596Vm7IjX4M7XU1fyhlZaF7dRT8TlJJeU6Ho8bk0mIDu1oQXzLYJAztLJc+v3j7aKdbGapKNv/2rEkaQ6gxaE/PNRfJaEkyFarRaog6/m+H4WgBFFUSDmvEgAGBFtOKfFOyvIqTSOGUulY4UqwrGoESs9uvX8uKgdg2+QQAKcU2NwEznlxGdzfJoEGKiKaEUWCAdveeFxf8SeeI0x7Ut9H+R7pDrABBo4tRVbYjiAUSnrfqH49LKFqDQ3FUjUinrjE4PFOTi1QgQgIaO6gVST+NFZ3JPnrITVAN9UQwZCjHy1eECiUpr4br5fAgRRQxRUBjig3J2LDbEq07r/GcdZ0doMrwkxl0xz+Vfi/2RUqYrEX2PUNjjkTCsPwFIpqIxoK/HdiM8pVamSsuinKLVoQo3UMWMzHR0xlVST4XiAiROQs0vE3/AqxF0xsDYGTrsRVYAnz83ereJeF9XOaX+GrXOc3YZgd0uNdXd66wRcJYNUpy9GTKYsVtq+d3DcN76NVcjwNpyrIrbDq9oNWGW9zgzZB1fHqHYOvx3rfi7P7pGqAr6/1HIT1r1rEyxxcsQUUk3i7xH74SBQnVGO0nstGHfGKq/+D1Ylxj69mnd0PJzfWxarSsThE2T9vPapMdTVJw4NjQFhiUlf6+5f+RNzqDbRrnFr5MlY1egxHd0jlhWAsd7+zZxJ2Se3dfU+9NipEYeQqpx4/cGHpr8fa5ROjTihrZ9HPppEOeoAURkHPtq74h5LX7oaRnXi2FkTgC9HfLMd+/wT4Js9Fv/ZBMNROyAYoFzwzxOBP4ytEURbN0TeuEpa9+pZs577LAIyt1J7OUWVseZ7O3fDsnPzPWZVhqaOXXr82jdPLn0r4htt/TriIBysLQWAgHz2z9+K+DYYtcYdN1x55Q/23PjtPLNRqALoM28V945/IZ7qlYaiifBsSaJhqShO6SYvCudPOQ5VKmGs14y/YAEEhZIkNdQgosL587QL7jnrc2tcl4tN8BpOAFh1GZQ4JOJP8hrnoig2LWXwukZRKseOrPZm67XVrc71yVneud0p1z44mGPPsIoMOedQgz8XsQvutCBqgm0i30uAIHIuNAdQiXYkZ7fIv6dK6nm+OEGiUjhAIqgWo2evxMpH/u2OVyP+WhPwNTVkAMZKC/LdqIbGM3l2F4IWjKqSxgyikogDEl+KuNVcNZzxRwwYf9CCfDVRUUAGIvspUXya5GTqfXCFtbjrUSsAYUAIRZQC5SqB1PlYvDTCrIJRL8Y3abOmICoSJ9xCAtI6D+Y3xkmlAk4kgQQY18bc1cwqXNs141Sc4DVYXSoRa71RFcRedOj6iK+TKqSxL6yNWzK4NAbWDy+1CITk/KWIHXAnwMwMaYDfMTAlQxAZmQwsll0XgwjLVkqstiqJHXL8mIaVbNTzrSlVmVCVCqpNjxwol7txeMRJeAn4SqnSmLwwfkOHAEtcHzNHmQjuH8w7hyzwzP5TCQhE7E5Dy71ezFsbLyW+dh0OkGznweLzoZLnPD8K8sDSL8X0DuFEwcI8CBFdf2Tulx+0DDSj6M9NoMWvJyCUvZhDi/LZqyHjuIijwNyNrtfjGxXZiiZVNIvcFDjbz0gSYDZiVnOGFQK8KD7bBsYxOe8EnhxOijcnYZDt+Qttx92bQIs/x2XKIChaO+LOQZGnGQZi+QV/JZXExP44fnLLyOxmaYNpFn2bvRUPrIrh7Oisu04j9y697YHc/GM8g7HNOP5nw9yMjhVuO+BBssV2sE5hhaVd734EgXPwK/HXFQHWvzGuWUEFYpsVfKtRCETfTr7WxiEV42ZNd6Ns3BnxvSjt6n6evA7ngFfiuA5g3O/ivsk4QOiE27qv/fZl/+3ZdMonth20DOH7eJyKk/hbxBVjEHzi9Buv2AGVGl+fdve/p24YFset9GAcuyDo6jjtkE6Esfum/2LrP0H/a//80WngBSivd+RdmZ8RdPQt+9v5Up70qXvyEV9vWRR3P/4VHHAmTn3+ndvPOOfu2ZdvB4RirS/dfuPLvxktSSO++87Nd3xjUjD+x7dcdwwCjG2vu+rym44xK7bY4OZb7/5yUh3OxB8+P+PCv142+4kvNXAqmLvHyTN+wrvevGHSy8qg/LkVb7v91t2RrPsfd939j/VQdCXopFJdQOoBRgjwlARgAJ20LaOyQwy1s8nQHZh8zLFf2mMsSAANAV0OYB2Ad4ILGlQ3AHoECaB3QbSBF6SN1+qc88QMsFwBClh9tb63H5mDZcpd/UAPtR1dlE3US2KISi6qpQrzZLQpiCoFZUkVkqpQotqdWjNRKxOVJuplLsoyBUOUFVR6DuplBWWPTLUQlTIZi1M1H5MyT+7iY1ISRGaIkiAH/39ci0k1qtGwTWjxoAoxTFedcBavoRKmYYfKFdIQAkTjzgNx4x8n4iXnsy/vjpecb56HVYjO23fAEcs8tiuOs+GjG+EY608fjwBVf3QkDUkQH7LF3dhyXEpRrLcyAqSRz915XzIBYrVJ1GuziYBYI14ZbWb2l0l9gBixWVfFcFQSEFlRynP+2+qHD1qUEye+1PfeERjlBQvbiHlBeY135v6dDo5de143EAzODYCOnpACQRAEhIgAEQQRQY4gSgFBLLa9fzDgwkqBSgpQhUpSSYBqVIoQmBAySREMdpz2C8+o+fn7e2hO+ltJarz2U/74DFER1gbJK1aZ/+k3dmWp753wywaVBqTWCT9qJlEZERBAREiKKOXIOSJCRClyBAGBRAQRRFTEYOeZf3A+fSaLOoIcKIpciiACAgiBRI4AAolAQNEYSzlGLjW6mJdUco6Y082Y+XtnL+XcTu1yscFxr+rSf10wor9CovzZnw6kEASIshQKEVRKSFIgKiMllZFAQog2V0Z0r0hkiIooBSITQECIIQcCI6KCEhWhDygEPb+/Crh9RCnrvrjrvnvibqIU2cyIKEXVMgM7MePGNz7ZF4MVgaCF92SGKCCoFwhRqSh5jdUJCMzU+QYFH8MWo5PnaATgxY75M8d88fAvxxbZAUu5RW3d/F3YMk5+dYVMpYKP40BafPFhCkAjopVy7u4ARePZKynf+VcPIG14Sxro/P1VnlGPVywb28MGnRrs6QRE6vMKfSS0qCL4+A0W7P0oBRz/LgXZj1jGG558hZ9ZpuCvh/Q2Cn+aHAzs8RSZTMduozHgnb0fJJN5ao/XySwpBkM1MQz3HIBHLiVVWKqwFIIiAFxVLipdFfKPJ1ZQOCC2FgAAkFMAnQEq5wBaAD4xFolDIiEhEzo+mCADBKCHABk75Kvzyx/P/l30T3X0Wh0NzV/0vWr/hvtm+QH6M/5PuD/pp/r+oJ5g/5N/Qf+D/c/eD/1v61+4j+xftJ7gH60dYV/ZP+Z7AH80/rvpi/83/jfA5+2X7f/AT/KP7L/xPz6+QD0AP/j6gHYS/yD8Mfcz4M/lPyg/Zz11/HPn/8V+VPLu6i7Xb7DzZ/4Xhz8f/8L1AvXH+Y8Tva1al/lvQC9XPnv+s/v37r+ebqcd9vYA/l/8x/4X9f9tP8H+qPlsfav8l7An8g/qP+6/z35K/Hd/o/4792P837aPzj/Ff93/OfAT/Iv57/sv7x/lP/h/m///9V3sj/bL2Lv1l/TD95UPYEa4GYd/abbNVX6niLpKnvoETsrjiPHCI0/Wc8PrAjg7n8EqVXtHR+RLc2i7lY7+CTVqwOIzc5BHJlkYxvvrp1PLDFN2XvLePuLoZt0rLW7ny3aM7NhScU6XHruudQM3RIc4U0UqF9LZxJyznP7T1gMLBOkT+AcPhqBYnFJqI/taFKX0yroNPsy2Fpkkxv/aMYxdfZZmBZ1BHZ81obGX7T4vWQVvip+jZg9X3Hd29F/udLL20QewFOqp2t/VxG97A568m8ySl+5zsLa5ZcvMOPEoMbiC+r8Kt+W5bx4Cf7ebDhNyICv+u9b3XVNsJoOhSG9kWk6vm2WU4AiGw1gKXdgV/ei3YOTHaCXb4eEuy4UTvPGZ/8w3a13r8dumfz235RMJYcjXg94Pl2C3tP18XbJ7q8YhHgBuV+1zWAmGDcC8XoznK0aVGP/4qMS8ryT6NZ81gsdEHi/AfD69XH2MCPMYd6k/rS+QfPfTS1H0/AwQhZlvoCrvH37Zz0/36f97PWf4+2X5SAAA/vATgBDX98Iw+ABTcICW5/6API/Qk0kJwFpjSafS/Q4+b3nCKiUfLEieBTx8OyypKGtKaLklX6k60AYToicN2+4K1YTQFcbHVuQx3Kgcjjp8Hh9nPMvwN03aLtADfGNtJV8t4irAxRcuILD/z12f5R6NlESmcD22h2lMiLggyioErnZL1i12Wouql2i0pNCbXOcScIfntHmaa7l92hnQDo02eXoO4Drs1mPAMuSivzcgRN4iG/g8T8X0Cu4mVFVyl2ZGHkQ0wPO+Fcjyqc/WrV5a22RDK8oFFD7Y1gfHJI4An5FjehKfXjTd5FbXuRpL6FoEq6BE+6lHd2ScUiip1BqaF6j7o0PTgUulHIV6uyngPo+03zz8hpN8waRusKJX3AOC/3GHy9T8/svkpMuGotcE7w32wZ6DD3KkX+jdC8j9CkOGSiBH38cpRihEEC9CpjploWb4H2iwJ9UnP+rnAHByuvNcc+U/LiBjSEWgpcyFUM37YX5Iqg95iXjQy0dYazh+Al1Jzny3p3iVMmmDeUm2W8jKhRlf10ZPtCAAAVcyxHoE6kSP2J/L2fxdqnT7MOBfjlEVQ0sJBxAxQJFRy/ACI+WPEcAODz/eHtYVwDSymCIn3pswZa8pEHl716Xx1a28jcMOpvG9ruF6JHhrwtIwjX98nb564WtsH5S4NaWJbxBL4KJVPHSPi3WZlDUi+JmJOhGvwUrDVZ+VFW3e369BnVBifU+7gpMh2SZwH4n/pBqxeeqtQa+7DfHK8PSVsK8mN7dOPZVrmXEDnNwQxhmAtfFUn/T4x03v7wEHNL+M71Qn+dlr/nDSfB7db9qR0QWyLcdPorN5qv5VQDvysftQTP+cnT77NE8chxPYrrGIHfmsp8NwSViVln0PnImSVSWZIX4Mk5h9KgcZpH2lVr8pKKRozBL5VHHbsFWhZ/c2iqPUBKb8RDytqaZ7OJiMrBRSiX00t++6goH74wNKiZT4ePyP+Yh/V7LO1rrZFbte/il1GsPPLv5MsqANiy5j9H750prvo8z4beZ0NuHxfxLROMIItU5fIwFw0c9wmnGbqmWnAho71i61UxteJIIcU/e4D74XxVnNe2+7X4lYMqjIi+YUTN36cvpiVcYrinaT2JIWr0r6Vw705Y+1FrT+5BxIsU99YqcFjaeQtp5xR1OWTBoP0gpOy0DkNMtPZnNU+Yzm0VxG75/+RtOAcVJf+KYtDwAdHC0GUS0Lwa1UAdq1Xvh8dEQzNgDs1eCmDg7qQM6w/xp4WjTM+8RR6IPqE0+9c1Xslh7TC7l4GUYpUgS5Mn3PedZVrx8yeBlroZWPah7/CcfyN7gqzkAAG9nFyi0n9XLfeyIrDCmgvKqwqTGiZSXPsgZLxoZL/3NxkBwPKpbak2uVoapL8ZTDcsHlZgt59DaMjg2xiDgyj1vS0Vn8fQ420OyDMglyFFDo2ZLIzgrU2AgzOS0r9ExRG2LaBnavLmZbvNfMqmp4S94JHRHs5lBsamuNH6iwrFJIa0R5AVZvlLqhHBBlbFeBVbIajI7/3lYOAFuvrL22+YqmSm44PVCTq1lvi7U90LIkm0OPfqzUwgwLv8SiHgO63VO0jPd4J92kxLUfA4lJn4Art8iSYnuvh++8/lH6SPy5XL+FG1bW/1p9h8gpBfNohHuAZUq0AVRC4qyGQ1pcepXEdfj/u32jiwgfsOH+CjRkSjWnK5s6fXAWjNd6KBaO/5JM2BCFp/y/JgjXTvb9Mwg0iiIQY/yu8+/B54a2fdv0NhBaJMH7oSedFS27NcrExUXsSz6pARo+4xC/wGig1XsXDoUjpkJReOxNOCPTZ7eSutjXqanB3flcO8Kb73Qgftv3KOnhbRRmgI4FNyyzWLGp3mI9epIwwTREyUd7vgbysyfFPhIO7cJPd+WjI69LuxDiA3uwxvDzaB+oYxwvJOHTCCrhgweLo1C/ClZJeTRDGsErqzKN2GZNQXMFaZOk3OtW9zlgjvgOSh1XdT3Qs91yKAhJ7eYOta+XvNcQTBDQCV6lY9mQWksgXI7CT0mqt+GiMuaR8TgmKtGP1oLIVLciA/7RMWtkyfrctlQjPBUG13mgUdRpiGxva67jfd5fXcUvHp9e8+B7G8As9LHMxHT9dhFm5f3PMj3YL4zzQozi3XnPEJKnnoA/r+OavX+MzdmMfw4HSN0VSXa4mIsy2sf4/W4AwwCH7XH7RSb85mSZMFWOQcL/0BH8HtwZ6mixuMFKSWjk0tjj8kbuZn5FxY38tVHlx1aVDqjd79MEwbh62AjaWPzOZclh7gCxlscmEOL0bhrellqZUtddDw/PGpCIcXDVi41IbkURJgQz7c+Oxkg+dDTakjypzXNz/pQfiIUhWnfJN28yv3/1GLA1Z5owSSrsm+yrkpoZyrwcXDhqQLPLLat341qbP/OIudJ4B3NXV4zcjIKTQfrl8xe7QL0pNXDxta9GbrVr1gAzDjqJEu10+mrYeVxIS2vxV6WSbNG7t9Q/tdxFUu86nj5KQFbTx5yqJA8KPN3utK/B7R1o8E+JMMEUxt2xXmyRMuQDLpu7MuWso4RbLCGhUgJf2zf8D4bOPqjr4k8AyMaW69uyqtL8E3/UEL+FnyUn6QTyUH3VuKuj5I51CREkS/3es/v5P54Fw8+4JT5Fx3U1njx5Skex1dQUrheamL7wylm/gv6Y19zKDz84xXU614rk4GWQItNgQWaXkHNpLCCwn41ZxCbHXYZ9IaL0XE+9Cs5pS9Rh8eR8dRRspECRQFEw756c6qHnu2UxXOh1XpP2kf8EbBib/LFXwLDEaTkdmHLVHWlQygB6hrs9QvWqncCugDInRsPW4rr5d59adLifU5hAQXwm0I/KihsWipg9BgWX8Q6CGhCqlLH5K3QuotivIvxTMnatMGiBBqU0xbkRcJC2wYTeyb+3yetRS/Rv0SbjuJ/H9/68if/oYM79K+jiwSUfHOIDb712dZe8klrm1m/9zISAE6S7XcfZgk8GhomIF/w5fFWGFY09BmfJMcKxjlGuccgIGWfRhlg4Vt2WtVKNXpnQ3AOh40whOVMV8NRBs/+hWOYdc9m53n3khRGTiAff1AGWQ04nNaIhI5/6YwvXVpMawMu7THRWD9Z+Ub5dXPJDVl3G+JnIumQwPSGKfndinL9xZ099ULrFzdpwF1qPQf+26rPy2/JLVs30/ED6fg+W5sZDH4wuq7pPbZ1dF4Bejz+NRMJrCb4PER4o7y0KJ/SMbsOc8TuJOR27bpV9T0foFXSmviDA7ymNLnowSejpY1PWMB9jTnMVYuznkzAKSwNabkzzH8aMQ4m3xyis2D+c75TVBA4zvWjiHApa0AoZZL/WGfXaCD/OhVAftRdJnZ6NyjINc+NraHciIpNLMBj7Q4Tjl6JQp16Aq1SxVxsq+kJkxktMEQnPfGMrBGvP/3lmIkA4ERgENCQE4UJ0G2Oi0jpjsToifs6NeEKC5ccMSSs4TMTnemXl6mxbx5iAWRs4r9T6r1OTz7WELHlJW3e7nh5Zg4vYzH2hU9PX41tRl3hAhf9njzIZ/uJClipzVmFL+bfOg7gyHExSzuYX4L8fq10yS3A1+gDSAcJywdPdLj417Ds7Z45oTRLU3K5yYmjOHv97IrYpH8KA45br/KlyDjOT010aulVc5p+m/OAR2uIpVRRQ/WiG22C/X/3xi2rmgnXc3/K705yRk2zJ6ZX6wb738u8x3wNuKH48JKu3449b74ImUPzEXiiKhPGCuFNCZcoEj2G5kfrm+6ENkyk4HCvBxE8l9IBJEPu6Cbj0tYNGdm+LuQzQ+sH22nBtwF9pXG6H1Q4P3YuSKtKuw/kDvgMGfYuSy9XGLVR/cUMRGJ9BjKLGbiJPdkvduilVnipBzPIk+e+Ru6uLzsVBJPa0LpgTwBF7C5YTuQqaCJElDJcSoE8KJJyiQC+wfetr4EMA+4JPIi1PhK+CfSGMrQHIHohG84FBlsqebrYiF0ocamWlSDbfKmcCTp8cHOyqjLuSrjJM9J6oQ/BLyKntP9kc9kekIa2EsaOwtzsfCb8+NQTfJQ5Z9oSlYZE1Mgf2whgoG8WXfMY+X9HLh8DkubqY7fQhyyJYuuvhki1IC4Hdhu3Gf3IE7gWNpWJLexNEVwuB5F0lDGFpGTV7U7nqmvzBhuf1fwy8R9M+pcK9e13yY4J6f1599ifl0InlQ8fZXUv+KN5r9kRXU3uYFvCAQXfvv8G5Ur3yJ0t9QbQ/246I4N59ifaRUOoN1vb7NaxsrDnDqealFiAkRWtB7I1sqRKXonprWOwc+0pGDgV67CiP6szbbjwQxMaJf9qQcz0a3En43c1Y1JylqC2wrUsnpyCEypZHe4a0ZI1Sbrgy8jjzPHEl9kwm6RxV2aG2kKSlLmnJzFHUvL/gh7sGfx/Y0VczBL62x1fUcu1qmOlHTUS2H1DdNrERXaINUo3D1X7vpvQHAssKG3M66KB2ofvgOYm+dbM7kudnddqlowpMsq1dXQkZ9Plvk59papxyAHOoaPdueivZD/GDphJTP6TYbwe6WMO+F2sKcsbiHUTOv0XdDr/U1vJyOxUMbsK0rOgbF8D9gSYzWeNXOQYioNqIGabtgk7nTDVTn5yQIfTQW5HYKEyKwa8R/BKhPTN2IaugjqVZWtWDo53xM1qTDN7guQgQyuW5Px7d9cB+up9fzmnjC4aYDk/zCJeRAk+dF/LHASrWSz9NxUYpjh+pFEnukKQ5SkmdiJ/8jRUQV25pBq1fWWAqQTthpU4KCBdLIBiUzX30eIsI6AHb44YkJZNAwg8T8gO1qoB/ErXCLJjJsUCyE5qCPcaK979riGE49FhTdClzf0p6AP7HwaNw29U8Zi1aLImmYoKFOKAjnFZBhjezAskTEzY01KnOrT0n70VBO8Z/p/wo+k/tqSAnqgu6ltoGjyC4RIHv8OW+wB/hH5cfzBqv4SRzTsgjIYo9/C0Az8Z+5AUaBfRmBStj+/deInNA1uHxdw4MW/EjHC4uQjSuk8HdsdqadJNlFR779p8slkZ6khz4tcf/HmsTo0z8tWTL9LA9HmNlUgKaZ3Or7IWoVlLGSZIkXhXcNvToaUJl2eJv3QRFB+bR7vRrgMcla+EZoQH2DvFlEyUevB5HUhV/extLE2UgH+sdylBpUbiNCDZJ1jVe1dF+iYhaQ04AGKH+udCsT4AeQZ2D+iowqLjEcVjk7XX3fTMBm/HJHgGWYSQTNsrI5cOVPY9I0Ux8Hos7YbMTZSttQpgJ1oTg61z7KlYGV6DNgqYwx5X19yKE4ooYiZX0622QR0Qp+bgOgEF19u16f+1jS/WAPuH7ipaIwyTtggq5jRugyd1+bsnyu71SrqtFZDDviJcVShzIO8hU0v8XxKfBjBcHlhMxphJ27iXYID88Bxo/Q5Of0VS3RJXLHIk/Ragm67uo5hpnMFMHSbIK6E/Jugqbp3oboYWAcIX7ZO1eS+FFhD+9s7CMwJY8V75NQE/sEtf6N2Pg0tFlz5znSMHrKgLxtVobmhnu5ie0Q2JzUDE3V8TfePpVpn1zyHZh8qY+luf3pk/Hj5uVGvvKPT7pCuQoDJyILc3P+Gab9t8JUaZObF7J3lGpheFQCioxq2JAuQt/hw89/0ha5df7zjkzx2VCLi1m8sDx6qvIg5IBNWf/WcDltqnB/4zzEHCRQfSu291BlhTlm0CRYphysQx/kMZAlKyEcvDvhBiWP6MQal63KZr4+h7Keac0SYcAXNY8qg/4LqT253kCm0M3OVI0btHYq3C3l4qObplGEjoq5zJyXDOl7b9MvDhOUa1ZpNvBosWFVHCH1AihE9nnyirEt0sDANKVtpsqyPIG0y2bK7RwSQiYJUrygN13RQ73LDq/umtHFr2eQXC0GydMV9jpSm+vKWY5aoE4zRXwyf+kYDI5NX27awP+76F6DC4XhU92joZlOA2l+1aTdV02SEWAByTyItZy2jlrDKDKd7tpTQdsgwowKFJrMbt17cmHt55XlU12xkvLJVtxkJGIEhEvpoZrbmmaHp3xaUKWQK1+AWYo7w0XljKSjVZPJF3v+mRMCTyFmGs2L85gna1f7dThR6NkGC8XOXjljWYuLReJx5TkL6dsiWyJnSxPwIfgNHUJWY4vx7OtXHrmE4WDVufuQMeuDI2md33yJhOGl2KfqccmeYK1eFE1WlNv/YSqXclxJsjeI26zgxTn0i9Ocoi843ftYDfsZzHdkDNJcEYAUNSNuJirSvpl3806LzrY4deTMbavVK+Sx/V7IztCW4tfS2B4D/fW14h3upRkDlh6Jxaa2f6IUM4pBrJIMKTKVYvTZnBD6p8NCqej5Ag6nyKhz1mq9FSBI//kCpyc3Med35j5yks4fHZNwHtQrYYuG0n9cTxi/9v7mL1/2RMDTMh2wB/35vGGY8w+akNn8b/y0UJ+ARD1fbElHWmX+uSaXEeYmfY5IvXwjOfgcfMKeHjfSA8oaXh4Een3AhS8HlMMBsWhq86BWwYAA0NgwOY3E+d6sv8/ZPbY7Qn94gPNEqTvLKPKy7Z+HvVP84M/LZKzR+Lzo28Jcfanex/VEV3VtSSWISJCQ2IKzcMfJ1O4x3lwDmXW+XBvsNQ9nACKNwWXE0CgkAn9F4yvI+NekwAoOLFdjHg1uO87D3e5y1Zi7Zg64yZLLx871+65A+c6/8JBW/PAfOk/p6KocHyRhI5mcIICMS+lzY1u2Usfh9/CWwvnfX4r299vlIhdvjfO6U4I6uhpwiz/zg5NLobAkM4DaPf0ZmMlHMDLjNL1OpVtwp/YWCILRhVroXjMHGalACHoHTL+WC5D0y7WejtU1sOL4GJ+76EJUbLQAAAA\" alt=\"Dr. Abdelghafour HALIMI\" width=\"260\"/>\n",
    "  </a>\n",
    "</div>\n",
    "\n",
    "<div align=\"center\" style=\"text-align:center;margin-top:8px\"><sub>The Lab · AI Studio — Intro to AI · © 2026 <b>Dr. Abdelghafour HALIMI</b> · <a href=\"https://ahalimi.com\">ahalimi.com</a> · All rights reserved.</sub></div>"
   ]
  }
 ],
 "metadata": {
  "authors": [
   {
    "name": "Dr. Abdelghafour HALIMI"
   }
  ],
  "copyright": "© 2026 Dr. Abdelghafour HALIMI · ahalimi.com · All rights reserved",
  "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
}
