{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Bab 9 — Neural Network Playground\n",
        "Forward pass, XOR, gradient descent, dan decision boundary dari nol.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "#!/usr/bin/env python3\n",
        "\"\"\"Bab 9 — Neural networks from scratch.\n",
        "\n",
        "Portable standard-library lab: activations, perceptrons, XOR network,\n",
        "forward pass, gradients, training loop, and SVG decision boundary.\n",
        "\"\"\"\n",
        "from __future__ import annotations\n",
        "\n",
        "import math\n",
        "from pathlib import Path\n",
        "from typing import Sequence\n",
        "\n",
        "\n",
        "def sigmoid(z: float) -> float:\n",
        "    return 1 / (1 + math.exp(-z))\n",
        "\n",
        "\n",
        "def relu(z: float) -> float:\n",
        "    return max(0.0, z)\n",
        "\n",
        "\n",
        "def step(z: float) -> int:\n",
        "    return 1 if z >= 0 else 0\n",
        "\n",
        "\n",
        "def dot(a: Sequence[float], b: Sequence[float]) -> float:\n",
        "    return sum(x * y for x, y in zip(a, b))\n",
        "\n",
        "\n",
        "def perceptron(x: Sequence[float], w: Sequence[float], b: float) -> int:\n",
        "    return step(dot(x, w) + b)\n",
        "\n",
        "\n",
        "def xor_network(x1: int, x2: int) -> int:\n",
        "    h1 = step(1 * x1 + 1 * x2 - 0.5)       # OR\n",
        "    h2 = step(-1 * x1 + -1 * x2 + 1.5)     # NAND\n",
        "    return step(1 * h1 + 1 * h2 - 1.5)     # AND\n",
        "\n",
        "\n",
        "def sigmoid_xor_probability(x1: int, x2: int) -> float:\n",
        "    h1 = sigmoid(10 * x1 + 10 * x2 - 5)\n",
        "    h2 = sigmoid(-10 * x1 - 10 * x2 + 15)\n",
        "    return sigmoid(10 * h1 + 10 * h2 - 15)\n",
        "\n",
        "\n",
        "def forward_layer(x: Sequence[float], weights: Sequence[Sequence[float]], bias: Sequence[float], activation=relu) -> list[float]:\n",
        "    # weights is input_dim x output_dim\n",
        "    outputs = []\n",
        "    for j in range(len(bias)):\n",
        "        z = sum(x[i] * weights[i][j] for i in range(len(x))) + bias[j]\n",
        "        outputs.append(activation(z))\n",
        "    return outputs\n",
        "\n",
        "\n",
        "def mse(preds: Sequence[float], targets: Sequence[float]) -> float:\n",
        "    return sum((p - y) ** 2 for p, y in zip(preds, targets)) / len(preds)\n",
        "\n",
        "\n",
        "def train_linear_neuron(xs: Sequence[float], ys: Sequence[float], w: float = 0.0, b: float = 0.0, lr: float = 0.01, epochs: int = 80):\n",
        "    history = []\n",
        "    n = len(xs)\n",
        "    for epoch in range(epochs):\n",
        "        preds = [w * x + b for x in xs]\n",
        "        loss = mse(preds, ys)\n",
        "        grad_w = sum(2 * (p - y) * x for x, y, p in zip(xs, ys, preds)) / n\n",
        "        grad_b = sum(2 * (p - y) for y, p in zip(ys, preds)) / n\n",
        "        w -= lr * grad_w\n",
        "        b -= lr * grad_b\n",
        "        if epoch % 10 == 0 or epoch == epochs - 1:\n",
        "            history.append((epoch, loss, w, b, grad_w, grad_b))\n",
        "    return w, b, history\n",
        "\n",
        "\n",
        "def write_xor_boundary_svg(path: Path) -> None:\n",
        "    cells = []\n",
        "    size = 42\n",
        "    for i in range(11):\n",
        "        for j in range(11):\n",
        "            # map grid to 0..1, threshold for network demonstration\n",
        "            x1 = 1 if i / 10 >= 0.5 else 0\n",
        "            x2 = 1 if j / 10 >= 0.5 else 0\n",
        "            y = xor_network(x1, x2)\n",
        "            color = \"#bbf7d0\" if y else \"#fecaca\"\n",
        "            x = 90 + i * size\n",
        "            yy = 90 + (10 - j) * size\n",
        "            cells.append(f'<rect x=\"{x}\" y=\"{yy}\" width=\"{size}\" height=\"{size}\" fill=\"{color}\" stroke=\"#ffffff\"/>')\n",
        "    points = [\n",
        "        (90, 90 + 10 * size, \"0\"),\n",
        "        (90, 90, \"1\"),\n",
        "        (90 + 10 * size, 90 + 10 * size, \"1\"),\n",
        "        (90 + 10 * size, 90, \"0\"),\n",
        "    ]\n",
        "    pt_svg = \"\".join(f'<circle cx=\"{x}\" cy=\"{y}\" r=\"14\" fill=\"#0f172a\"/><text x=\"{x-4}\" y=\"{y+5}\" font-size=\"14\" fill=\"white\">{label}</text>' for x, y, label in points)\n",
        "    path.parent.mkdir(parents=True, exist_ok=True)\n",
        "    path.write_text(f'''<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"650\" height=\"620\" viewBox=\"0 0 650 620\" role=\"img\" aria-label=\"XOR decision boundary\">\n",
        "<rect width=\"650\" height=\"620\" fill=\"#f8fafc\"/>\n",
        "<text x=\"70\" y=\"50\" font-family=\"Arial\" font-size=\"24\" font-weight=\"700\">XOR decision boundary dari hidden layer</text>\n",
        "{''.join(cells)}{pt_svg}\n",
        "<text x=\"90\" y=\"580\" font-family=\"Arial\" font-size=\"15\">Hijau = prediksi 1, merah = prediksi 0. Pola ini tidak bisa dibuat satu garis lurus.</text>\n",
        "</svg>''', encoding=\"utf-8\")\n",
        "\n",
        "\n",
        "def main() -> None:\n",
        "    print(\"=== Bab 9 Neural Network Playground ===\")\n",
        "    print(\"sigmoid(0)=\", sigmoid(0))\n",
        "    print(\"ReLU(-3), ReLU(4)=\", relu(-3), relu(4))\n",
        "\n",
        "    print(\"\\nPerceptron AND w=[1,1], b=-1.5\")\n",
        "    for x in [(0, 0), (0, 1), (1, 0), (1, 1)]:\n",
        "        print(x, perceptron(x, [1, 1], -1.5))\n",
        "\n",
        "    print(\"\\nXOR network step activation\")\n",
        "    xor_outputs = []\n",
        "    for x in [(0, 0), (0, 1), (1, 0), (1, 1)]:\n",
        "        y = xor_network(*x)\n",
        "        xor_outputs.append(y)\n",
        "        print(x, y, \"sigmoid_prob=\", round(sigmoid_xor_probability(*x), 3))\n",
        "    assert xor_outputs == [0, 1, 1, 0]\n",
        "\n",
        "    print(\"\\nForward layer example\")\n",
        "    h = forward_layer([2, 3], [[1, -1], [2, 1]], [0, 1], relu)\n",
        "    print(\"h=\", h)\n",
        "\n",
        "    print(\"\\nGradient training linear neuron for y=3x+1\")\n",
        "    xs = [0, 1, 2, 3, 4]\n",
        "    ys = [1, 4, 7, 10, 13]\n",
        "    w, b, history = train_linear_neuron(xs, ys, lr=0.03, epochs=120)\n",
        "    for epoch, loss, ww, bb, gw, gb in history:\n",
        "        print(f\"epoch={epoch:3d} loss={loss:8.4f} w={ww:6.3f} b={bb:6.3f} grad_w={gw:7.3f} grad_b={gb:7.3f}\")\n",
        "    print(\"final approx:\", round(w, 3), round(b, 3))\n",
        "\n",
        "    out = (Path(__file__).resolve().parent if \"__file__\" in globals() else Path.cwd()) / \"outputs\"\n",
        "    write_xor_boundary_svg(out / \"xor_boundary.svg\")\n",
        "    print(\"SVG written:\", out / \"xor_boundary.svg\")\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Jalankan praktikum utama\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "main()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Latihan cepat\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "print(\"XOR manual:\", [xor_network(*x) for x in [(0,0),(0,1),(1,0),(1,1)]])\n",
        "print(\"Gradient example final:\", train_linear_neuron([0,1,2], [1,3,5], lr=0.05, epochs=50)[:2])\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}