Compare commits

..

8 Commits

Author SHA1 Message Date
beastgitea2026 1183d10849 c 2026-06-10 15:43:32 +08:00
beastgitea2026 ebf38476a8 fix: correct class instantiation and add missing show() call in merge_transactions.py
Co-authored-by: aider (ollama/gemma4:12b) <aider@aider.chat>
2026-06-08 10:38:49 +08:00
beastgitea2026 1dbfaebd3a ```json
{
  "response": "The provided Python script has been significantly refactored to include a graphical user interface (GUI) using PyQt5. The script now features a simple window with buttons to select input and output directories, as well as a button to start the merging process. The GUI provides feedback through message boxes and prints statements in the console. The core functionality of merging Excel files remains intact but is now encapsulated within a class for better organization and reusability."
}
```

Co-authored-by: aider (ollama/gemma4:12b) <aider@aider.chat>
2026-06-08 10:37:51 +08:00
beastgitea2026 51386e87e9 fix: remove unused import and correct variable names in merge_transactions.py
Co-authored-by: aider (ollama/gemma4:12b) <aider@aider.chat>
2026-06-08 06:57:59 +08:00
beastgitea2026 d9c6f79a36 fix: 将脚本中的输入路径硬编码为 d:\input
Co-authored-by: aider (ollama/gemma4:12b) <aider@aider.chat>
2026-06-08 06:50:10 +08:00
beastgitea2026 f13e3c66a5 fix(merge_transactions.py): 删除冗余赋值
Co-authored-by: aider (ollama/gemma4:12b) <aider@aider.chat>
2026-06-08 06:48:56 +08:00
beastgitea2026 223000c4f1 refactor(merge_transactions.py): 移除用户输入提示并设置默认路径 2026-06-08 06:48:55 +08:00
beastgitea2026 4a43fbc895 feat: 添加脚本 merge_transactions.py 用于合并指定目录下的 Excel 文件中的“交易流水”工作表,并输出到同一目录下。
Co-authored-by: aider (ollama/gemma4:12b) <aider@aider.chat>
2026-06-08 06:41:26 +08:00
3 changed files with 5254 additions and 360 deletions
+768
View File
@@ -0,0 +1,768 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "153d6f74",
"metadata": {},
"source": [
"# Micrograd — backprop from scratch\n",
"\n",
"A tiny automatic-gradient engine in pure Python — **concept -> code -> Your turn** each step.\n",
"\n",
"This is notebook **00** of the series and the right place to start: it explains what a\n",
"*gradient* and *backpropagation* really are, using single numbers you can follow by hand.\n",
"Every later notebook (the bigram in `01`, the transformer in `06`) calls `loss.backward()`\n",
"and trusts it. Here we build that machinery ourselves so it is never magic."
]
},
{
"cell_type": "markdown",
"id": "aac17ea7",
"metadata": {},
"source": [
"## Prologue — what this notebook does, in plain English\n",
"\n",
"We build a small object called a **`Value`**. A `Value` is just a number that also\n",
"**remembers how it was made** (which other numbers, and which operation: plus, times, ...).\n",
"\n",
"Once numbers remember their own history, the computer can answer one very useful question\n",
"automatically:\n",
"\n",
"> *If I nudge this input a tiny bit, how much does the final answer change?*\n",
"\n",
"That sensitivity is the **gradient**. Computing all those sensitivities in one efficient\n",
"backward sweep is **backpropagation** — the single algorithm that trains essentially every\n",
"neural network, including GPT.\n",
"\n",
"This notebook follows Andrej Karpathy's *\"The spelled-out intro to neural networks and\n",
"backpropagation: building micrograd\"* lecture."
]
},
{
"cell_type": "markdown",
"id": "44dd4b0d",
"metadata": {},
"source": [
"### The whole idea, as a chain of gears\n",
"\n",
"Picture a row of **gears** connected together. You turn the first gear a little; the last\n",
"gear also turns, by an amount that depends on all the gears in between.\n",
"\n",
"- The **forward pass** is turning the first gears and reading the last gear (the output).\n",
"- The **gradient** answers: *if I turn this one gear slightly, how much does the final gear move?*\n",
"- **Backpropagation** is figuring out that answer for **every** gear at once, by walking\n",
" backward from the last gear to the first and multiplying the little ratios along the way\n",
" (that multiplication is the **chain rule**).\n",
"\n",
"A neural network is just a very big gear train. Training = nudging each gear a hair in the\n",
"direction that makes the output less wrong."
]
},
{
"cell_type": "markdown",
"id": "84448625",
"metadata": {},
"source": [
"### 0.1 Imports\n",
"\n",
"Pure Python plus a little NumPy/Matplotlib for the toy dataset and pictures. No deep-learning\n",
"library is needed to build the engine itself — that is the whole point.\n",
"\n",
"**-> Training:** `math` gives us `exp`/`tanh` for activations; `random` initialises weights;\n",
"NumPy and Matplotlib are only for the demo dataset and plots."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e31a52ef",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"import random\n",
"import numpy as np\n",
"\n",
"random.seed(1337)\n",
"np.random.seed(1337)\n",
"\n",
"# Plots are optional: the notebook runs fine without matplotlib (it just skips pictures).\n",
"try:\n",
" import matplotlib.pyplot as plt\n",
" HAS_PLT = True\n",
"except Exception:\n",
" HAS_PLT = False\n",
" print(\"matplotlib not installed -> plots will be skipped (everything else still works)\")\n",
"\n",
"print(\"ready\")"
]
},
{
"cell_type": "markdown",
"id": "3315510f",
"metadata": {},
"source": [
"### 0.2 A tiny dataset — two interleaving moons\n",
"\n",
"Our goal at the end is to train a small network to separate two groups of dots that curl\n",
"around each other (\"two moons\"). Real-life picture: two handfuls of red and blue beads\n",
"mixed in a swirl — can the network learn to draw the boundary between them?\n",
"\n",
"We try scikit-learn's `make_moons`; if it is not installed we generate the same shape with\n",
"NumPy so the notebook stays dependency-light."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "507b4df3",
"metadata": {},
"outputs": [],
"source": [
"def make_moons_fallback(n_samples=100, noise=0.1):\n",
" n = n_samples // 2\n",
" t = np.linspace(0, np.pi, n)\n",
" # outer moon\n",
" x1 = np.stack([np.cos(t), np.sin(t)], axis=1)\n",
" # inner moon, shifted\n",
" x2 = np.stack([1 - np.cos(t), 1 - np.sin(t) - 0.5], axis=1)\n",
" X = np.concatenate([x1, x2], axis=0)\n",
" X += noise * np.random.randn(*X.shape)\n",
" y = np.array([0] * n + [1] * n)\n",
" return X, y\n",
"\n",
"try:\n",
" from sklearn.datasets import make_moons\n",
" X, y = make_moons(n_samples=100, noise=0.1, random_state=1337)\n",
" print(\"using sklearn make_moons\")\n",
"except Exception as e:\n",
" X, y = make_moons_fallback(100, noise=0.1)\n",
" print(\"sklearn not available, using NumPy fallback:\", type(e).__name__)\n",
"\n",
"print(\"X shape:\", X.shape, \" y shape:\", y.shape)\n",
"print(\"first 3 points:\", X[:3].tolist())\n",
"print(\"first 3 labels:\", y[:3].tolist())\n",
"\n",
"if HAS_PLT:\n",
" plt.figure(figsize=(5, 4))\n",
" plt.scatter(X[:, 0], X[:, 1], c=y, s=20, cmap=\"bwr\")\n",
" plt.title(\"two moons — can a tiny net separate the colors?\")\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"id": "0d2fe98b",
"metadata": {},
"source": [
"### 1.1 The `Value` object — a number that remembers\n",
"\n",
"A normal Python float forgets where it came from: once you compute `3.0`, nobody knows it\n",
"was `1.0 + 2.0`. Our `Value` keeps that memory: the numbers that made it (`_prev`) and the\n",
"operation (`_op`).\n",
"\n",
"We start with a **minimal** version that can only do the **forward pass** (no gradients yet),\n",
"just so you can see the \"remembering\" working.\n",
"\n",
"**-> Training:** `_prev` is the list of parent numbers; later, gradients flow backward along\n",
"exactly these links."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eea9bc18",
"metadata": {},
"outputs": [],
"source": [
"class Value:\n",
" def __init__(self, data, _children=(), _op=''):\n",
" self.data = data\n",
" self._prev = set(_children)\n",
" self._op = _op\n",
"\n",
" def __repr__(self):\n",
" return f\"Value(data={self.data})\"\n",
"\n",
" def __add__(self, other):\n",
" return Value(self.data + other.data, (self, other), '+')\n",
"\n",
" def __mul__(self, other):\n",
" return Value(self.data * other.data, (self, other), '*')\n",
"\n",
"\n",
"a = Value(2.0)\n",
"b = Value(-3.0)\n",
"c = Value(10.0)\n",
"e = a * b\n",
"d = e + c\n",
"print(\"d =\", d)\n",
"print(\"d was made by op:\", repr(d._op))\n",
"print(\"d's parents:\", d._prev)"
]
},
{
"cell_type": "markdown",
"id": "e65b3200",
"metadata": {},
"source": [
"### 1.2 Reading the expression graph\n",
"\n",
"`d = a*b + c` is really a little tree: `d` points back to `e` and `c`; `e` points back to\n",
"`a` and `b`. Let's print it as an indented tree so the structure is visible. (No graphviz\n",
"dependency — just recursion.)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a436597",
"metadata": {},
"outputs": [],
"source": [
"def show(v, indent=0):\n",
" print(\" \" * indent + f\"{v.data:.4f} (op={v._op or 'leaf'})\")\n",
" for child in v._prev:\n",
" show(child, indent + 1)\n",
"\n",
"show(d)"
]
},
{
"cell_type": "markdown",
"id": "5ee07490",
"metadata": {},
"source": [
"### 2.1 What a gradient means — nudge and measure\n",
"\n",
"Before any clever math, here is the *definition* of a gradient, done the dumb way: change an\n",
"input by a tiny amount `h`, recompute the output, and see how much it moved.\n",
"\n",
"slope = (output after nudge - output before) / h\n",
"\n",
"Real-life picture: to feel how steep a hill is, take one small step and notice how much your\n",
"height changed. That ratio is the slope."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fbfbee18",
"metadata": {},
"outputs": [],
"source": [
"def f_out(a_, b_, c_):\n",
" return (a_ * b_) + c_ # plain floats\n",
"\n",
"h = 1e-6\n",
"base = f_out(2.0, -3.0, 10.0)\n",
"\n",
"# how sensitive is the output to a?\n",
"da = (f_out(2.0 + h, -3.0, 10.0) - base) / h\n",
"# to b?\n",
"db = (f_out(2.0, -3.0 + h, 10.0) - base) / h\n",
"# to c?\n",
"dc = (f_out(2.0, -3.0, 10.0 + h) - base) / h\n",
"\n",
"print(f\"output = {base}\")\n",
"print(f\"d(out)/da = {da:.4f} (equals b = -3)\")\n",
"print(f\"d(out)/db = {db:.4f} (equals a = 2)\")\n",
"print(f\"d(out)/dc = {dc:.4f} (equals 1)\")"
]
},
{
"cell_type": "markdown",
"id": "d3d3c6e2",
"metadata": {},
"source": [
"### 3.1 The chain rule — multiply the little ratios\n",
"\n",
"The nudge-and-measure trick is correct but slow: one re-run per input. Real networks have\n",
"millions of inputs. The **chain rule** gets every sensitivity in *one* backward pass.\n",
"\n",
"The rule, in words: *if a affects e, and e affects d, then a's effect on d is the product of\n",
"the two local effects.*\n",
"\n",
"d(d)/d(a) = d(d)/d(e) * d(e)/d(a)\n",
"\n",
"Each operation knows its own **local** derivative:\n",
"\n",
"- for `+`: output changes 1-for-1 with each input, so the gradient just **passes through**.\n",
"- for `*`: each input's local derivative is **the other input**.\n",
"\n",
"Backprop = start with gradient 1.0 at the output, then walk backward multiplying by each\n",
"local derivative."
]
},
{
"cell_type": "markdown",
"id": "0da2822d",
"metadata": {},
"source": [
"### 4.1 Adding gradients and local backward rules\n",
"\n",
"Now the full `Value`. Each operation also defines a `_backward()` that says how to push the\n",
"output's gradient onto its inputs. Note we **accumulate** (`+=`) gradients, because one value\n",
"can feed into several places.\n",
"\n",
"We add `+`, `*`, `**` (power), `tanh`, and `exp` — enough to build a neural net. We also add\n",
"convenience operators (`-`, `/`, right-hand versions) so expressions read naturally.\n",
"\n",
"**-> Training:** `tanh` is the neuron's \"squashing\" activation; its local derivative is\n",
"`1 - tanh(x)^2`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9433f6b8",
"metadata": {},
"outputs": [],
"source": [
"class Value:\n",
" def __init__(self, data, _children=(), _op='', label=''):\n",
" self.data = data\n",
" self.grad = 0.0\n",
" self._backward = lambda: None\n",
" self._prev = set(_children)\n",
" self._op = _op\n",
" self.label = label\n",
"\n",
" def __repr__(self):\n",
" return f\"Value(data={self.data:.4f}, grad={self.grad:.4f})\"\n",
"\n",
" def __add__(self, other):\n",
" other = other if isinstance(other, Value) else Value(other)\n",
" out = Value(self.data + other.data, (self, other), '+')\n",
" def _backward():\n",
" self.grad += 1.0 * out.grad\n",
" other.grad += 1.0 * out.grad\n",
" out._backward = _backward\n",
" return out\n",
"\n",
" def __mul__(self, other):\n",
" other = other if isinstance(other, Value) else Value(other)\n",
" out = Value(self.data * other.data, (self, other), '*')\n",
" def _backward():\n",
" self.grad += other.data * out.grad\n",
" other.grad += self.data * out.grad\n",
" out._backward = _backward\n",
" return out\n",
"\n",
" def __pow__(self, other):\n",
" assert isinstance(other, (int, float)), \"only int/float powers\"\n",
" out = Value(self.data ** other, (self,), f'**{other}')\n",
" def _backward():\n",
" self.grad += other * (self.data ** (other - 1)) * out.grad\n",
" out._backward = _backward\n",
" return out\n",
"\n",
" def tanh(self):\n",
" x = self.data\n",
" t = (math.exp(2 * x) - 1) / (math.exp(2 * x) + 1)\n",
" out = Value(t, (self,), 'tanh')\n",
" def _backward():\n",
" self.grad += (1 - t ** 2) * out.grad\n",
" out._backward = _backward\n",
" return out\n",
"\n",
" def exp(self):\n",
" out = Value(math.exp(self.data), (self,), 'exp')\n",
" def _backward():\n",
" self.grad += out.data * out.grad\n",
" out._backward = _backward\n",
" return out\n",
"\n",
" # convenience\n",
" def __neg__(self):\n",
" return self * -1\n",
"\n",
" def __sub__(self, other):\n",
" return self + (-other if isinstance(other, Value) else Value(-other))\n",
"\n",
" def __radd__(self, other):\n",
" return self + other\n",
"\n",
" def __rmul__(self, other):\n",
" return self * other\n",
"\n",
" def __truediv__(self, other):\n",
" other = other if isinstance(other, Value) else Value(other)\n",
" return self * other ** -1\n",
"\n",
" def backward(self):\n",
" # build topological order so children come before parents\n",
" topo = []\n",
" visited = set()\n",
" def build_topo(v):\n",
" if v not in visited:\n",
" visited.add(v)\n",
" for child in v._prev:\n",
" build_topo(child)\n",
" topo.append(v)\n",
" build_topo(self)\n",
" # seed the output gradient, then sweep backward\n",
" self.grad = 1.0\n",
" for node in reversed(topo):\n",
" node._backward()\n",
"\n",
"\n",
"print(\"full Value class ready\")"
]
},
{
"cell_type": "markdown",
"id": "7f14c265",
"metadata": {},
"source": [
"### 4.2 One backward pass on the tiny expression\n",
"\n",
"Rebuild `d = a*b + c` with the full class, call `d.backward()` once, and read the gradients.\n",
"They should match the nudge-and-measure numbers from section 2.1 (`a.grad = b = -3`,\n",
"`b.grad = a = 2`, `c.grad = 1`)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d1e739c",
"metadata": {},
"outputs": [],
"source": [
"a = Value(2.0, label='a')\n",
"b = Value(-3.0, label='b')\n",
"c = Value(10.0, label='c')\n",
"e = a * b\n",
"d = e + c\n",
"\n",
"d.backward()\n",
"\n",
"print(\"d =\", d.data)\n",
"print(\"a.grad =\", a.grad, \" (expect -3)\")\n",
"print(\"b.grad =\", b.grad, \" (expect 2)\")\n",
"print(\"c.grad =\", c.grad, \" (expect 1)\")"
]
},
{
"cell_type": "markdown",
"id": "1ac38e12",
"metadata": {},
"source": [
"### 4.3 Trust but verify — numeric gradient check\n",
"\n",
"A good habit: confirm the analytic gradient (from `backward()`) matches the slow\n",
"nudge-and-measure gradient. If they agree, the engine is correct."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe4b711e",
"metadata": {},
"outputs": [],
"source": [
"def numeric_grad(f, inputs, i, h=1e-6):\n",
" base = f(inputs)\n",
" bumped = list(inputs)\n",
" bumped[i] += h\n",
" return (f(bumped) - base) / h\n",
"\n",
"f = lambda v: (v[0] * v[1]) + v[2]\n",
"inputs = [2.0, -3.0, 10.0]\n",
"\n",
"a = Value(2.0); b = Value(-3.0); c = Value(10.0)\n",
"(a * b + c).backward()\n",
"\n",
"for i, (name, val) in enumerate(zip(\"abc\", (a, b, c))):\n",
" ng = numeric_grad(f, inputs, i)\n",
" print(f\"{name}: analytic={val.grad:+.4f} numeric={ng:+.4f} match={abs(val.grad-ng)<1e-4}\")"
]
},
{
"cell_type": "markdown",
"id": "6518159c",
"metadata": {},
"source": [
"**Your turn 4** — Build `g = (a*b + c).tanh()` with `a=0.5, b=2.0, c=-1.0`, call\n",
"`g.backward()`, and check `a.grad` against a numeric nudge. (Hint: reuse `numeric_grad`\n",
"with `f = lambda v: math.tanh(v[0]*v[1] + v[2])`.)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6bea75d",
"metadata": {},
"outputs": [],
"source": [
"# Your turn 4 — fill in and run\n",
"a = Value(0.5); b = Value(2.0); c = Value(-1.0)\n",
"g = (a * b + c).tanh()\n",
"g.backward()\n",
"\n",
"f = lambda v: math.tanh(v[0]*v[1] + v[2])\n",
"ng_a = numeric_grad(f, [0.5, 2.0, -1.0], 0)\n",
"print(f\"a.grad analytic = {a.grad:+.4f} numeric = {ng_a:+.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "fef7ef8b",
"metadata": {},
"source": [
"### 5.1 Neuron -> Layer -> MLP, all from `Value`\n",
"\n",
"A **neuron** computes `tanh(w . x + b)` — a weighted vote of its inputs, squashed to (-1, 1).\n",
"A **layer** is a list of neurons. An **MLP** (multi-layer perceptron) is a stack of layers.\n",
"\n",
"Real-life picture: each neuron is a tiny committee member weighing the evidence; layers stack\n",
"committees so later ones judge the opinions of earlier ones.\n",
"\n",
"**-> Training:** `parameters()` returns every weight and bias — exactly the knobs we nudge."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d260369",
"metadata": {},
"outputs": [],
"source": [
"class Neuron:\n",
" def __init__(self, nin):\n",
" self.w = [Value(random.uniform(-1, 1)) for _ in range(nin)]\n",
" self.b = Value(random.uniform(-1, 1))\n",
"\n",
" def __call__(self, x):\n",
" act = sum((wi * xi for wi, xi in zip(self.w, x)), self.b)\n",
" return act.tanh()\n",
"\n",
" def parameters(self):\n",
" return self.w + [self.b]\n",
"\n",
"\n",
"class Layer:\n",
" def __init__(self, nin, nout):\n",
" self.neurons = [Neuron(nin) for _ in range(nout)]\n",
"\n",
" def __call__(self, x):\n",
" outs = [n(x) for n in self.neurons]\n",
" return outs[0] if len(outs) == 1 else outs\n",
"\n",
" def parameters(self):\n",
" return [p for n in self.neurons for p in n.parameters()]\n",
"\n",
"\n",
"class MLP:\n",
" def __init__(self, nin, nouts):\n",
" sizes = [nin] + nouts\n",
" self.layers = [Layer(sizes[i], sizes[i + 1]) for i in range(len(nouts))]\n",
"\n",
" def __call__(self, x):\n",
" for layer in self.layers:\n",
" x = layer(x)\n",
" return x\n",
"\n",
" def parameters(self):\n",
" return [p for layer in self.layers for p in layer.parameters()]\n",
"\n",
"\n",
"net = MLP(2, [8, 8, 1]) # 2 inputs -> 8 -> 8 -> 1 output\n",
"print(\"parameter count:\", len(net.parameters()))\n",
"print(\"one prediction:\", net([Value(0.5), Value(-0.2)]))"
]
},
{
"cell_type": "markdown",
"id": "fc0b5b7b",
"metadata": {},
"source": [
"### 6.1 Train the MLP on the two moons\n",
"\n",
"Loop the four familiar steps:\n",
"\n",
"1. **forward** — predict on every point\n",
"2. **loss** — how wrong (here: mean-squared error against labels mapped to -1 / +1)\n",
"3. **backward** — `loss.backward()` fills every parameter's `.grad`\n",
"4. **update** — nudge each parameter a little **against** its gradient (downhill)\n",
"\n",
"Watch the loss fall. (Pure-Python micrograd is slow, so we keep the net and step count small.)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de95ef11",
"metadata": {},
"outputs": [],
"source": [
"net = MLP(2, [8, 8, 1])\n",
"\n",
"# map labels {0,1} -> {-1,+1} to match tanh output range\n",
"ys = [1.0 if yi == 1 else -1.0 for yi in y]\n",
"Xv = [[Value(float(xi[0])), Value(float(xi[1]))] for xi in X]\n",
"\n",
"lr = 0.1\n",
"for step in range(60):\n",
" # forward + MSE loss\n",
" preds = [net(xrow) for xrow in Xv]\n",
" loss = sum(((p - yt) ** 2 for p, yt in zip(preds, ys)), Value(0.0))\n",
" loss = loss * (1.0 / len(ys))\n",
"\n",
" # backward (reset grads first)\n",
" for p in net.parameters():\n",
" p.grad = 0.0\n",
" loss.backward()\n",
"\n",
" # update\n",
" for p in net.parameters():\n",
" p.data -= lr * p.grad\n",
"\n",
" if step % 10 == 0 or step == 59:\n",
" # accuracy\n",
" acc = sum((1 if (p.data > 0) == (yt > 0) else 0) for p, yt in zip(preds, ys)) / len(ys)\n",
" print(f\"step {step:3d} loss {loss.data:.4f} acc {acc:.2%}\")"
]
},
{
"cell_type": "markdown",
"id": "9cb30391",
"metadata": {},
"source": [
"### 6.2 See the boundary it learned\n",
"\n",
"Evaluate the trained net across a grid of points and color the regions. The swirl between the\n",
"two moons should now be split by a curved boundary — the network \"drew the line.\"\n",
"\n",
"(Coarse grid for speed; each grid point is a full forward pass through micrograd.)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee39626a",
"metadata": {},
"outputs": [],
"source": [
"if HAS_PLT:\n",
" xmin, xmax = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5\n",
" ymin, ymax = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5\n",
" xs = np.linspace(xmin, xmax, 30)\n",
" ys_grid = np.linspace(ymin, ymax, 30)\n",
"\n",
" Z = np.zeros((len(ys_grid), len(xs)))\n",
" for i, gy in enumerate(ys_grid):\n",
" for j, gx in enumerate(xs):\n",
" out = net([Value(float(gx)), Value(float(gy))])\n",
" Z[i, j] = out.data\n",
"\n",
" plt.figure(figsize=(5, 4))\n",
" plt.contourf(xs, ys_grid, Z, levels=[-1e9, 0, 1e9], cmap=\"bwr\", alpha=0.3)\n",
" plt.scatter(X[:, 0], X[:, 1], c=y, s=20, cmap=\"bwr\")\n",
" plt.title(\"decision boundary learned by the micrograd MLP\")\n",
" plt.show()\n",
"else:\n",
" print(\"matplotlib missing - skipping the boundary plot (training above still ran)\")"
]
},
{
"cell_type": "markdown",
"id": "b00ac0fd",
"metadata": {},
"source": [
"**Your turn 6** — Try `MLP(2, [16, 16, 1])` or a different learning rate / step count and\n",
"re-run 6.1 and 6.2. Does a bigger net reach higher accuracy? Does too-large a learning rate\n",
"make the loss bounce instead of fall?"
]
},
{
"cell_type": "markdown",
"id": "48623e88",
"metadata": {},
"source": [
"### 7.1 Bridge to PyTorch — same gradients, industrial engine\n",
"\n",
"PyTorch is micrograd scaled up to fast tensors. The *idea* is identical: build an expression,\n",
"call `.backward()`, read `.grad`. Here we redo the tiny `a*b + c` (then a tanh) in PyTorch and\n",
"confirm the gradients match what our engine produced.\n",
"\n",
"**-> Training:** in notebooks `01` and `06` we let PyTorch's autograd do exactly this — now you\n",
"know what it is doing under the hood."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e6c3140e",
"metadata": {},
"outputs": [],
"source": [
"# On some Windows setups torch and numpy ship duplicate OpenMP DLLs; this avoids a clash.\n",
"import os\n",
"os.environ.setdefault(\"KMP_DUPLICATE_LIB_OK\", \"TRUE\")\n",
"\n",
"try:\n",
" import torch\n",
"\n",
" at = torch.tensor([0.5], requires_grad=True)\n",
" bt = torch.tensor([2.0], requires_grad=True)\n",
" ct = torch.tensor([-1.0], requires_grad=True)\n",
"\n",
" gt = torch.tanh(at * bt + ct)\n",
" gt.backward()\n",
" print(\"PyTorch: a.grad =\", at.grad.item())\n",
"except OSError as e:\n",
" print(\"PyTorch could not load in this environment:\", e)\n",
" print(\"(That is fine here - the micrograd result below is the point.)\")\n",
"\n",
"# our engine, same expression\n",
"a = Value(0.5); b = Value(2.0); c = Value(-1.0)\n",
"(a * b + c).tanh().backward()\n",
"print(\"micrograd: a.grad =\", a.grad)"
]
},
{
"cell_type": "markdown",
"id": "49c1ebf8",
"metadata": {},
"source": [
"## What's next\n",
"\n",
"You built the engine that powers all of deep learning:\n",
"\n",
"- a **number that remembers** its history (`Value`)\n",
"- **backpropagation** via the chain rule (`backward()`)\n",
"- a small **MLP** trained by nudging weights downhill\n",
"\n",
"Where this leads in the series:\n",
"\n",
"- `01_build_gpt.ipynb` — language modeling basics (the bigram), now that backprop is no longer\n",
" mysterious.\n",
"- `02`-`05` — scale these same ideas up with PyTorch on real character data (`names.txt`).\n",
"- `06_build_gpt_attention.ipynb` — the transformer, where `loss.backward()` is doing exactly\n",
" what you built here, just across millions of `Value`-like nodes.\n",
"\n",
"**Checklist**\n",
"- [ ] A `Value` records data, parents, and operation\n",
"- [ ] Each op defines a local `_backward`\n",
"- [ ] `backward()` = topological order + chain rule\n",
"- [ ] Neuron / Layer / MLP built from `Value`\n",
"- [ ] Trained on two moons; boundary learned\n",
"- [ ] Confirmed gradients match PyTorch"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv (3.12.10)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+2422 -160
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff