Notebook: hiker-in-the-fog gradient widget (trees, marks, slope)
Browse files- tiny_llm_from_scratch.ipynb +199 -45
tiny_llm_from_scratch.ipynb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"cells": [
|
| 3 |
{
|
| 4 |
"cell_type": "markdown",
|
| 5 |
-
"id": "
|
| 6 |
"metadata": {},
|
| 7 |
"source": [
|
| 8 |
"# Tiny Storyteller: building a language model from scratch\n",
|
|
@@ -38,7 +38,7 @@
|
|
| 38 |
},
|
| 39 |
{
|
| 40 |
"cell_type": "markdown",
|
| 41 |
-
"id": "
|
| 42 |
"metadata": {},
|
| 43 |
"source": [
|
| 44 |
"# Before any code: the story of what we are about to build\n",
|
|
@@ -168,7 +168,7 @@
|
|
| 168 |
},
|
| 169 |
{
|
| 170 |
"cell_type": "markdown",
|
| 171 |
-
"id": "
|
| 172 |
"metadata": {},
|
| 173 |
"source": [
|
| 174 |
"## Live flow diagram\n",
|
|
@@ -183,7 +183,7 @@
|
|
| 183 |
{
|
| 184 |
"cell_type": "code",
|
| 185 |
"execution_count": null,
|
| 186 |
-
"id": "
|
| 187 |
"metadata": {},
|
| 188 |
"outputs": [],
|
| 189 |
"source": [
|
|
@@ -308,7 +308,7 @@
|
|
| 308 |
},
|
| 309 |
{
|
| 310 |
"cell_type": "markdown",
|
| 311 |
-
"id": "
|
| 312 |
"metadata": {},
|
| 313 |
"source": [
|
| 314 |
"## 0. Setup\n",
|
|
@@ -318,7 +318,7 @@
|
|
| 318 |
{
|
| 319 |
"cell_type": "code",
|
| 320 |
"execution_count": null,
|
| 321 |
-
"id": "
|
| 322 |
"metadata": {},
|
| 323 |
"outputs": [],
|
| 324 |
"source": [
|
|
@@ -328,7 +328,7 @@
|
|
| 328 |
{
|
| 329 |
"cell_type": "code",
|
| 330 |
"execution_count": null,
|
| 331 |
-
"id": "
|
| 332 |
"metadata": {},
|
| 333 |
"outputs": [],
|
| 334 |
"source": [
|
|
@@ -345,7 +345,7 @@
|
|
| 345 |
},
|
| 346 |
{
|
| 347 |
"cell_type": "markdown",
|
| 348 |
-
"id": "
|
| 349 |
"metadata": {},
|
| 350 |
"source": [
|
| 351 |
"## 1. Tensors and gradients: the two building blocks\n",
|
|
@@ -364,7 +364,7 @@
|
|
| 364 |
{
|
| 365 |
"cell_type": "code",
|
| 366 |
"execution_count": null,
|
| 367 |
-
"id": "
|
| 368 |
"metadata": {},
|
| 369 |
"outputs": [],
|
| 370 |
"source": [
|
|
@@ -407,7 +407,7 @@
|
|
| 407 |
},
|
| 408 |
{
|
| 409 |
"cell_type": "markdown",
|
| 410 |
-
"id": "
|
| 411 |
"metadata": {},
|
| 412 |
"source": [
|
| 413 |
"### How matrix multiplication actually happens\n",
|
|
@@ -422,7 +422,7 @@
|
|
| 422 |
{
|
| 423 |
"cell_type": "code",
|
| 424 |
"execution_count": null,
|
| 425 |
-
"id": "
|
| 426 |
"metadata": {},
|
| 427 |
"outputs": [],
|
| 428 |
"source": [
|
|
@@ -479,7 +479,7 @@
|
|
| 479 |
{
|
| 480 |
"cell_type": "code",
|
| 481 |
"execution_count": null,
|
| 482 |
-
"id": "
|
| 483 |
"metadata": {},
|
| 484 |
"outputs": [],
|
| 485 |
"source": [
|
|
@@ -492,7 +492,7 @@
|
|
| 492 |
},
|
| 493 |
{
|
| 494 |
"cell_type": "markdown",
|
| 495 |
-
"id": "
|
| 496 |
"metadata": {},
|
| 497 |
"source": [
|
| 498 |
"A **gradient** tells us how much the loss would change if we nudged a weight.\n",
|
|
@@ -510,7 +510,7 @@
|
|
| 510 |
{
|
| 511 |
"cell_type": "code",
|
| 512 |
"execution_count": null,
|
| 513 |
-
"id": "
|
| 514 |
"metadata": {},
|
| 515 |
"outputs": [],
|
| 516 |
"source": [
|
|
@@ -548,10 +548,164 @@
|
|
| 548 |
"\"\"\"))"
|
| 549 |
]
|
| 550 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
{
|
| 552 |
"cell_type": "code",
|
| 553 |
"execution_count": null,
|
| 554 |
-
"id": "
|
| 555 |
"metadata": {},
|
| 556 |
"outputs": [],
|
| 557 |
"source": [
|
|
@@ -569,7 +723,7 @@
|
|
| 569 |
},
|
| 570 |
{
|
| 571 |
"cell_type": "markdown",
|
| 572 |
-
"id": "
|
| 573 |
"metadata": {},
|
| 574 |
"source": [
|
| 575 |
"## 2. Data: TinyStories\n",
|
|
@@ -584,7 +738,7 @@
|
|
| 584 |
{
|
| 585 |
"cell_type": "code",
|
| 586 |
"execution_count": null,
|
| 587 |
-
"id": "
|
| 588 |
"metadata": {},
|
| 589 |
"outputs": [],
|
| 590 |
"source": [
|
|
@@ -602,7 +756,7 @@
|
|
| 602 |
},
|
| 603 |
{
|
| 604 |
"cell_type": "markdown",
|
| 605 |
-
"id": "
|
| 606 |
"metadata": {},
|
| 607 |
"source": [
|
| 608 |
"## 3. The simplest language model: a bigram\n",
|
|
@@ -622,7 +776,7 @@
|
|
| 622 |
{
|
| 623 |
"cell_type": "code",
|
| 624 |
"execution_count": null,
|
| 625 |
-
"id": "
|
| 626 |
"metadata": {},
|
| 627 |
"outputs": [],
|
| 628 |
"source": [
|
|
@@ -653,7 +807,7 @@
|
|
| 653 |
{
|
| 654 |
"cell_type": "code",
|
| 655 |
"execution_count": null,
|
| 656 |
-
"id": "
|
| 657 |
"metadata": {},
|
| 658 |
"outputs": [],
|
| 659 |
"source": [
|
|
@@ -673,7 +827,7 @@
|
|
| 673 |
{
|
| 674 |
"cell_type": "code",
|
| 675 |
"execution_count": null,
|
| 676 |
-
"id": "
|
| 677 |
"metadata": {},
|
| 678 |
"outputs": [],
|
| 679 |
"source": [
|
|
@@ -722,7 +876,7 @@
|
|
| 722 |
},
|
| 723 |
{
|
| 724 |
"cell_type": "markdown",
|
| 725 |
-
"id": "
|
| 726 |
"metadata": {},
|
| 727 |
"source": [
|
| 728 |
"It learned *something* (the loss dropped, and it produces word-ish shapes with spaces),\n",
|
|
@@ -732,7 +886,7 @@
|
|
| 732 |
},
|
| 733 |
{
|
| 734 |
"cell_type": "markdown",
|
| 735 |
-
"id": "
|
| 736 |
"metadata": {},
|
| 737 |
"source": [
|
| 738 |
"## 4. A real tokenizer: byte-pair encoding (BPE)\n",
|
|
@@ -750,7 +904,7 @@
|
|
| 750 |
{
|
| 751 |
"cell_type": "code",
|
| 752 |
"execution_count": null,
|
| 753 |
-
"id": "
|
| 754 |
"metadata": {},
|
| 755 |
"outputs": [],
|
| 756 |
"source": [
|
|
@@ -780,7 +934,7 @@
|
|
| 780 |
{
|
| 781 |
"cell_type": "code",
|
| 782 |
"execution_count": null,
|
| 783 |
-
"id": "
|
| 784 |
"metadata": {},
|
| 785 |
"outputs": [],
|
| 786 |
"source": [
|
|
@@ -806,7 +960,7 @@
|
|
| 806 |
{
|
| 807 |
"cell_type": "code",
|
| 808 |
"execution_count": null,
|
| 809 |
-
"id": "
|
| 810 |
"metadata": {},
|
| 811 |
"outputs": [],
|
| 812 |
"source": [
|
|
@@ -820,7 +974,7 @@
|
|
| 820 |
},
|
| 821 |
{
|
| 822 |
"cell_type": "markdown",
|
| 823 |
-
"id": "
|
| 824 |
"metadata": {},
|
| 825 |
"source": [
|
| 826 |
"## 5. Attention, by hand\n",
|
|
@@ -841,7 +995,7 @@
|
|
| 841 |
{
|
| 842 |
"cell_type": "code",
|
| 843 |
"execution_count": null,
|
| 844 |
-
"id": "
|
| 845 |
"metadata": {},
|
| 846 |
"outputs": [],
|
| 847 |
"source": [
|
|
@@ -865,7 +1019,7 @@
|
|
| 865 |
},
|
| 866 |
{
|
| 867 |
"cell_type": "markdown",
|
| 868 |
-
"id": "
|
| 869 |
"metadata": {},
|
| 870 |
"source": [
|
| 871 |
"### The KV cache, in twelve lines\n",
|
|
@@ -879,7 +1033,7 @@
|
|
| 879 |
{
|
| 880 |
"cell_type": "code",
|
| 881 |
"execution_count": null,
|
| 882 |
-
"id": "
|
| 883 |
"metadata": {},
|
| 884 |
"outputs": [],
|
| 885 |
"source": [
|
|
@@ -909,7 +1063,7 @@
|
|
| 909 |
},
|
| 910 |
{
|
| 911 |
"cell_type": "markdown",
|
| 912 |
-
"id": "
|
| 913 |
"metadata": {},
|
| 914 |
"source": [
|
| 915 |
"## 6. A tiny GPT\n",
|
|
@@ -932,7 +1086,7 @@
|
|
| 932 |
{
|
| 933 |
"cell_type": "code",
|
| 934 |
"execution_count": null,
|
| 935 |
-
"id": "
|
| 936 |
"metadata": {},
|
| 937 |
"outputs": [],
|
| 938 |
"source": [
|
|
@@ -976,7 +1130,7 @@
|
|
| 976 |
{
|
| 977 |
"cell_type": "code",
|
| 978 |
"execution_count": null,
|
| 979 |
-
"id": "
|
| 980 |
"metadata": {},
|
| 981 |
"outputs": [],
|
| 982 |
"source": [
|
|
@@ -1087,7 +1241,7 @@
|
|
| 1087 |
},
|
| 1088 |
{
|
| 1089 |
"cell_type": "markdown",
|
| 1090 |
-
"id": "
|
| 1091 |
"metadata": {},
|
| 1092 |
"source": [
|
| 1093 |
"## 7. Training\n",
|
|
@@ -1109,7 +1263,7 @@
|
|
| 1109 |
{
|
| 1110 |
"cell_type": "code",
|
| 1111 |
"execution_count": null,
|
| 1112 |
-
"id": "
|
| 1113 |
"metadata": {},
|
| 1114 |
"outputs": [],
|
| 1115 |
"source": [
|
|
@@ -1151,7 +1305,7 @@
|
|
| 1151 |
{
|
| 1152 |
"cell_type": "code",
|
| 1153 |
"execution_count": null,
|
| 1154 |
-
"id": "
|
| 1155 |
"metadata": {},
|
| 1156 |
"outputs": [],
|
| 1157 |
"source": [
|
|
@@ -1218,7 +1372,7 @@
|
|
| 1218 |
},
|
| 1219 |
{
|
| 1220 |
"cell_type": "markdown",
|
| 1221 |
-
"id": "
|
| 1222 |
"metadata": {},
|
| 1223 |
"source": [
|
| 1224 |
"**How to read the numbers.** The loss is the average \"surprise\" per token. *Perplexity* is\n",
|
|
@@ -1228,7 +1382,7 @@
|
|
| 1228 |
},
|
| 1229 |
{
|
| 1230 |
"cell_type": "markdown",
|
| 1231 |
-
"id": "
|
| 1232 |
"metadata": {},
|
| 1233 |
"source": [
|
| 1234 |
"## 8. Generate stories: the use case\n",
|
|
@@ -1240,7 +1394,7 @@
|
|
| 1240 |
{
|
| 1241 |
"cell_type": "code",
|
| 1242 |
"execution_count": null,
|
| 1243 |
-
"id": "
|
| 1244 |
"metadata": {},
|
| 1245 |
"outputs": [],
|
| 1246 |
"source": [
|
|
@@ -1257,7 +1411,7 @@
|
|
| 1257 |
},
|
| 1258 |
{
|
| 1259 |
"cell_type": "markdown",
|
| 1260 |
-
"id": "
|
| 1261 |
"metadata": {},
|
| 1262 |
"source": [
|
| 1263 |
"## 9. Save and publish on the Hugging Face Hub\n",
|
|
@@ -1273,7 +1427,7 @@
|
|
| 1273 |
{
|
| 1274 |
"cell_type": "code",
|
| 1275 |
"execution_count": null,
|
| 1276 |
-
"id": "
|
| 1277 |
"metadata": {},
|
| 1278 |
"outputs": [],
|
| 1279 |
"source": [
|
|
@@ -1314,7 +1468,7 @@
|
|
| 1314 |
{
|
| 1315 |
"cell_type": "code",
|
| 1316 |
"execution_count": null,
|
| 1317 |
-
"id": "
|
| 1318 |
"metadata": {},
|
| 1319 |
"outputs": [],
|
| 1320 |
"source": [
|
|
@@ -1335,7 +1489,7 @@
|
|
| 1335 |
},
|
| 1336 |
{
|
| 1337 |
"cell_type": "markdown",
|
| 1338 |
-
"id": "
|
| 1339 |
"metadata": {},
|
| 1340 |
"source": [
|
| 1341 |
"## Loading it back later\n",
|
|
@@ -1346,7 +1500,7 @@
|
|
| 1346 |
{
|
| 1347 |
"cell_type": "code",
|
| 1348 |
"execution_count": null,
|
| 1349 |
-
"id": "
|
| 1350 |
"metadata": {},
|
| 1351 |
"outputs": [],
|
| 1352 |
"source": [
|
|
@@ -1367,7 +1521,7 @@
|
|
| 1367 |
},
|
| 1368 |
{
|
| 1369 |
"cell_type": "markdown",
|
| 1370 |
-
"id": "
|
| 1371 |
"metadata": {},
|
| 1372 |
"source": [
|
| 1373 |
"## What we built, in one paragraph\n",
|
|
|
|
| 2 |
"cells": [
|
| 3 |
{
|
| 4 |
"cell_type": "markdown",
|
| 5 |
+
"id": "4d281dd0",
|
| 6 |
"metadata": {},
|
| 7 |
"source": [
|
| 8 |
"# Tiny Storyteller: building a language model from scratch\n",
|
|
|
|
| 38 |
},
|
| 39 |
{
|
| 40 |
"cell_type": "markdown",
|
| 41 |
+
"id": "f9396768",
|
| 42 |
"metadata": {},
|
| 43 |
"source": [
|
| 44 |
"# Before any code: the story of what we are about to build\n",
|
|
|
|
| 168 |
},
|
| 169 |
{
|
| 170 |
"cell_type": "markdown",
|
| 171 |
+
"id": "197e5a08",
|
| 172 |
"metadata": {},
|
| 173 |
"source": [
|
| 174 |
"## Live flow diagram\n",
|
|
|
|
| 183 |
{
|
| 184 |
"cell_type": "code",
|
| 185 |
"execution_count": null,
|
| 186 |
+
"id": "4afe7ae1",
|
| 187 |
"metadata": {},
|
| 188 |
"outputs": [],
|
| 189 |
"source": [
|
|
|
|
| 308 |
},
|
| 309 |
{
|
| 310 |
"cell_type": "markdown",
|
| 311 |
+
"id": "02ff5701",
|
| 312 |
"metadata": {},
|
| 313 |
"source": [
|
| 314 |
"## 0. Setup\n",
|
|
|
|
| 318 |
{
|
| 319 |
"cell_type": "code",
|
| 320 |
"execution_count": null,
|
| 321 |
+
"id": "b52205e8",
|
| 322 |
"metadata": {},
|
| 323 |
"outputs": [],
|
| 324 |
"source": [
|
|
|
|
| 328 |
{
|
| 329 |
"cell_type": "code",
|
| 330 |
"execution_count": null,
|
| 331 |
+
"id": "bb2258ba",
|
| 332 |
"metadata": {},
|
| 333 |
"outputs": [],
|
| 334 |
"source": [
|
|
|
|
| 345 |
},
|
| 346 |
{
|
| 347 |
"cell_type": "markdown",
|
| 348 |
+
"id": "86f68410",
|
| 349 |
"metadata": {},
|
| 350 |
"source": [
|
| 351 |
"## 1. Tensors and gradients: the two building blocks\n",
|
|
|
|
| 364 |
{
|
| 365 |
"cell_type": "code",
|
| 366 |
"execution_count": null,
|
| 367 |
+
"id": "fc4c7ffb",
|
| 368 |
"metadata": {},
|
| 369 |
"outputs": [],
|
| 370 |
"source": [
|
|
|
|
| 407 |
},
|
| 408 |
{
|
| 409 |
"cell_type": "markdown",
|
| 410 |
+
"id": "e6a14516",
|
| 411 |
"metadata": {},
|
| 412 |
"source": [
|
| 413 |
"### How matrix multiplication actually happens\n",
|
|
|
|
| 422 |
{
|
| 423 |
"cell_type": "code",
|
| 424 |
"execution_count": null,
|
| 425 |
+
"id": "3980d801",
|
| 426 |
"metadata": {},
|
| 427 |
"outputs": [],
|
| 428 |
"source": [
|
|
|
|
| 479 |
{
|
| 480 |
"cell_type": "code",
|
| 481 |
"execution_count": null,
|
| 482 |
+
"id": "11d147bf",
|
| 483 |
"metadata": {},
|
| 484 |
"outputs": [],
|
| 485 |
"source": [
|
|
|
|
| 492 |
},
|
| 493 |
{
|
| 494 |
"cell_type": "markdown",
|
| 495 |
+
"id": "91129a59",
|
| 496 |
"metadata": {},
|
| 497 |
"source": [
|
| 498 |
"A **gradient** tells us how much the loss would change if we nudged a weight.\n",
|
|
|
|
| 510 |
{
|
| 511 |
"cell_type": "code",
|
| 512 |
"execution_count": null,
|
| 513 |
+
"id": "45ad3be9",
|
| 514 |
"metadata": {},
|
| 515 |
"outputs": [],
|
| 516 |
"source": [
|
|
|
|
| 548 |
"\"\"\"))"
|
| 549 |
]
|
| 550 |
},
|
| 551 |
+
{
|
| 552 |
+
"cell_type": "markdown",
|
| 553 |
+
"id": "30398c6f",
|
| 554 |
+
"metadata": {},
|
| 555 |
+
"source": [
|
| 556 |
+
"### The hiker, the trees, and the marks on the map\n",
|
| 557 |
+
"\n",
|
| 558 |
+
"Here is the same idea as a story, because this is the part people picture wrongly.\n",
|
| 559 |
+
"\n",
|
| 560 |
+
"**The hill is the loss.** Height is \"how wrong the model is right now\". The valley floor,\n",
|
| 561 |
+
"the flag, is the set of weights that makes the fewest mistakes. The hiker's position *is*\n",
|
| 562 |
+
"the model's weights. One step is one weight update.\n",
|
| 563 |
+
"\n",
|
| 564 |
+
"**A tree is a step that made things worse.** The hiker tried a direction and the loss went\n",
|
| 565 |
+
"up. The hiker steps back, marks the spot with a red X, and corrects, say, to the left.\n",
|
| 566 |
+
"That correction is the gradient doing its job: after a bad step the slope under the feet\n",
|
| 567 |
+
"points back the other way.\n",
|
| 568 |
+
"\n",
|
| 569 |
+
"**What the gradient adds to this picture.** A real hiker learns a tree is there by bumping\n",
|
| 570 |
+
"into it. The gradient is better than that. It is the slope under the feet, felt *before*\n",
|
| 571 |
+
"stepping. Autograd feels the slope for all 5.9 million weights at once and says \"down is\n",
|
| 572 |
+
"this way\" without needing to try the wrong way first. Most of the time the hiker never\n",
|
| 573 |
+
"touches the tree at all. Guessing would mean testing 5.9 million directions one at a time.\n",
|
| 574 |
+
"\n",
|
| 575 |
+
"**So why does training still sometimes go uphill?**\n",
|
| 576 |
+
"- The stride was too long. That is the *learning rate*. The slope was right, but the hiker\n",
|
| 577 |
+
" overshot the valley and landed on the far wall. The next slope reading points back.\n",
|
| 578 |
+
"- The reading was noisy. Each step uses one batch of stories, so the slope is an estimate.\n",
|
| 579 |
+
"\n",
|
| 580 |
+
"**The marks in space are real, in two forms.**\n",
|
| 581 |
+
"- *The weights themselves.* Every step is written into the weights and nothing is forgotten.\n",
|
| 582 |
+
" The model does not get a map, it *is* the map after training.\n",
|
| 583 |
+
"- *The optimizer's memory.* AdamW keeps a running average of recent slopes. That is the\n",
|
| 584 |
+
" hiker remembering \"the last ten readings all said left, so one odd reading saying right is\n",
|
| 585 |
+
" probably a bump; keep going left\". One tree does not fool it.\n",
|
| 586 |
+
"\n",
|
| 587 |
+
"**Fastest and most accurate.** Short careful steps at the start (warm-up, the terrain is\n",
|
| 588 |
+
"unknown), full stride in the middle, tiny steps near the flag (cosine decay) so the hiker\n",
|
| 589 |
+
"stops on the floor instead of bouncing between the walls.\n",
|
| 590 |
+
"\n",
|
| 591 |
+
"Try both ways of walking below and compare the step and tree counts."
|
| 592 |
+
]
|
| 593 |
+
},
|
| 594 |
+
{
|
| 595 |
+
"cell_type": "code",
|
| 596 |
+
"execution_count": null,
|
| 597 |
+
"id": "78e7b447",
|
| 598 |
+
"metadata": {},
|
| 599 |
+
"outputs": [],
|
| 600 |
+
"source": [
|
| 601 |
+
"# Interactive: the hiker in the fog. Guess and bump into trees, or read the slope first.\n",
|
| 602 |
+
"from IPython.display import HTML, display\n",
|
| 603 |
+
"display(HTML(r\"\"\"<div id=\"vh\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:960px\">\n",
|
| 604 |
+
"<style>\n",
|
| 605 |
+
"#vh button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin:0 6px 6px 0;cursor:pointer;font-size:13px}\n",
|
| 606 |
+
"#vh button:hover{background:#444}\n",
|
| 607 |
+
"#vh button.g{border-color:#6fcf97}#vh button.r{border-color:#ff6b6b}\n",
|
| 608 |
+
"#vh .cap{font-size:13px;line-height:1.5;margin-top:8px;background:#262626;border-radius:8px;padding:10px}\n",
|
| 609 |
+
"#vh .log{font-size:12px;background:#262626;border-radius:8px;padding:8px 10px;height:150px;overflow:auto;line-height:1.5}\n",
|
| 610 |
+
"#vh .stat{display:inline-block;padding:4px 10px;border-radius:6px;background:#2a2a2a;border:1px solid #555;margin:2px 6px 2px 0;font-size:12px}\n",
|
| 611 |
+
"</style>\n",
|
| 612 |
+
"<div style=\"font-size:15px;font-weight:600;margin-bottom:4px\">The hiker in the fog: trees, marks, and the slope under your feet</div>\n",
|
| 613 |
+
"<div style=\"font-size:12px;color:#aaa;margin-bottom:8px\">The map is the loss. Dark blue is low (good), yellow is high (bad). The flag is the best set of weights. The hiker cannot see the map; it only feels the ground.</div>\n",
|
| 614 |
+
"<div style=\"display:flex;gap:16px;flex-wrap:wrap\">\n",
|
| 615 |
+
" <div><canvas id=\"vhC\" width=\"520\" height=\"360\" style=\"border-radius:8px;background:#262626\"></canvas></div>\n",
|
| 616 |
+
" <div style=\"flex:1;min-width:300px\">\n",
|
| 617 |
+
" <div style=\"font-size:12px;color:#aaa;margin-bottom:4px\">TWO WAYS TO WALK</div>\n",
|
| 618 |
+
" <button class=\"r\" onclick=\"vhBlind()\">Guess a step (may hit a tree)</button>\n",
|
| 619 |
+
" <button class=\"g\" onclick=\"vhGrad()\">Read the slope, then step</button><br>\n",
|
| 620 |
+
" <button onclick=\"vhAuto('blind')\">Auto: guessing</button>\n",
|
| 621 |
+
" <button onclick=\"vhAuto('grad')\">Auto: slope</button>\n",
|
| 622 |
+
" <button onclick=\"vhReset()\">Reset</button>\n",
|
| 623 |
+
" <div style=\"margin:6px 0\">\n",
|
| 624 |
+
" <label style=\"font-size:12px\">Stride (learning rate) <input id=\"vhLr\" type=\"range\" min=\"1\" max=\"10\" value=\"4\" style=\"vertical-align:middle;width:120px\"></label>\n",
|
| 625 |
+
" <label style=\"font-size:12px;margin-left:8px\"><input id=\"vhMem\" type=\"checkbox\" checked> memory of recent slopes (momentum)</label>\n",
|
| 626 |
+
" </div>\n",
|
| 627 |
+
" <div><span class=\"stat\" id=\"vhSteps\">steps: 0</span><span class=\"stat\" id=\"vhTrees\">trees hit: 0</span><span class=\"stat\" id=\"vhLoss\">loss: 0</span></div>\n",
|
| 628 |
+
" <div style=\"font-size:12px;color:#aaa;margin:8px 0 4px\">THE HIKER'S NOTEBOOK (marks in space)</div>\n",
|
| 629 |
+
" <div class=\"log\" id=\"vhLog\">Nothing tried yet.</div>\n",
|
| 630 |
+
" </div>\n",
|
| 631 |
+
"</div>\n",
|
| 632 |
+
"<div class=\"cap\" id=\"vhCap\"></div>\n",
|
| 633 |
+
"</div>\n",
|
| 634 |
+
"<script>\n",
|
| 635 |
+
"(function(){\n",
|
| 636 |
+
"const C=document.getElementById('vhC'),ctx=C.getContext('2d'),W=C.width,H=C.height;\n",
|
| 637 |
+
"// loss landscape in map coordinates (0..1, 0..1): a tilted bowl with two bumps (the \"trees\" of the terrain)\n",
|
| 638 |
+
"const goal=[0.72,0.68];\n",
|
| 639 |
+
"const loss=(x,y)=>{const dx=x-goal[0],dy=y-goal[1];let l=6*(dx*dx+dy*dy)+0.5*(1-x)+0.3*(1-y);\n",
|
| 640 |
+
" l+=1.1*Math.exp(-((x-0.40)**2+(y-0.55)**2)/0.012);l+=0.9*Math.exp(-((x-0.58)**2+(y-0.30)**2)/0.010);return l;};\n",
|
| 641 |
+
"const grad=(x,y)=>{const h=1e-3;return [(loss(x+h,y)-loss(x-h,y))/(2*h),(loss(x,y+h)-loss(x,y-h))/(2*h)];};\n",
|
| 642 |
+
"const px=x=>x*W, py=y=>(1-y)*H;\n",
|
| 643 |
+
"let bg=null,pos,trail,xs,steps,trees,mem,timer=null,mode='';\n",
|
| 644 |
+
"let minL=Infinity;for(let i=0;i<=100;i++)for(let j=0;j<=100;j++){const v=loss(i/100,j/100);if(v<minL)minL=v;}\n",
|
| 645 |
+
"function paintBg(){const img=ctx.createImageData(W,H);let mx=0;const vals=new Float32Array(W*H);\n",
|
| 646 |
+
" for(let j=0;j<H;j++)for(let i=0;i<W;i++){const v=loss(i/W,1-j/H);vals[j*W+i]=v;if(v>mx)mx=v;}\n",
|
| 647 |
+
" for(let k=0;k<W*H;k++){const t=Math.min(1,vals[k]/mx);const r=Math.round(30+225*t),g=Math.round(40+170*t*t),b=Math.round(110*(1-t)+40);\n",
|
| 648 |
+
" img.data[k*4]=r;img.data[k*4+1]=g;img.data[k*4+2]=b;img.data[k*4+3]=255;}\n",
|
| 649 |
+
" ctx.putImageData(img,0,0);\n",
|
| 650 |
+
" // contour-ish rings\n",
|
| 651 |
+
" ctx.strokeStyle='rgba(255,255,255,0.12)';ctx.lineWidth=1;\n",
|
| 652 |
+
" for(let lv=0.3;lv<4;lv+=0.35){ctx.beginPath();let first=true;for(let a=0;a<=Math.PI*2+0.05;a+=0.05){\n",
|
| 653 |
+
" // crude: walk outward from goal until loss>=lv\n",
|
| 654 |
+
" let r=0;for(;r<1.2;r+=0.004){if(loss(goal[0]+r*Math.cos(a),goal[1]+r*Math.sin(a))>=lv)break;}\n",
|
| 655 |
+
" const x=px(goal[0]+r*Math.cos(a)),y=py(goal[1]+r*Math.sin(a));if(first){ctx.moveTo(x,y);first=false;}else ctx.lineTo(x,y);}ctx.stroke();}\n",
|
| 656 |
+
" bg=ctx.getImageData(0,0,W,H);}\n",
|
| 657 |
+
"function draw(){ctx.putImageData(bg,0,0);\n",
|
| 658 |
+
" // flag\n",
|
| 659 |
+
" ctx.fillStyle='#fff';ctx.fillRect(px(goal[0])-1,py(goal[1])-22,2,22);ctx.fillStyle='#6fcf97';ctx.beginPath();ctx.moveTo(px(goal[0]),py(goal[1])-22);ctx.lineTo(px(goal[0])+14,py(goal[1])-17);ctx.lineTo(px(goal[0]),py(goal[1])-12);ctx.fill();\n",
|
| 660 |
+
" ctx.font='11px sans-serif';ctx.fillStyle='#fff';ctx.fillText('best weights',px(goal[0])+16,py(goal[1])-14);\n",
|
| 661 |
+
" // trail\n",
|
| 662 |
+
" ctx.strokeStyle='#6fcf97';ctx.lineWidth=2;ctx.beginPath();trail.forEach((p,i)=>{i?ctx.lineTo(px(p[0]),py(p[1])):ctx.moveTo(px(p[0]),py(p[1]));});ctx.stroke();\n",
|
| 663 |
+
" trail.forEach(p=>{ctx.fillStyle='#6fcf97';ctx.beginPath();ctx.arc(px(p[0]),py(p[1]),3,0,7);ctx.fill();});\n",
|
| 664 |
+
" // trees (bad tries)\n",
|
| 665 |
+
" xs.forEach(p=>{ctx.strokeStyle='#ff6b6b';ctx.lineWidth=2.5;const x=px(p[0]),y=py(p[1]);ctx.beginPath();ctx.moveTo(x-6,y-6);ctx.lineTo(x+6,y+6);ctx.moveTo(x+6,y-6);ctx.lineTo(x-6,y+6);ctx.stroke();});\n",
|
| 666 |
+
" // slope arrow (what the gradient says)\n",
|
| 667 |
+
" const g=grad(pos[0],pos[1]);const n=Math.hypot(g[0],g[1])||1;const ax=-g[0]/n*34,ay=g[1]/n*34;\n",
|
| 668 |
+
" ctx.strokeStyle='#ffb347';ctx.lineWidth=3;ctx.beginPath();ctx.moveTo(px(pos[0]),py(pos[1]));ctx.lineTo(px(pos[0])+ax,py(pos[1])+ay);ctx.stroke();\n",
|
| 669 |
+
" ctx.fillStyle='#ffb347';ctx.beginPath();const ang=Math.atan2(ay,ax);ctx.moveTo(px(pos[0])+ax,py(pos[1])+ay);ctx.lineTo(px(pos[0])+ax-9*Math.cos(ang-0.4),py(pos[1])+ay-9*Math.sin(ang-0.4));ctx.lineTo(px(pos[0])+ax-9*Math.cos(ang+0.4),py(pos[1])+ay-9*Math.sin(ang+0.4));ctx.fill();\n",
|
| 670 |
+
" // hiker\n",
|
| 671 |
+
" ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(px(pos[0]),py(pos[1]),8,0,7);ctx.fill();ctx.fillStyle='#1e1e1e';ctx.font='11px sans-serif';ctx.fillText('\\u{1F6B6}',px(pos[0])-6,py(pos[1])+4);\n",
|
| 672 |
+
" const l=loss(pos[0],pos[1]);\n",
|
| 673 |
+
" document.getElementById('vhSteps').textContent='steps: '+steps;document.getElementById('vhTrees').textContent='trees hit: '+trees;document.getElementById('vhLoss').textContent='loss: '+l.toFixed(2);\n",
|
| 674 |
+
" if(l<minL+0.04){document.getElementById('vhCap').innerHTML='<b>Arrived.</b> The hiker is on the valley floor: these weights make the fewest mistakes. Steps: <b>'+steps+'</b>, trees hit: <b>'+trees+'</b>. Press Reset and try the other way of walking.';if(timer){clearInterval(timer);timer=null;}}\n",
|
| 675 |
+
"}\n",
|
| 676 |
+
"function logLine(html){const L=document.getElementById('vhLog');if(L.textContent.startsWith('Nothing'))L.innerHTML='';L.innerHTML='<div>'+html+'</div>'+L.innerHTML;}\n",
|
| 677 |
+
"const lr=()=>0.008*(+document.getElementById('vhLr').value);\n",
|
| 678 |
+
"window.vhBlind=function(){steps++;const a=Math.random()*Math.PI*2;const d=lr()*2.2;const nx=pos[0]+Math.cos(a)*d,ny=pos[1]+Math.sin(a)*d;\n",
|
| 679 |
+
" const before=loss(pos[0],pos[1]),after=loss(nx,ny);\n",
|
| 680 |
+
" if(after>=before){trees++;xs.push([nx,ny]);logLine('❌ tried '+dir(a)+': loss went <b>up</b> '+before.toFixed(2)+' → '+after.toFixed(2)+'. Tree. Marked, stayed put.');\n",
|
| 681 |
+
" document.getElementById('vhCap').innerHTML='<b>Hit a tree.</b> The guess made the loss worse, so the hiker steps back and marks the spot with a red X. Guessing finds the valley eventually, but it pays for every tree. Real training cannot afford this: each guess would mean testing 5.9 million weights one direction at a time.';}\n",
|
| 682 |
+
" else{pos=[nx,ny];trail.push(pos.slice());logLine('✔ tried '+dir(a)+': loss went down '+before.toFixed(2)+' → '+after.toFixed(2)+'. Kept it.');\n",
|
| 683 |
+
" document.getElementById('vhCap').innerHTML='<b>Lucky guess.</b> The loss went down, so the step is kept and the trail (green) records it.';}\n",
|
| 684 |
+
" draw();};\n",
|
| 685 |
+
"window.vhGrad=function(){steps++;let g=grad(pos[0],pos[1]);\n",
|
| 686 |
+
" // momentum: remember recent slopes\n",
|
| 687 |
+
" if(document.getElementById('vhMem').checked){mem=[0.8*mem[0]+0.2*g[0],0.8*mem[1]+0.2*g[1]];g=mem;}\n",
|
| 688 |
+
" const n=Math.hypot(g[0],g[1])||1;const d=Math.min(lr()*2.2,lr()*n*0.6);const nx=Math.min(0.98,Math.max(0.02,pos[0]-g[0]/n*d)),ny=Math.min(0.98,Math.max(0.02,pos[1]-g[1]/n*d));\n",
|
| 689 |
+
" const before=loss(pos[0],pos[1]),after=loss(nx,ny);pos=[nx,ny];trail.push(pos.slice());\n",
|
| 690 |
+
" if(after>=before){trees++;xs.push([nx,ny]);logLine('⚠ slope said '+dir(Math.atan2(-g[1],-g[0]))+' but loss went <b>up</b> '+before.toFixed(2)+' → '+after.toFixed(2)+' (stride too long, or noisy reading). Next reading will point back.');\n",
|
| 691 |
+
" document.getElementById('vhCap').innerHTML='<b>Overshot.</b> The slope was right but the stride was too long, so the hiker landed on the far wall. Nothing is lost: the next slope reading points straight back. Shorten the stride (lower learning rate) and this stops happening. That is why training ends with tiny steps.';}\n",
|
| 692 |
+
" else{logLine('✔ slope said '+dir(Math.atan2(-g[1],-g[0]))+': loss '+before.toFixed(2)+' → '+after.toFixed(2)+'.');\n",
|
| 693 |
+
" document.getElementById('vhCap').innerHTML='<b>Read the slope first.</b> The orange arrow is the gradient: which way is downhill, felt through the feet before moving. No tree needed. Autograd gives this arrow for all 5.9 million weights at once. With memory on, the arrow is the average of recent readings, so one odd bump does not fool the hiker.';}\n",
|
| 694 |
+
" draw();};\n",
|
| 695 |
+
"window.vhAuto=function(m){if(timer){clearInterval(timer);timer=null;if(mode===m)return;}mode=m;timer=setInterval(()=>{m==='blind'?vhBlind():vhGrad();if(steps>400){clearInterval(timer);timer=null;}},m==='blind'?120:260);};\n",
|
| 696 |
+
"window.vhReset=function(){if(timer){clearInterval(timer);timer=null;}pos=[0.12,0.86];trail=[pos.slice()];xs=[];steps=0;trees=0;mem=[0,0];document.getElementById('vhLog').textContent='Nothing tried yet.';\n",
|
| 697 |
+
" document.getElementById('vhCap').innerHTML='The hiker starts top-left in fog. <b>Guess a step</b>: pick a random direction, walk, and only then find out if the loss went up (a tree, marked red) or down (kept, marked green). <b>Read the slope</b>: feel the ground first, step downhill. Compare how many steps and trees each way needs to reach the flag.';draw();};\n",
|
| 698 |
+
"function dir(a){const d=['east','north-east','north','north-west','west','south-west','south','south-east'];return d[Math.round(((a%(2*Math.PI))+2*Math.PI)%(2*Math.PI)/(Math.PI/4))%8];}\n",
|
| 699 |
+
"paintBg();vhReset();\n",
|
| 700 |
+
"})();\n",
|
| 701 |
+
"</script>\n",
|
| 702 |
+
"\"\"\"))"
|
| 703 |
+
]
|
| 704 |
+
},
|
| 705 |
{
|
| 706 |
"cell_type": "code",
|
| 707 |
"execution_count": null,
|
| 708 |
+
"id": "87220190",
|
| 709 |
"metadata": {},
|
| 710 |
"outputs": [],
|
| 711 |
"source": [
|
|
|
|
| 723 |
},
|
| 724 |
{
|
| 725 |
"cell_type": "markdown",
|
| 726 |
+
"id": "0b60c411",
|
| 727 |
"metadata": {},
|
| 728 |
"source": [
|
| 729 |
"## 2. Data: TinyStories\n",
|
|
|
|
| 738 |
{
|
| 739 |
"cell_type": "code",
|
| 740 |
"execution_count": null,
|
| 741 |
+
"id": "463680d1",
|
| 742 |
"metadata": {},
|
| 743 |
"outputs": [],
|
| 744 |
"source": [
|
|
|
|
| 756 |
},
|
| 757 |
{
|
| 758 |
"cell_type": "markdown",
|
| 759 |
+
"id": "ddf8b81e",
|
| 760 |
"metadata": {},
|
| 761 |
"source": [
|
| 762 |
"## 3. The simplest language model: a bigram\n",
|
|
|
|
| 776 |
{
|
| 777 |
"cell_type": "code",
|
| 778 |
"execution_count": null,
|
| 779 |
+
"id": "84a17efb",
|
| 780 |
"metadata": {},
|
| 781 |
"outputs": [],
|
| 782 |
"source": [
|
|
|
|
| 807 |
{
|
| 808 |
"cell_type": "code",
|
| 809 |
"execution_count": null,
|
| 810 |
+
"id": "553fcb26",
|
| 811 |
"metadata": {},
|
| 812 |
"outputs": [],
|
| 813 |
"source": [
|
|
|
|
| 827 |
{
|
| 828 |
"cell_type": "code",
|
| 829 |
"execution_count": null,
|
| 830 |
+
"id": "bdda55d3",
|
| 831 |
"metadata": {},
|
| 832 |
"outputs": [],
|
| 833 |
"source": [
|
|
|
|
| 876 |
},
|
| 877 |
{
|
| 878 |
"cell_type": "markdown",
|
| 879 |
+
"id": "9fdda98f",
|
| 880 |
"metadata": {},
|
| 881 |
"source": [
|
| 882 |
"It learned *something* (the loss dropped, and it produces word-ish shapes with spaces),\n",
|
|
|
|
| 886 |
},
|
| 887 |
{
|
| 888 |
"cell_type": "markdown",
|
| 889 |
+
"id": "586b2b1e",
|
| 890 |
"metadata": {},
|
| 891 |
"source": [
|
| 892 |
"## 4. A real tokenizer: byte-pair encoding (BPE)\n",
|
|
|
|
| 904 |
{
|
| 905 |
"cell_type": "code",
|
| 906 |
"execution_count": null,
|
| 907 |
+
"id": "a08a191a",
|
| 908 |
"metadata": {},
|
| 909 |
"outputs": [],
|
| 910 |
"source": [
|
|
|
|
| 934 |
{
|
| 935 |
"cell_type": "code",
|
| 936 |
"execution_count": null,
|
| 937 |
+
"id": "4a3fe207",
|
| 938 |
"metadata": {},
|
| 939 |
"outputs": [],
|
| 940 |
"source": [
|
|
|
|
| 960 |
{
|
| 961 |
"cell_type": "code",
|
| 962 |
"execution_count": null,
|
| 963 |
+
"id": "f29885a8",
|
| 964 |
"metadata": {},
|
| 965 |
"outputs": [],
|
| 966 |
"source": [
|
|
|
|
| 974 |
},
|
| 975 |
{
|
| 976 |
"cell_type": "markdown",
|
| 977 |
+
"id": "9fc7ca98",
|
| 978 |
"metadata": {},
|
| 979 |
"source": [
|
| 980 |
"## 5. Attention, by hand\n",
|
|
|
|
| 995 |
{
|
| 996 |
"cell_type": "code",
|
| 997 |
"execution_count": null,
|
| 998 |
+
"id": "7ca9b266",
|
| 999 |
"metadata": {},
|
| 1000 |
"outputs": [],
|
| 1001 |
"source": [
|
|
|
|
| 1019 |
},
|
| 1020 |
{
|
| 1021 |
"cell_type": "markdown",
|
| 1022 |
+
"id": "ff47fa82",
|
| 1023 |
"metadata": {},
|
| 1024 |
"source": [
|
| 1025 |
"### The KV cache, in twelve lines\n",
|
|
|
|
| 1033 |
{
|
| 1034 |
"cell_type": "code",
|
| 1035 |
"execution_count": null,
|
| 1036 |
+
"id": "f71b1fb1",
|
| 1037 |
"metadata": {},
|
| 1038 |
"outputs": [],
|
| 1039 |
"source": [
|
|
|
|
| 1063 |
},
|
| 1064 |
{
|
| 1065 |
"cell_type": "markdown",
|
| 1066 |
+
"id": "fb78d22a",
|
| 1067 |
"metadata": {},
|
| 1068 |
"source": [
|
| 1069 |
"## 6. A tiny GPT\n",
|
|
|
|
| 1086 |
{
|
| 1087 |
"cell_type": "code",
|
| 1088 |
"execution_count": null,
|
| 1089 |
+
"id": "4f0b2930",
|
| 1090 |
"metadata": {},
|
| 1091 |
"outputs": [],
|
| 1092 |
"source": [
|
|
|
|
| 1130 |
{
|
| 1131 |
"cell_type": "code",
|
| 1132 |
"execution_count": null,
|
| 1133 |
+
"id": "352f105d",
|
| 1134 |
"metadata": {},
|
| 1135 |
"outputs": [],
|
| 1136 |
"source": [
|
|
|
|
| 1241 |
},
|
| 1242 |
{
|
| 1243 |
"cell_type": "markdown",
|
| 1244 |
+
"id": "dc0da2c9",
|
| 1245 |
"metadata": {},
|
| 1246 |
"source": [
|
| 1247 |
"## 7. Training\n",
|
|
|
|
| 1263 |
{
|
| 1264 |
"cell_type": "code",
|
| 1265 |
"execution_count": null,
|
| 1266 |
+
"id": "047dde66",
|
| 1267 |
"metadata": {},
|
| 1268 |
"outputs": [],
|
| 1269 |
"source": [
|
|
|
|
| 1305 |
{
|
| 1306 |
"cell_type": "code",
|
| 1307 |
"execution_count": null,
|
| 1308 |
+
"id": "7f86a500",
|
| 1309 |
"metadata": {},
|
| 1310 |
"outputs": [],
|
| 1311 |
"source": [
|
|
|
|
| 1372 |
},
|
| 1373 |
{
|
| 1374 |
"cell_type": "markdown",
|
| 1375 |
+
"id": "56158b70",
|
| 1376 |
"metadata": {},
|
| 1377 |
"source": [
|
| 1378 |
"**How to read the numbers.** The loss is the average \"surprise\" per token. *Perplexity* is\n",
|
|
|
|
| 1382 |
},
|
| 1383 |
{
|
| 1384 |
"cell_type": "markdown",
|
| 1385 |
+
"id": "f03d66b2",
|
| 1386 |
"metadata": {},
|
| 1387 |
"source": [
|
| 1388 |
"## 8. Generate stories: the use case\n",
|
|
|
|
| 1394 |
{
|
| 1395 |
"cell_type": "code",
|
| 1396 |
"execution_count": null,
|
| 1397 |
+
"id": "49179e8e",
|
| 1398 |
"metadata": {},
|
| 1399 |
"outputs": [],
|
| 1400 |
"source": [
|
|
|
|
| 1411 |
},
|
| 1412 |
{
|
| 1413 |
"cell_type": "markdown",
|
| 1414 |
+
"id": "2be38804",
|
| 1415 |
"metadata": {},
|
| 1416 |
"source": [
|
| 1417 |
"## 9. Save and publish on the Hugging Face Hub\n",
|
|
|
|
| 1427 |
{
|
| 1428 |
"cell_type": "code",
|
| 1429 |
"execution_count": null,
|
| 1430 |
+
"id": "3bf91999",
|
| 1431 |
"metadata": {},
|
| 1432 |
"outputs": [],
|
| 1433 |
"source": [
|
|
|
|
| 1468 |
{
|
| 1469 |
"cell_type": "code",
|
| 1470 |
"execution_count": null,
|
| 1471 |
+
"id": "8e406129",
|
| 1472 |
"metadata": {},
|
| 1473 |
"outputs": [],
|
| 1474 |
"source": [
|
|
|
|
| 1489 |
},
|
| 1490 |
{
|
| 1491 |
"cell_type": "markdown",
|
| 1492 |
+
"id": "fa86efcb",
|
| 1493 |
"metadata": {},
|
| 1494 |
"source": [
|
| 1495 |
"## Loading it back later\n",
|
|
|
|
| 1500 |
{
|
| 1501 |
"cell_type": "code",
|
| 1502 |
"execution_count": null,
|
| 1503 |
+
"id": "3d6c508e",
|
| 1504 |
"metadata": {},
|
| 1505 |
"outputs": [],
|
| 1506 |
"source": [
|
|
|
|
| 1521 |
},
|
| 1522 |
{
|
| 1523 |
"cell_type": "markdown",
|
| 1524 |
+
"id": "20653937",
|
| 1525 |
"metadata": {},
|
| 1526 |
"source": [
|
| 1527 |
"## What we built, in one paragraph\n",
|