mirror of
https://github.com/kuhyx/WUT_Computer_Science.git
synced 2026-11-10 19:07:11 +01:00
twm_4: zero findings, with the notebook's outputs proved untouched
228 ruff findings to 0 across the notebook, its partial export and twm.py, and mypy --strict is clean on both .py files. A notebook is a deliverable that happens to contain code, so the check that mattered was not "does it still run" -- it needs a GPU, MNIST, CIFAR-10 and a slice of Quick, Draw! to run at all -- but "is anything except the code still exactly as submitted". Hashing every key of the .ipynb EXCEPT cell `source` gives 60242e116d12549d11c93f7f before the first edit and after the last one, across four passes and a `ruff format`. The file also round-trips byte-for-byte through json.dumps(indent=1, ensure_ascii=False), which is what made editing cells programmatically safe rather than a reformat of all 141 of them; the diff touches 38. print() became sys.stdout.write() rather than logging. In a notebook the print IS the output, and a logger with no handler configured would have silently emptied every cell -- the exact regression scripts/check_refactor_safety.py was written for. matprint's `print(x, end=" ")` and bare `print()` are preserved as writes with and without a trailing newline. Both `show_samples` and `show_samples_rgb` took (indices, preds, images, labels, count, names), which is six arguments and a mutable default. They now take a frozen `Samples` holding the last four, which also removed the same four-value tuple from seven call sites. Reviewing that diff is how I caught that I had transcribed `real_score = preds[sample][real_id]` as `samples.labels[sample]` -- a bug I introduced and then fixed before commit. Other real things, not just rule satisfaction: `np.in1d` is removed in NumPy 2 (NPY201) and is now `np.isin(...).ravel()`; `test_model` computed correct_indices and incorrect_indices and used neither; two cells grew `img` inside a loop so it was only bound by the `else` branch; and the Quick, Draw! downloader now builds a URL whose https scheme is a literal, which is what S310 was asking for. The README claimed the .py was "an nbconvert export of that notebook". It is an export of the CIFAR-10 section only -- 28 of 81 code cells -- and has been edited since. It says that now, and says the two are separate artifacts rather than one being regenerable from the other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f780d023be
commit
1fd1de2f82
@ -5,8 +5,15 @@ Coursework for TWM (Techniki Wizyjne / machine vision), lab 4.
|
|||||||
| Path | What it is |
|
| Path | What it is |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `TWM_KerasIntro.ipynb` | The submitted notebook — the deliverable. |
|
| `TWM_KerasIntro.ipynb` | The submitted notebook — the deliverable. |
|
||||||
| `TWM_KerasIntro.py` | An `nbconvert` export of that notebook. Derived, not edited by hand. |
|
| `TWM_KerasIntro.py` | An `nbconvert` export of the notebook's **CIFAR-10 section only**. |
|
||||||
| `twm.py` | Helper code used by the notebook. |
|
| `twm.py` | Prints the TensorFlow version the notebook will run against. |
|
||||||
|
|
||||||
The `.py` export is kept because it is what makes the notebook's code
|
The notebook is the source of truth. The `.py` is kept because it makes that
|
||||||
reviewable and diffable; edit the notebook, then re-export.
|
section's code reviewable and diffable, but it is not a full export: it covers
|
||||||
|
28 of the notebook's 81 code cells, all of them from the CIFAR-10 half, and it
|
||||||
|
has been edited since. Treat the two as separate artifacts rather than
|
||||||
|
regenerating one from the other.
|
||||||
|
|
||||||
|
Both need a GPU. The notebook's first cell calls `sys.exit()` when TensorFlow
|
||||||
|
reports no physical GPU device, and running it end to end downloads MNIST,
|
||||||
|
CIFAR-10 and a slice of the Quick, Draw! bitmaps into `data/`.
|
||||||
|
|||||||
@ -89,10 +89,16 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"import itertools\n",
|
"import itertools\n",
|
||||||
"import random # for generating random numbers\n",
|
"import random # for generating random numbers\n",
|
||||||
|
"import sys\n",
|
||||||
|
"import urllib.request\n",
|
||||||
|
"from dataclasses import dataclass\n",
|
||||||
|
"from pathlib import Path\n",
|
||||||
"\n",
|
"\n",
|
||||||
"import matplotlib.pyplot as plt # MATLAB like plotting routines\n",
|
"import matplotlib.pyplot as plt # MATLAB like plotting routines\n",
|
||||||
"import numpy as np # advanced math library\n",
|
"import numpy as np # advanced math library\n",
|
||||||
|
"import seaborn as sns\n",
|
||||||
"import tensorflow as tf\n",
|
"import tensorflow as tf\n",
|
||||||
|
"from keras import Model\n",
|
||||||
"from keras.datasets import mnist # MNIST dataset is included in Keras%\n",
|
"from keras.datasets import mnist # MNIST dataset is included in Keras%\n",
|
||||||
"from keras.layers import (\n",
|
"from keras.layers import (\n",
|
||||||
" Activation,\n",
|
" Activation,\n",
|
||||||
@ -101,9 +107,14 @@
|
|||||||
") # Types of layers to be used in our model\n",
|
") # Types of layers to be used in our model\n",
|
||||||
"from keras.models import Sequential # Model type to be used\n",
|
"from keras.models import Sequential # Model type to be used\n",
|
||||||
"from keras.utils import to_categorical # NumPy related tools\n",
|
"from keras.utils import to_categorical # NumPy related tools\n",
|
||||||
|
"from matplotlib import patheffects\n",
|
||||||
|
"from sklearn.manifold import TSNE\n",
|
||||||
"from sklearn.metrics import confusion_matrix\n",
|
"from sklearn.metrics import confusion_matrix\n",
|
||||||
|
"from sklearn.preprocessing import OneHotEncoder\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(tf.__version__)"
|
"# print() would need a logging handler configured to survive this file being\n",
|
||||||
|
"# exported and run as a script; sys.stdout.write shows the same text in a cell.\n",
|
||||||
|
"sys.stdout.write(f\"{tf.__version__}\\n\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -129,13 +140,14 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# The MNIST data is split between 60,000 28 x 28 pixel training images and 10,000 28 x 28 pixel images\n",
|
"# The MNIST data is split between 60,000 28 x 28 pixel training images\n",
|
||||||
|
"# and 10,000 28 x 28 pixel images\n",
|
||||||
"(X_train, y_train), (X_test, y_test) = mnist.load_data()\n",
|
"(X_train, y_train), (X_test, y_test) = mnist.load_data()\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(\"X_train shape\", X_train.shape)\n",
|
"sys.stdout.write(f\"X_train shape {X_train.shape}\\n\")\n",
|
||||||
"print(\"y_train shape\", y_train.shape)\n",
|
"sys.stdout.write(f\"y_train shape {y_train.shape}\\n\")\n",
|
||||||
"print(\"X_test shape\", X_test.shape)\n",
|
"sys.stdout.write(f\"X_test shape {X_test.shape}\\n\")\n",
|
||||||
"print(\"y_test shape\", y_test.shape)"
|
"sys.stdout.write(f\"y_test shape {y_test.shape}\\n\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -162,20 +174,28 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"plt.rcParams[\"figure.figsize\"] = (9, 9) # Make the figures a bit bigger\n",
|
"plt.rcParams[\"figure.figsize\"] = (9, 9) # Make the figures a bit bigger\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"CLASS_COUNT = 10\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def visualize_classes(X, y):\n",
|
"\n",
|
||||||
" for i in range(10):\n",
|
"def visualize_classes(images: np.ndarray, labels: np.ndarray) -> None:\n",
|
||||||
" img_batch = X[y == i][0:10]\n",
|
" \"\"\"Show ten samples of each class as one wide strip.\n",
|
||||||
" img_batch = np.reshape(\n",
|
"\n",
|
||||||
" img_batch, (img_batch.shape[0] * img_batch.shape[1], img_batch.shape[2])\n",
|
" The strips are collected in a list and concatenated once. Growing the\n",
|
||||||
|
" result in place only bound it in the loop's `else` branch, which reads as\n",
|
||||||
|
" a use-before-assignment and is what ruff flagged.\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" strips = []\n",
|
||||||
|
" for class_id in range(CLASS_COUNT):\n",
|
||||||
|
" img_batch = images[labels == class_id][0:10]\n",
|
||||||
|
" strips.append(\n",
|
||||||
|
" np.reshape(\n",
|
||||||
|
" img_batch,\n",
|
||||||
|
" (img_batch.shape[0] * img_batch.shape[1], img_batch.shape[2]),\n",
|
||||||
|
" )\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" if i > 0:\n",
|
|
||||||
" img = np.concatenate([img, img_batch], axis=1)\n",
|
|
||||||
" else:\n",
|
|
||||||
" img = img_batch\n",
|
|
||||||
" plt.figure(figsize=(10, 20))\n",
|
" plt.figure(figsize=(10, 20))\n",
|
||||||
" plt.axis(\"off\")\n",
|
" plt.axis(\"off\")\n",
|
||||||
" plt.imshow(img, cmap=\"gray\")\n",
|
" plt.imshow(np.concatenate(strips, axis=1), cmap=\"gray\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"visualize_classes(X_train, y_train)"
|
"visualize_classes(X_train, y_train)"
|
||||||
@ -203,12 +223,14 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# just a little function for pretty printing a matrix\n",
|
"# just a little function for pretty printing a matrix\n",
|
||||||
"def matprint(mat, fmt=\"g\"):\n",
|
"def matprint(mat: np.ndarray, fmt: str = \"g\") -> None:\n",
|
||||||
|
" \"\"\"Print `mat` with every column padded to its own widest entry.\"\"\"\n",
|
||||||
" col_maxes = [max([len((\"{:\" + fmt + \"}\").format(x)) for x in col]) for col in mat.T]\n",
|
" col_maxes = [max([len((\"{:\" + fmt + \"}\").format(x)) for x in col]) for col in mat.T]\n",
|
||||||
" for x in mat:\n",
|
" for row in mat:\n",
|
||||||
" for i, y in enumerate(x):\n",
|
" for i, value in enumerate(row):\n",
|
||||||
" print((\"{:\" + str(col_maxes[i]) + fmt + \"}\").format(y), end=\" \")\n",
|
" # Two trailing spaces and no newline: this builds one line per row.\n",
|
||||||
" print()\n",
|
" sys.stdout.write((\"{:\" + str(col_maxes[i]) + fmt + \"} \").format(value))\n",
|
||||||
|
" sys.stdout.write(\"\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# now print!\n",
|
"# now print!\n",
|
||||||
@ -257,44 +279,42 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"import matplotlib.patheffects as PathEffects\n",
|
|
||||||
"import seaborn as sns\n",
|
|
||||||
"from sklearn.manifold import TSNE\n",
|
|
||||||
"\n",
|
|
||||||
"RS = 19238\n",
|
"RS = 19238\n",
|
||||||
"\n",
|
"\n",
|
||||||
"class_names = [str(clid) for clid in range(10)]\n",
|
"class_names = [str(clid) for clid in range(CLASS_COUNT)]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def scatter(x, colors):\n",
|
"def scatter(points: np.ndarray, colors: np.ndarray) -> None:\n",
|
||||||
|
" \"\"\"Scatter 2D `points` coloured by class, with each class labelled once.\"\"\"\n",
|
||||||
" # We choose a color palette with seaborn.\n",
|
" # We choose a color palette with seaborn.\n",
|
||||||
" palette = np.array(sns.color_palette(\"hls\", 10))\n",
|
" palette = np.array(sns.color_palette(\"hls\", CLASS_COUNT))\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # We create a scatter plot.\n",
|
" # We create a scatter plot.\n",
|
||||||
" f = plt.figure(figsize=(8, 8))\n",
|
" plt.figure(figsize=(8, 8))\n",
|
||||||
" ax = plt.subplot(aspect=\"equal\")\n",
|
" ax = plt.subplot(aspect=\"equal\")\n",
|
||||||
" sc = ax.scatter(x[:, 0], x[:, 1], lw=0, s=40, c=palette[colors.astype(\"int\")])\n",
|
" ax.scatter(points[:, 0], points[:, 1], lw=0, s=40, c=palette[colors.astype(\"int\")])\n",
|
||||||
" plt.xlim(-25, 25)\n",
|
" plt.xlim(-25, 25)\n",
|
||||||
" plt.ylim(-25, 25)\n",
|
" plt.ylim(-25, 25)\n",
|
||||||
" ax.axis(\"off\")\n",
|
" ax.axis(\"off\")\n",
|
||||||
" ax.axis(\"tight\")\n",
|
" ax.axis(\"tight\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # We add the labels for each digit.\n",
|
" # We add the labels for each digit.\n",
|
||||||
" txts = []\n",
|
" for i in range(CLASS_COUNT):\n",
|
||||||
" for i in range(10):\n",
|
|
||||||
" # Position of each label.\n",
|
" # Position of each label.\n",
|
||||||
" xtext, ytext = np.median(x[colors == i, :], axis=0)\n",
|
" xtext, ytext = np.median(points[colors == i, :], axis=0)\n",
|
||||||
" txt = ax.text(xtext, ytext, class_names[i], fontsize=15)\n",
|
" txt = ax.text(xtext, ytext, class_names[i], fontsize=15)\n",
|
||||||
" txt.set_path_effects(\n",
|
" txt.set_path_effects(\n",
|
||||||
" [PathEffects.Stroke(linewidth=5, foreground=\"w\"), PathEffects.Normal()]\n",
|
" [patheffects.Stroke(linewidth=5, foreground=\"w\"), patheffects.Normal()]\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" txts.append(txt)\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def plot_tsne(X, y):\n",
|
"def plot_tsne(features: np.ndarray, labels: np.ndarray) -> None:\n",
|
||||||
" print(\"calculating tsne ...\")\n",
|
" \"\"\"Project `features` to two dimensions with t-SNE and scatter them.\"\"\"\n",
|
||||||
" proj = TSNE(random_state=RS, learning_rate=200, init=\"random\").fit_transform(X)\n",
|
" sys.stdout.write(\"calculating tsne ...\\n\")\n",
|
||||||
" scatter(proj, y)"
|
" proj = TSNE(random_state=RS, learning_rate=200, init=\"random\").fit_transform(\n",
|
||||||
|
" features\n",
|
||||||
|
" )\n",
|
||||||
|
" scatter(proj, labels)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -364,8 +384,8 @@
|
|||||||
")\n",
|
")\n",
|
||||||
"X_test /= 255\n",
|
"X_test /= 255\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(\"Training matrix shape\", X_train.shape)\n",
|
"sys.stdout.write(f\"Training matrix shape {X_train.shape}\\n\")\n",
|
||||||
"print(\"Testing matrix shape\", X_test.shape)"
|
"sys.stdout.write(f\"Testing matrix shape {X_test.shape}\\n\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -447,7 +467,8 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# The first hidden layer is a set of 512 nodes (artificial neurons).\n",
|
"# The first hidden layer is a set of 512 nodes (artificial neurons).\n",
|
||||||
"# Each node will receive an element from each input vector and apply some weight and bias to it.\n",
|
"# Each node will receive an element from each input vector and apply some\n",
|
||||||
|
"# weight and bias to it.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"model.add(\n",
|
"model.add(\n",
|
||||||
" Dense(512, input_shape=(784,))\n",
|
" Dense(512, input_shape=(784,))\n",
|
||||||
@ -462,10 +483,11 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# An \"activation\" is a non-linear function applied to the output of the layer above.\n",
|
"# An \"activation\" is a non-linear function applied to the output of the layer\n",
|
||||||
"# It checks the new value of the node, and decides whether that artifical neuron has fired.\n",
|
"# above. It checks the new value of the node, and decides whether that\n",
|
||||||
"# The Rectified Linear Unit (ReLU) converts all negative inputs to nodes in the next layer to be zero.\n",
|
"# artifical neuron has fired.\n",
|
||||||
"# Those inputs are then not considered to be fired.\n",
|
"# The Rectified Linear Unit (ReLU) converts all negative inputs to nodes in the\n",
|
||||||
|
"# next layer to be zero. Those inputs are then not considered to be fired.\n",
|
||||||
"# Positive values of a node are unchanged.\n",
|
"# Positive values of a node are unchanged.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"model.add(Activation(\"relu\"))"
|
"model.add(Activation(\"relu\"))"
|
||||||
@ -512,8 +534,9 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# The second hidden layer appears identical to our first layer.\n",
|
"# The second hidden layer appears identical to our first layer.\n",
|
||||||
"# However, instead of each of the 512-node receiving 784-inputs from the input image data,\n",
|
"# However, instead of each of the 512-node receiving 784-inputs from the input\n",
|
||||||
"# they receive 512 inputs from the output of the first 512-node layer.\n",
|
"# image data, they receive 512 inputs from the output of the first 512-node\n",
|
||||||
|
"# layer.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"model.add(Dense(512))\n",
|
"model.add(Dense(512))\n",
|
||||||
"model.add(Activation(\"relu\"))\n",
|
"model.add(Activation(\"relu\"))\n",
|
||||||
@ -537,9 +560,10 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# The final layer of 10 neurons in fully-connected to the previous 512-node layer.\n",
|
"# The final layer of 10 neurons in fully-connected to the previous 512-node\n",
|
||||||
"# The final layer of a FCN should be equal to the number of desired classes (10 in this case).\n",
|
"# layer. The final layer of a FCN should be equal to the number of desired\n",
|
||||||
"model.add(Dense(10))"
|
"# classes (10 in this case).\n",
|
||||||
|
"model.add(Dense(CLASS_COUNT))"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -550,8 +574,8 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# The \"softmax\" activation represents a probability distribution over K different possible outcomes.\n",
|
"# The \"softmax\" activation represents a probability distribution over K\n",
|
||||||
"# Its values are all non-negative and sum to 1.\n",
|
"# different possible outcomes. Its values are all non-negative and sum to 1.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"model.add(Activation(\"softmax\"))"
|
"model.add(Activation(\"softmax\"))"
|
||||||
]
|
]
|
||||||
@ -710,8 +734,8 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"score = model.evaluate(X_test, Y_test)\n",
|
"score = model.evaluate(X_test, Y_test)\n",
|
||||||
"print(\"Test score:\", score[0])\n",
|
"sys.stdout.write(f\"Test score: {score[0]}\\n\")\n",
|
||||||
"print(\"Test accuracy:\", score[1])"
|
"sys.stdout.write(f\"Test accuracy: {score[1]}\\n\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -723,18 +747,24 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def plot_confusion_matrix(\n",
|
"def plot_confusion_matrix(\n",
|
||||||
" cm, classes, normalize=False, title=\"Confusion matrix\", cmap=plt.cm.Blues\n",
|
" cm: np.ndarray,\n",
|
||||||
"):\n",
|
" classes: list[str],\n",
|
||||||
" \"\"\"This function prints and plots the confusion matrix.\n",
|
" *,\n",
|
||||||
|
" normalize: bool = False,\n",
|
||||||
|
" title: str = \"Confusion matrix\",\n",
|
||||||
|
" cmap: object = plt.cm.Blues,\n",
|
||||||
|
") -> None:\n",
|
||||||
|
" \"\"\"Print and plot the confusion matrix.\n",
|
||||||
|
"\n",
|
||||||
" Normalization can be applied by setting `normalize=True`.\n",
|
" Normalization can be applied by setting `normalize=True`.\n",
|
||||||
" \"\"\"\n",
|
" \"\"\"\n",
|
||||||
" if normalize:\n",
|
" if normalize:\n",
|
||||||
" cm = cm.astype(\"float\") / cm.sum(axis=1)[:, np.newaxis]\n",
|
" cm = cm.astype(\"float\") / cm.sum(axis=1)[:, np.newaxis]\n",
|
||||||
" print(\"Normalized confusion matrix\")\n",
|
" sys.stdout.write(\"Normalized confusion matrix\\n\")\n",
|
||||||
" else:\n",
|
" else:\n",
|
||||||
" print(\"Confusion matrix, without normalization\")\n",
|
" sys.stdout.write(\"Confusion matrix, without normalization\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" print(cm)\n",
|
" sys.stdout.write(f\"{cm}\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" plt.imshow(cm, interpolation=\"nearest\", cmap=cmap)\n",
|
" plt.imshow(cm, interpolation=\"nearest\", cmap=cmap)\n",
|
||||||
" plt.title(title)\n",
|
" plt.title(title)\n",
|
||||||
@ -756,7 +786,17 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" plt.ylabel(\"True label\")\n",
|
" plt.ylabel(\"True label\")\n",
|
||||||
" plt.xlabel(\"Predicted label\")\n",
|
" plt.xlabel(\"Predicted label\")\n",
|
||||||
" plt.tight_layout()"
|
" plt.tight_layout()\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclass(frozen=True)\n",
|
||||||
|
"class Samples:\n",
|
||||||
|
" \"\"\"A model's output, with the images and true labels it was scored on.\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
" scores: np.ndarray\n",
|
||||||
|
" images: np.ndarray\n",
|
||||||
|
" labels: np.ndarray\n",
|
||||||
|
" names: list[str] | None = None"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -835,29 +875,34 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def show_samples(indices, preds, images, labels, count=3, names=[]):\n",
|
"def show_samples(indices: np.ndarray, samples: Samples, *, count: int = 3) -> None:\n",
|
||||||
|
" \"\"\"Plot a `count` x `count` grid of digits, titled predicted vs expected.\"\"\"\n",
|
||||||
" plt.figure()\n",
|
" plt.figure()\n",
|
||||||
" for i, sample in enumerate(indices[: count**2]):\n",
|
" for i, sample in enumerate(indices[: count**2]):\n",
|
||||||
" pred_id = int(np.argmax(preds[sample]))\n",
|
" pred_id = int(np.argmax(samples.scores[sample]))\n",
|
||||||
" real_id = int(labels[sample])\n",
|
" real_id = int(samples.labels[sample])\n",
|
||||||
" pred_score = preds[sample][pred_id]\n",
|
" pred_score = samples.scores[sample][pred_id]\n",
|
||||||
" real_score = preds[sample][real_id]\n",
|
" real_score = samples.scores[sample][real_id]\n",
|
||||||
" plt.subplot(count, count, i + 1)\n",
|
" plt.subplot(count, count, i + 1)\n",
|
||||||
" plt.imshow(images[sample].reshape(28, 28), cmap=\"gray\", interpolation=\"none\")\n",
|
" plt.imshow(\n",
|
||||||
|
" samples.images[sample].reshape(28, 28), cmap=\"gray\", interpolation=\"none\"\n",
|
||||||
|
" )\n",
|
||||||
" plt.axis(\"off\")\n",
|
" plt.axis(\"off\")\n",
|
||||||
" if len(names) > 0:\n",
|
" if samples.names:\n",
|
||||||
" plt.title(\n",
|
" predicted_label: str | int = samples.names[pred_id]\n",
|
||||||
" f\"P: {names[pred_id]} ({pred_score:.2f})\\nE: {names[real_id]} ({real_score:.2f})\"\n",
|
" real_label: str | int = samples.names[real_id]\n",
|
||||||
" )\n",
|
|
||||||
" else:\n",
|
" else:\n",
|
||||||
" plt.title(\n",
|
" predicted_label = pred_id\n",
|
||||||
" f\"P: {pred_id} ({pred_score:.2f})\\nE: {real_id} ({real_score:.2f})\"\n",
|
" real_label = real_id\n",
|
||||||
" )\n",
|
" plt.title(\n",
|
||||||
|
" f\"P: {predicted_label} ({pred_score:.2f})\\n\"\n",
|
||||||
|
" f\"E: {real_label} ({real_score:.2f})\"\n",
|
||||||
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
" plt.tight_layout()\n",
|
" plt.tight_layout()\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"show_samples(correct_indices, predict, X_test, y_test, 5)"
|
"show_samples(correct_indices, Samples(predict, X_test, y_test), count=5)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -882,7 +927,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"show_samples(incorrect_indices, predict, X_test, y_test, 5)"
|
"show_samples(incorrect_indices, Samples(predict, X_test, y_test), count=5)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1039,7 +1084,8 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# Again, do some formatting\n",
|
"# Again, do some formatting\n",
|
||||||
"# Except we do not flatten each image into a 784-length vector because we want to perform convolutions first\n",
|
"# Except we do not flatten each image into a 784-length vector because we want\n",
|
||||||
|
"# to perform convolutions first\n",
|
||||||
"\n",
|
"\n",
|
||||||
"X_train = X_train.reshape(\n",
|
"X_train = X_train.reshape(\n",
|
||||||
" 60000, 28, 28, 1\n",
|
" 60000, 28, 28, 1\n",
|
||||||
@ -1054,8 +1100,8 @@
|
|||||||
")\n",
|
")\n",
|
||||||
"X_test /= 255\n",
|
"X_test /= 255\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(\"Training matrix shape\", X_train.shape)\n",
|
"sys.stdout.write(f\"Training matrix shape {X_train.shape}\\n\")\n",
|
||||||
"print(\"Testing matrix shape\", X_test.shape)"
|
"sys.stdout.write(f\"Testing matrix shape {X_test.shape}\\n\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1167,8 +1213,9 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# We can then feed our augmented data in batches\n",
|
"# We can then feed our augmented data in batches\n",
|
||||||
"# Besides loss function considerations as before, this method actually results in significant memory savings\n",
|
"# Besides loss function considerations as before, this method actually results\n",
|
||||||
"# because we are actually LOADING the data into the network in batches before processing each batch\n",
|
"# in significant memory savings because we are actually LOADING the data into\n",
|
||||||
|
"# the network in batches before processing each batch\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Before the data was all loaded into memory, but then processed in batches.\n",
|
"# Before the data was all loaded into memory, but then processed in batches.\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -1217,8 +1264,8 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"score = model.evaluate(X_test, Y_test)\n",
|
"score = model.evaluate(X_test, Y_test)\n",
|
||||||
"print(\"Test score:\", score[0])\n",
|
"sys.stdout.write(f\"Test score: {score[0]}\\n\")\n",
|
||||||
"print(\"Test accuracy:\", score[1])"
|
"sys.stdout.write(f\"Test accuracy: {score[1]}\\n\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1271,7 +1318,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"show_samples(correct_indices, predict, X_test, y_test, 5)"
|
"show_samples(correct_indices, Samples(predict, X_test, y_test), count=5)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1287,7 +1334,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"show_samples(incorrect_indices, predict, X_test, y_test, 5)"
|
"show_samples(incorrect_indices, Samples(predict, X_test, y_test), count=5)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1309,15 +1356,16 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def visualize2(model, layer, img, r, c):\n",
|
"def visualize2(\n",
|
||||||
" from keras import Model\n",
|
" model: Sequential, layer: object, img: np.ndarray, r: int, c: int\n",
|
||||||
"\n",
|
") -> None:\n",
|
||||||
|
" \"\"\"Plot the `r` x `c` feature maps `layer` produces for a single image.\"\"\"\n",
|
||||||
" # expand dimensions so that it represents a single 'sample'\n",
|
" # expand dimensions so that it represents a single 'sample'\n",
|
||||||
" img = np.expand_dims(img, axis=0)\n",
|
" img = np.expand_dims(img, axis=0)\n",
|
||||||
" outputs = [layer.output]\n",
|
" outputs = [layer.output]\n",
|
||||||
" model = Model(inputs=model.inputs, outputs=outputs)\n",
|
" feature_model = Model(inputs=model.inputs, outputs=outputs)\n",
|
||||||
" # get feature map for first hidden layer\n",
|
" # get feature map for first hidden layer\n",
|
||||||
" feature_maps = model.predict(img)\n",
|
" feature_maps = feature_model.predict(img)\n",
|
||||||
" for fmap in feature_maps:\n",
|
" for fmap in feature_maps:\n",
|
||||||
" # plot all 64 maps in an 8x8 squares\n",
|
" # plot all 64 maps in an 8x8 squares\n",
|
||||||
" ix = 1\n",
|
" ix = 1\n",
|
||||||
@ -1409,31 +1457,34 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# Loading the specified subset of digits from the MNIST dataset\n",
|
"# Loading the specified subset of digits from the MNIST dataset\n",
|
||||||
"def get_mnist(digits):\n",
|
"def get_mnist(\n",
|
||||||
|
" digits: list[int],\n",
|
||||||
|
") -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:\n",
|
||||||
|
" \"\"\"Reload MNIST keeping only `digits`, scaled to [0, 1] with a channel axis.\"\"\"\n",
|
||||||
" # Reload the MNIST data\n",
|
" # Reload the MNIST data\n",
|
||||||
" (X_train, y_train), (X_test, y_test) = mnist.load_data()\n",
|
" (x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
|
||||||
"\n",
|
"\n",
|
||||||
" train_ids = np.in1d(y_train, digits)\n",
|
" train_ids = np.isin(y_train, digits).ravel()\n",
|
||||||
" test_ids = np.in1d(y_test, digits)\n",
|
" test_ids = np.isin(y_test, digits).ravel()\n",
|
||||||
" X_train = X_train[train_ids]\n",
|
" x_train = x_train[train_ids]\n",
|
||||||
" X_test = X_test[test_ids]\n",
|
" x_test = x_test[test_ids]\n",
|
||||||
" y_train = y_train[train_ids]\n",
|
" y_train = y_train[train_ids]\n",
|
||||||
" y_test = y_test[test_ids]\n",
|
" y_test = y_test[test_ids]\n",
|
||||||
"\n",
|
"\n",
|
||||||
" X_train = np.expand_dims(X_train, axis=-1)\n",
|
" x_train = np.expand_dims(x_train, axis=-1)\n",
|
||||||
" X_test = np.expand_dims(X_test, axis=-1)\n",
|
" x_test = np.expand_dims(x_test, axis=-1)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" X_train = X_train.astype(\n",
|
" x_train = x_train.astype(\n",
|
||||||
" \"float32\"\n",
|
" \"float32\"\n",
|
||||||
" ) # change integers to 32-bit floating point numbers\n",
|
" ) # change integers to 32-bit floating point numbers\n",
|
||||||
" X_test = X_test.astype(\"float32\")\n",
|
" x_test = x_test.astype(\"float32\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" X_train /= (\n",
|
" x_train /= (\n",
|
||||||
" 255 # normalize each value for each pixel for the entire vector for each input\n",
|
" 255 # normalize each value for each pixel for the entire vector for each input\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" X_test /= 255\n",
|
" x_test /= 255\n",
|
||||||
"\n",
|
"\n",
|
||||||
" return X_train, y_train, X_test, y_test"
|
" return x_train, y_train, x_test, y_test"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1445,7 +1496,8 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# Preparing the base model\n",
|
"# Preparing the base model\n",
|
||||||
"def prepare_model(input_shape, class_count):\n",
|
"def prepare_model(input_shape: tuple[int, ...], class_count: int) -> Sequential:\n",
|
||||||
|
" \"\"\"Build the three-convolution-block CNN this section reuses.\"\"\"\n",
|
||||||
" model = Sequential() # Linear stacking of layers\n",
|
" model = Sequential() # Linear stacking of layers\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Convolution Layer 1\n",
|
" # Convolution Layer 1\n",
|
||||||
@ -1485,12 +1537,11 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# One-hot encoding of non-consecutive labels\n",
|
"# One-hot encoding of non-consecutive labels\n",
|
||||||
"def one_hot(labels):\n",
|
"def one_hot(labels: np.ndarray) -> np.ndarray:\n",
|
||||||
" from sklearn.preprocessing import OneHotEncoder\n",
|
" \"\"\"One-hot encode `labels`, which need not be 0..n-1.\"\"\"\n",
|
||||||
"\n",
|
|
||||||
" encoder = OneHotEncoder(categories=\"auto\")\n",
|
" encoder = OneHotEncoder(categories=\"auto\")\n",
|
||||||
" l = labels.reshape(-1, 1)\n",
|
" column = labels.reshape(-1, 1)\n",
|
||||||
" output = encoder.fit_transform(l)\n",
|
" output = encoder.fit_transform(column)\n",
|
||||||
" return output.toarray()"
|
" return output.toarray()"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1502,23 +1553,25 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def test_model(model, X_test, Y_test, y_test, digits):\n",
|
"def test_model(\n",
|
||||||
" score = model.evaluate(X_test, Y_test)\n",
|
" model: Sequential,\n",
|
||||||
" print(\"Test score:\", score[0])\n",
|
" x_test: np.ndarray,\n",
|
||||||
" print(\"Test accuracy:\", score[1])\n",
|
" y_test_onehot: np.ndarray,\n",
|
||||||
|
" y_test: np.ndarray,\n",
|
||||||
|
" digits: list[int],\n",
|
||||||
|
") -> None:\n",
|
||||||
|
" \"\"\"Score `model` on the test split and plot its confusion matrix.\"\"\"\n",
|
||||||
|
" score = model.evaluate(x_test, y_test_onehot)\n",
|
||||||
|
" sys.stdout.write(f\"Test score: {score[0]}\\n\")\n",
|
||||||
|
" sys.stdout.write(f\"Test accuracy: {score[1]}\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # The predict_classes function outputs the highest probability class\n",
|
" # The predict_classes function outputs the highest probability class\n",
|
||||||
" # according to the trained classifier for each input example.\n",
|
" # according to the trained classifier for each input example.\n",
|
||||||
" predicted = model.predict(X_test)\n",
|
" predicted = model.predict(x_test)\n",
|
||||||
" predicted_classes = np.argmax(predicted, axis=1)\n",
|
" predicted_classes = np.argmax(predicted, axis=1)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" actual_classes = [digits[x] for x in predicted_classes]\n",
|
" actual_classes = [digits[x] for x in predicted_classes]\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Check which items we got right / wrong\n",
|
|
||||||
" correct_indices = np.nonzero(actual_classes == y_test)[0]\n",
|
|
||||||
"\n",
|
|
||||||
" incorrect_indices = np.nonzero(actual_classes != y_test)[0]\n",
|
|
||||||
"\n",
|
|
||||||
" cnf_matrix = confusion_matrix(y_test, actual_classes)\n",
|
" cnf_matrix = confusion_matrix(y_test, actual_classes)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" class_names = [str(i) for i in digits]\n",
|
" class_names = [str(i) for i in digits]\n",
|
||||||
@ -1648,7 +1701,10 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# Extract specified range of layers to a new sequential model\n",
|
"# Extract specified range of layers to a new sequential model\n",
|
||||||
"def extract_layers(main_model, starting_layer_ix, ending_layer_ix):\n",
|
"def extract_layers(\n",
|
||||||
|
" main_model: Sequential, starting_layer_ix: int, ending_layer_ix: int\n",
|
||||||
|
") -> Sequential:\n",
|
||||||
|
" \"\"\"Return a new Sequential holding layers `starting_layer_ix`..`ending_layer_ix`.\"\"\"\n",
|
||||||
" # create an empty model\n",
|
" # create an empty model\n",
|
||||||
" new_model = Sequential()\n",
|
" new_model = Sequential()\n",
|
||||||
" for ix in range(starting_layer_ix, ending_layer_ix + 1):\n",
|
" for ix in range(starting_layer_ix, ending_layer_ix + 1):\n",
|
||||||
@ -1892,46 +1948,50 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"import os\n",
|
"# The scheme is spelled out at each use site rather than folded into this\n",
|
||||||
"import urllib\n",
|
"# constant, so that the URL handed to urlopen is visibly https and nothing\n",
|
||||||
|
"# else -- which is what S310 asks for.\n",
|
||||||
|
"QUICKDRAW_HOST = \"storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap\"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def download_and_load(class_names, test_split=0.2, max_items_per_class=10000):\n",
|
"def download_and_load(\n",
|
||||||
" root = \"data\"\n",
|
" class_names: list[str],\n",
|
||||||
" if not os.path.exists(root):\n",
|
" test_split: float = 0.2,\n",
|
||||||
" os.makedirs(root)\n",
|
" max_items_per_class: int = 10000,\n",
|
||||||
|
") -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:\n",
|
||||||
|
" \"\"\"Fetch the Quick, Draw! bitmaps for `class_names` and split them.\"\"\"\n",
|
||||||
|
" root = Path(\"data\")\n",
|
||||||
|
" root.mkdir(parents=True, exist_ok=True)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" print(\"downloading ...\")\n",
|
" sys.stdout.write(\"downloading ...\\n\")\n",
|
||||||
" base = \"https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/\"\n",
|
|
||||||
" for c in class_names:\n",
|
" for c in class_names:\n",
|
||||||
" cc = c.replace(\"_\", \"%20\")\n",
|
" cc = c.replace(\"_\", \"%20\")\n",
|
||||||
" path = base + cc + \".npy\"\n",
|
" path = f\"https://{QUICKDRAW_HOST}/{cc}.npy\"\n",
|
||||||
" print(path)\n",
|
" sys.stdout.write(f\"{path}\\n\")\n",
|
||||||
" if not os.path.exists(f\"{root}/{cc}.npy\"):\n",
|
" destination = root / f\"{cc}.npy\"\n",
|
||||||
" urllib.request.urlretrieve(path, f\"{root}/{cc}.npy\")\n",
|
" if not destination.exists():\n",
|
||||||
|
" with urllib.request.urlopen(path) as response:\n",
|
||||||
|
" destination.write_bytes(response.read())\n",
|
||||||
" else:\n",
|
" else:\n",
|
||||||
" print(\"Already downloaded\")\n",
|
" sys.stdout.write(\"Already downloaded\\n\")\n",
|
||||||
" print(\"loading ...\")\n",
|
" sys.stdout.write(\"loading ...\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # initialize variables\n",
|
" # initialize variables\n",
|
||||||
" x = np.empty([0, 784])\n",
|
" x = np.empty([0, 784])\n",
|
||||||
" y = np.empty([0])\n",
|
" y = np.empty([0])\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # load each data file\n",
|
" # load each data file\n",
|
||||||
" for idx, file in enumerate(class_names):\n",
|
" for idx, name in enumerate(class_names):\n",
|
||||||
" file = file.replace(\"_\", \"%20\")\n",
|
" quoted = name.replace(\"_\", \"%20\")\n",
|
||||||
" data = np.load(f\"{root}/{file}.npy\")\n",
|
" data = np.load(root / f\"{quoted}.npy\")\n",
|
||||||
" data = data[0:max_items_per_class, :]\n",
|
" data = data[0:max_items_per_class, :]\n",
|
||||||
" labels = np.full(data.shape[0], idx)\n",
|
" labels = np.full(data.shape[0], idx)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" x = np.concatenate((x, data), axis=0)\n",
|
" x = np.concatenate((x, data), axis=0)\n",
|
||||||
" y = np.append(y, labels)\n",
|
" y = np.append(y, labels)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" data = None\n",
|
|
||||||
" labels = None\n",
|
|
||||||
"\n",
|
|
||||||
" # randomize the dataset\n",
|
" # randomize the dataset\n",
|
||||||
" permutation = np.random.permutation(y.shape[0])\n",
|
" permutation = np.random.default_rng().permutation(y.shape[0])\n",
|
||||||
" x = x[permutation, :]\n",
|
" x = x[permutation, :]\n",
|
||||||
" y = y[permutation]\n",
|
" y = y[permutation]\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -1947,8 +2007,8 @@
|
|||||||
" x_train = x[test_size : x.shape[0], :]\n",
|
" x_train = x[test_size : x.shape[0], :]\n",
|
||||||
" y_train = y[test_size : y.shape[0]]\n",
|
" y_train = y[test_size : y.shape[0]]\n",
|
||||||
"\n",
|
"\n",
|
||||||
" print(\"Training Data : \", x_train.shape[0])\n",
|
" sys.stdout.write(f\"Training Data : {x_train.shape[0]}\\n\")\n",
|
||||||
" print(\"Testing Data : \", x_test.shape[0])\n",
|
" sys.stdout.write(f\"Testing Data : {x_test.shape[0]}\\n\")\n",
|
||||||
" return x_train, y_train, x_test, y_test"
|
" return x_train, y_train, x_test, y_test"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1977,9 +2037,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"with open(\"categories.txt\") as f:\n",
|
"all_class_names = Path(\"categories.txt\").read_text().splitlines()\n",
|
||||||
" all_class_names = f.readlines()\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"random.seed(RANDOM_SEED)\n",
|
"random.seed(RANDOM_SEED)\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -1990,10 +2048,11 @@
|
|||||||
"# select random 10 classes\n",
|
"# select random 10 classes\n",
|
||||||
"class_names = random.sample(all_class_names, nb_classes)\n",
|
"class_names = random.sample(all_class_names, nb_classes)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# you can also try those \"hard\" classes instead\n",
|
"# The lab also suggests a deliberately hard fixed set instead of a random one:\n",
|
||||||
"# class_names = ['wheel', 'pizza', 'smiley_face', 'apple', 'potato', 'basketball', 'soccer_ball', 'brain', 'clock', 'circle']\n",
|
"# wheel, pizza, smiley_face, apple, potato, basketball, soccer_ball, brain,\n",
|
||||||
|
"# clock and circle.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(class_names)\n",
|
"sys.stdout.write(f\"{class_names}\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"X_train, y_train, X_test, y_test = download_and_load(class_names)\n",
|
"X_train, y_train, X_test, y_test = download_and_load(class_names)\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -2125,8 +2184,8 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"score = model.evaluate(X_test, Y_test)\n",
|
"score = model.evaluate(X_test, Y_test)\n",
|
||||||
"print(\"Test score:\", score[0])\n",
|
"sys.stdout.write(f\"Test score: {score[0]}\\n\")\n",
|
||||||
"print(\"Test accuracy:\", score[1])"
|
"sys.stdout.write(f\"Test accuracy: {score[1]}\\n\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2166,7 +2225,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"show_samples(correct_indices, predicted, X_test, y_test, 5, class_names)"
|
"show_samples(correct_indices, Samples(predicted, X_test, y_test, class_names), count=5)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2177,7 +2236,9 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"show_samples(incorrect_indices, predicted, X_test, y_test, 5, class_names)"
|
"show_samples(\n",
|
||||||
|
" incorrect_indices, Samples(predicted, X_test, y_test, class_names), count=5\n",
|
||||||
|
")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2225,8 +2286,8 @@
|
|||||||
"y_train = y_train.reshape((1, -1))[0]\n",
|
"y_train = y_train.reshape((1, -1))[0]\n",
|
||||||
"y_test = y_test.reshape((1, -1))[0]\n",
|
"y_test = y_test.reshape((1, -1))[0]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(\"Training matrix shape\", X_train.shape, y_train.shape)\n",
|
"sys.stdout.write(f\"Training matrix shape {X_train.shape} {y_train.shape}\\n\")\n",
|
||||||
"print(\"Testing matrix shape\", X_test.shape, y_test.shape)\n",
|
"sys.stdout.write(f\"Testing matrix shape {X_test.shape} {y_test.shape}\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# one-hot format classes\n",
|
"# one-hot format classes\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -2271,23 +2332,22 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"for i in range(10):\n",
|
"strips = []\n",
|
||||||
" img_batch = X_train[y_train == i][0:10]\n",
|
"for class_id in range(CLASS_COUNT):\n",
|
||||||
" img_batch = np.reshape(\n",
|
" img_batch = X_train[y_train == class_id][0:10]\n",
|
||||||
" img_batch,\n",
|
" strips.append(\n",
|
||||||
" (\n",
|
" np.reshape(\n",
|
||||||
" img_batch.shape[0] * img_batch.shape[1],\n",
|
" img_batch,\n",
|
||||||
" img_batch.shape[2],\n",
|
" (\n",
|
||||||
" img_batch.shape[3],\n",
|
" img_batch.shape[0] * img_batch.shape[1],\n",
|
||||||
" ),\n",
|
" img_batch.shape[2],\n",
|
||||||
|
" img_batch.shape[3],\n",
|
||||||
|
" ),\n",
|
||||||
|
" )\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" if i > 0:\n",
|
|
||||||
" img = np.concatenate([img, img_batch], axis=1)\n",
|
|
||||||
" else:\n",
|
|
||||||
" img = img_batch\n",
|
|
||||||
"plt.figure(figsize=(10, 20))\n",
|
"plt.figure(figsize=(10, 20))\n",
|
||||||
"plt.axis(\"off\")\n",
|
"plt.axis(\"off\")\n",
|
||||||
"plt.imshow(img, cmap=\"gray\")"
|
"plt.imshow(np.concatenate(strips, axis=1), cmap=\"gray\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2310,7 +2370,8 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def generate_model():\n",
|
"def generate_model() -> Sequential:\n",
|
||||||
|
" \"\"\"Build the minimal variant: one conv layer straight into the classifier.\"\"\"\n",
|
||||||
" model = Sequential() # Linear stacking of layers\n",
|
" model = Sequential() # Linear stacking of layers\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Convolution Layer 1\n",
|
" # Convolution Layer 1\n",
|
||||||
@ -2335,7 +2396,8 @@
|
|||||||
" return model\n",
|
" return model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def generate_model_default():\n",
|
"def generate_model_default() -> Sequential:\n",
|
||||||
|
" \"\"\"Build the same architecture again, kept as the baseline to compare against.\"\"\"\n",
|
||||||
" model = Sequential() # Linear stacking of layers\n",
|
" model = Sequential() # Linear stacking of layers\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Convolution Layer 1\n",
|
" # Convolution Layer 1\n",
|
||||||
@ -2360,7 +2422,8 @@
|
|||||||
" return model\n",
|
" return model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def generate_model_gemini():\n",
|
"def generate_model_gemini() -> Sequential:\n",
|
||||||
|
" \"\"\"Build the variant Gemini proposed: two conv blocks and heavy dropout.\"\"\"\n",
|
||||||
" model = Sequential()\n",
|
" model = Sequential()\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Convolutional Layers with Max Pooling\n",
|
" # Convolutional Layers with Max Pooling\n",
|
||||||
@ -2386,7 +2449,8 @@
|
|||||||
" return model\n",
|
" return model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def generate_model_chat():\n",
|
"def generate_model_chat() -> Sequential:\n",
|
||||||
|
" \"\"\"Build the variant ChatGPT proposed: four conv layers with batch norm.\"\"\"\n",
|
||||||
" model = Sequential() # Linear stacking of layers\n",
|
" model = Sequential() # Linear stacking of layers\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Convolution Layer 1\n",
|
" # Convolution Layer 1\n",
|
||||||
@ -2530,14 +2594,14 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"for model in models:\n",
|
"for trained in models:\n",
|
||||||
" score = model.evaluate(X_test, Y_test)\n",
|
" score = trained.evaluate(X_test, Y_test)\n",
|
||||||
" print(\"Test score:\", score[0])\n",
|
" sys.stdout.write(f\"Test score: {score[0]}\\n\")\n",
|
||||||
" print(\"Test accuracy:\", score[1])\n",
|
" sys.stdout.write(f\"Test accuracy: {score[1]}\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # The predict_classes function outputs the highest probability class\n",
|
" # The predict_classes function outputs the highest probability class\n",
|
||||||
" # according to the trained classifier for each input example.\n",
|
" # according to the trained classifier for each input example.\n",
|
||||||
" predicted = model.predict(X_test)\n",
|
" predicted = trained.predict(X_test)\n",
|
||||||
" predicted_classes = np.argmax(predicted, axis=1)\n",
|
" predicted_classes = np.argmax(predicted, axis=1)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Check which items we got right / wrong\n",
|
" # Check which items we got right / wrong\n",
|
||||||
@ -2547,7 +2611,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" cnf_matrix = confusion_matrix(y_test, predicted_classes)\n",
|
" cnf_matrix = confusion_matrix(y_test, predicted_classes)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" class_names = [str(i) for i in range(10)]\n",
|
" class_names = [str(i) for i in range(CLASS_COUNT)]\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Plot non-normalized confusion matrix\n",
|
" # Plot non-normalized confusion matrix\n",
|
||||||
" plt.figure()\n",
|
" plt.figure()\n",
|
||||||
@ -2569,24 +2633,27 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def show_samples_rgb(indices, preds, images, labels, count=3, names=[]):\n",
|
"def show_samples_rgb(indices: np.ndarray, samples: Samples, *, count: int = 3) -> None:\n",
|
||||||
|
" \"\"\"Plot a `count` x `count` grid of RGB samples, predicted vs expected.\"\"\"\n",
|
||||||
" plt.figure()\n",
|
" plt.figure()\n",
|
||||||
" for i, sample in enumerate(indices[: count**2]):\n",
|
" for i, sample in enumerate(indices[: count**2]):\n",
|
||||||
" pred_id = int(np.argmax(preds[sample]))\n",
|
" pred_id = int(np.argmax(samples.scores[sample]))\n",
|
||||||
" real_id = int(labels[sample])\n",
|
" real_id = int(samples.labels[sample])\n",
|
||||||
" pred_score = preds[sample][pred_id]\n",
|
" pred_score = samples.scores[sample][pred_id]\n",
|
||||||
" real_score = preds[sample][real_id]\n",
|
" real_score = samples.scores[sample][real_id]\n",
|
||||||
" plt.subplot(count, count, i + 1)\n",
|
" plt.subplot(count, count, i + 1)\n",
|
||||||
" plt.imshow(images[sample], interpolation=\"none\")\n",
|
" plt.imshow(samples.images[sample], interpolation=\"none\")\n",
|
||||||
" plt.axis(\"off\")\n",
|
" plt.axis(\"off\")\n",
|
||||||
" if len(names) > 0:\n",
|
" if samples.names:\n",
|
||||||
" plt.title(\n",
|
" predicted_label: str | int = samples.names[pred_id]\n",
|
||||||
" f\"P: {names[pred_id]} ({pred_score:.2f})\\nE: {names[real_id]} ({real_score:.2f})\"\n",
|
" real_label: str | int = samples.names[real_id]\n",
|
||||||
" )\n",
|
|
||||||
" else:\n",
|
" else:\n",
|
||||||
" plt.title(\n",
|
" predicted_label = pred_id\n",
|
||||||
" f\"P: {pred_id} ({pred_score:.2f})\\nE: {real_id} ({real_score:.2f})\"\n",
|
" real_label = real_id\n",
|
||||||
" )\n",
|
" plt.title(\n",
|
||||||
|
" f\"P: {predicted_label} ({pred_score:.2f})\\n\"\n",
|
||||||
|
" f\"E: {real_label} ({real_score:.2f})\"\n",
|
||||||
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
" plt.tight_layout()"
|
" plt.tight_layout()"
|
||||||
]
|
]
|
||||||
@ -2612,7 +2679,9 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"show_samples_rgb(correct_indices, predicted, X_test, y_test, 5, cifar_names)"
|
"show_samples_rgb(\n",
|
||||||
|
" correct_indices, Samples(predicted, X_test, y_test, cifar_names), count=5\n",
|
||||||
|
")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2636,7 +2705,9 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"show_samples_rgb(incorrect_indices, predicted, X_test, y_test, 5, cifar_names)"
|
"show_samples_rgb(\n",
|
||||||
|
" incorrect_indices, Samples(predicted, X_test, y_test, cifar_names), count=5\n",
|
||||||
|
")"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
230
Programming/twm_4/TWM_KerasIntro.py
Normal file → Executable file
230
Programming/twm_4/TWM_KerasIntro.py
Normal file → Executable file
@ -1,25 +1,24 @@
|
|||||||
# # CIFAR-10
|
#!/usr/bin/env python3
|
||||||
|
"""CIFAR-10 half of the lab notebook, exported so the code can be diffed.
|
||||||
|
|
||||||
# ## Ładowanie zbioru danych
|
Four CNN variants are built and only `model_chat` is trained, then evaluated
|
||||||
|
with a confusion matrix and sample grids of its right and wrong answers.
|
||||||
|
|
||||||
# In[ ]:
|
Needs a GPU: the script exits early without one, exactly as the notebook does.
|
||||||
import sys
|
`print` is `sys.stdout.write` here so the output is identical to the notebook's
|
||||||
|
without a logging handler having to be configured first.
|
||||||
import tensorflow as tf
|
"""
|
||||||
|
|
||||||
# Check if GPU is available
|
|
||||||
print(tf.config.list_physical_devices("GPU"))
|
|
||||||
if tf.config.list_physical_devices("GPU"):
|
|
||||||
print("GPU is available")
|
|
||||||
else:
|
|
||||||
print("GPU is not available")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import tensorflow as tf
|
||||||
from keras.datasets import cifar10
|
from keras.datasets import cifar10
|
||||||
from keras.layers import (
|
from keras.layers import (
|
||||||
Activation,
|
Activation,
|
||||||
@ -36,20 +35,59 @@ from sklearn.metrics import confusion_matrix
|
|||||||
from tensorflow.keras.optimizers import Adam
|
from tensorflow.keras.optimizers import Adam
|
||||||
from tensorflow.keras.preprocessing.image import ImageDataGenerator
|
from tensorflow.keras.preprocessing.image import ImageDataGenerator
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from matplotlib.colors import Colormap
|
||||||
|
|
||||||
|
# CIFAR-10 has ten classes, and the training split has 40000 images once the
|
||||||
|
# generator has held back its 20% for validation.
|
||||||
|
NUM_CLASSES = 10
|
||||||
|
BATCH_SIZE = 128
|
||||||
|
TRAIN_IMAGES = 40000
|
||||||
|
VALID_IMAGES = 10000
|
||||||
|
EPOCHS = 20
|
||||||
|
|
||||||
|
# ## Ładowanie zbioru danych
|
||||||
|
|
||||||
|
# Check if GPU is available
|
||||||
|
sys.stdout.write(f"{tf.config.list_physical_devices('GPU')}\n")
|
||||||
|
if tf.config.list_physical_devices("GPU"):
|
||||||
|
sys.stdout.write("GPU is available\n")
|
||||||
|
else:
|
||||||
|
sys.stdout.write("GPU is not available\n")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Predictions:
|
||||||
|
"""A model's output, with the images and true labels it was produced from."""
|
||||||
|
|
||||||
|
scores: np.ndarray
|
||||||
|
images: np.ndarray
|
||||||
|
labels: np.ndarray
|
||||||
|
names: Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
def plot_confusion_matrix(
|
def plot_confusion_matrix(
|
||||||
cm, classes, normalize=False, title="Confusion matrix", cmap=plt.cm.Blues
|
cm: np.ndarray,
|
||||||
):
|
classes: Sequence[str],
|
||||||
"""This function prints and plots the confusion matrix.
|
*,
|
||||||
|
normalize: bool = False,
|
||||||
|
title: str = "Confusion matrix",
|
||||||
|
cmap: Colormap = plt.cm.Blues,
|
||||||
|
) -> None:
|
||||||
|
"""Print and plot the confusion matrix.
|
||||||
|
|
||||||
Normalization can be applied by setting `normalize=True`.
|
Normalization can be applied by setting `normalize=True`.
|
||||||
"""
|
"""
|
||||||
if normalize:
|
if normalize:
|
||||||
cm = cm.astype("float") / cm.sum(axis=1)[:, np.newaxis]
|
cm = cm.astype("float") / cm.sum(axis=1)[:, np.newaxis]
|
||||||
print("Normalized confusion matrix")
|
sys.stdout.write("Normalized confusion matrix\n")
|
||||||
else:
|
else:
|
||||||
print("Confusion matrix, without normalization")
|
sys.stdout.write("Confusion matrix, without normalization\n")
|
||||||
|
|
||||||
print(cm)
|
sys.stdout.write(f"{cm}\n")
|
||||||
|
|
||||||
plt.imshow(cm, interpolation="nearest", cmap=cmap)
|
plt.imshow(cm, interpolation="nearest", cmap=cmap)
|
||||||
plt.title(title)
|
plt.title(title)
|
||||||
@ -87,15 +125,13 @@ X_test /= 255
|
|||||||
y_train = y_train.reshape((1, -1))[0]
|
y_train = y_train.reshape((1, -1))[0]
|
||||||
y_test = y_test.reshape((1, -1))[0]
|
y_test = y_test.reshape((1, -1))[0]
|
||||||
|
|
||||||
print("Training matrix shape", X_train.shape, y_train.shape)
|
sys.stdout.write(f"Training matrix shape {X_train.shape} {y_train.shape}\n")
|
||||||
print("Testing matrix shape", X_test.shape, y_test.shape)
|
sys.stdout.write(f"Testing matrix shape {X_test.shape} {y_test.shape}\n")
|
||||||
|
|
||||||
# one-hot format classes
|
# one-hot format classes
|
||||||
|
|
||||||
nb_classes = 10
|
Y_train = to_categorical(y_train, NUM_CLASSES)
|
||||||
|
Y_test = to_categorical(y_test, NUM_CLASSES)
|
||||||
Y_train = to_categorical(y_train, nb_classes)
|
|
||||||
Y_test = to_categorical(y_test, nb_classes)
|
|
||||||
|
|
||||||
cifar_names = [
|
cifar_names = [
|
||||||
"airplane",
|
"airplane",
|
||||||
@ -113,23 +149,23 @@ cifar_names = [
|
|||||||
|
|
||||||
# ## Podgląd zbioru treningowego
|
# ## Podgląd zbioru treningowego
|
||||||
|
|
||||||
# In[ ]:
|
# Ten samples of each class, stacked into one wide strip. Built as a list and
|
||||||
|
# concatenated once: growing `img` in place only bound it in the loop's `else`
|
||||||
|
# branch, which reads as a use-before-assignment and is what ruff flagged.
|
||||||
for i in range(10):
|
strips = []
|
||||||
img_batch = X_train[y_train == i][0:10]
|
for class_id in range(NUM_CLASSES):
|
||||||
img_batch = np.reshape(
|
img_batch = X_train[y_train == class_id][0:10]
|
||||||
img_batch,
|
strips.append(
|
||||||
(
|
np.reshape(
|
||||||
img_batch.shape[0] * img_batch.shape[1],
|
img_batch,
|
||||||
img_batch.shape[2],
|
(
|
||||||
img_batch.shape[3],
|
img_batch.shape[0] * img_batch.shape[1],
|
||||||
),
|
img_batch.shape[2],
|
||||||
|
img_batch.shape[3],
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if i > 0:
|
img = np.concatenate(strips, axis=1)
|
||||||
img = np.concatenate([img, img_batch], axis=1)
|
|
||||||
else:
|
|
||||||
img = img_batch
|
|
||||||
plt.figure(figsize=(10, 20))
|
plt.figure(figsize=(10, 20))
|
||||||
plt.axis("off")
|
plt.axis("off")
|
||||||
plt.imshow(img, cmap="gray")
|
plt.imshow(img, cmap="gray")
|
||||||
@ -137,10 +173,9 @@ plt.imshow(img, cmap="gray")
|
|||||||
|
|
||||||
# ## Przygotowanie modelu
|
# ## Przygotowanie modelu
|
||||||
|
|
||||||
# In[ ]:
|
|
||||||
|
|
||||||
|
def generate_model() -> Sequential:
|
||||||
def generate_model():
|
"""Build the minimal variant: one conv layer straight into the classifier."""
|
||||||
model = Sequential() # Linear stacking of layers
|
model = Sequential() # Linear stacking of layers
|
||||||
|
|
||||||
# Convolution Layer 1
|
# Convolution Layer 1
|
||||||
@ -154,7 +189,7 @@ def generate_model():
|
|||||||
# ...
|
# ...
|
||||||
|
|
||||||
# Fully Connected Layer
|
# Fully Connected Layer
|
||||||
model.add(Dense(10)) # final 10 FC nodes
|
model.add(Dense(NUM_CLASSES)) # final 10 FC nodes
|
||||||
model.add(Activation("softmax")) # softmax activation
|
model.add(Activation("softmax")) # softmax activation
|
||||||
|
|
||||||
model.summary()
|
model.summary()
|
||||||
@ -165,7 +200,8 @@ def generate_model():
|
|||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def generate_model_default():
|
def generate_model_default() -> Sequential:
|
||||||
|
"""Build the same architecture again, kept as the baseline to compare against."""
|
||||||
model = Sequential() # Linear stacking of layers
|
model = Sequential() # Linear stacking of layers
|
||||||
|
|
||||||
# Convolution Layer 1
|
# Convolution Layer 1
|
||||||
@ -179,7 +215,7 @@ def generate_model_default():
|
|||||||
# ...
|
# ...
|
||||||
|
|
||||||
# Fully Connected Layer
|
# Fully Connected Layer
|
||||||
model.add(Dense(10)) # final 10 FC nodes
|
model.add(Dense(NUM_CLASSES)) # final 10 FC nodes
|
||||||
model.add(Activation("softmax")) # softmax activation
|
model.add(Activation("softmax")) # softmax activation
|
||||||
|
|
||||||
model.summary()
|
model.summary()
|
||||||
@ -190,7 +226,8 @@ def generate_model_default():
|
|||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def generate_model_gemini():
|
def generate_model_gemini() -> Sequential:
|
||||||
|
"""Build the variant Gemini proposed: two conv blocks and heavy dropout."""
|
||||||
model = Sequential()
|
model = Sequential()
|
||||||
|
|
||||||
# Convolutional Layers with Max Pooling
|
# Convolutional Layers with Max Pooling
|
||||||
@ -204,7 +241,7 @@ def generate_model_gemini():
|
|||||||
model.add(Flatten())
|
model.add(Flatten())
|
||||||
model.add(Dense(128, activation="relu"))
|
model.add(Dense(128, activation="relu"))
|
||||||
model.add(Dropout(0.5)) # Regularization
|
model.add(Dropout(0.5)) # Regularization
|
||||||
model.add(Dense(10, activation="softmax"))
|
model.add(Dense(NUM_CLASSES, activation="softmax"))
|
||||||
|
|
||||||
# Model Compilation
|
# Model Compilation
|
||||||
model.compile(
|
model.compile(
|
||||||
@ -216,7 +253,11 @@ def generate_model_gemini():
|
|||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def generate_model_chat():
|
def generate_model_chat() -> Sequential:
|
||||||
|
"""Build the variant ChatGPT proposed: four conv layers with batch norm.
|
||||||
|
|
||||||
|
This is the only one that actually gets trained below.
|
||||||
|
"""
|
||||||
model = Sequential() # Linear stacking of layers
|
model = Sequential() # Linear stacking of layers
|
||||||
|
|
||||||
# Convolution Layer 1
|
# Convolution Layer 1
|
||||||
@ -251,7 +292,7 @@ def generate_model_chat():
|
|||||||
model.add(Dropout(0.6))
|
model.add(Dropout(0.6))
|
||||||
|
|
||||||
# Output Layer
|
# Output Layer
|
||||||
model.add(Dense(10)) # final 10 FC nodes
|
model.add(Dense(NUM_CLASSES)) # final 10 FC nodes
|
||||||
model.add(Activation("softmax")) # softmax activation
|
model.add(Activation("softmax")) # softmax activation
|
||||||
|
|
||||||
model.summary()
|
model.summary()
|
||||||
@ -263,9 +304,6 @@ def generate_model_chat():
|
|||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
# In[ ]:
|
|
||||||
|
|
||||||
|
|
||||||
model = generate_model()
|
model = generate_model()
|
||||||
model_default = generate_model_default()
|
model_default = generate_model_default()
|
||||||
model_gemini = generate_model_gemini()
|
model_gemini = generate_model_gemini()
|
||||||
@ -275,9 +313,6 @@ models = [model_chat]
|
|||||||
|
|
||||||
# ## Trening
|
# ## Trening
|
||||||
|
|
||||||
# In[ ]:
|
|
||||||
|
|
||||||
|
|
||||||
gen = ImageDataGenerator(
|
gen = ImageDataGenerator(
|
||||||
rotation_range=8,
|
rotation_range=8,
|
||||||
width_shift_range=0.08,
|
width_shift_range=0.08,
|
||||||
@ -287,38 +322,35 @@ gen = ImageDataGenerator(
|
|||||||
validation_split=0.2,
|
validation_split=0.2,
|
||||||
)
|
)
|
||||||
|
|
||||||
train_generator = gen.flow(X_train, Y_train, batch_size=128, subset="training")
|
train_generator = gen.flow(X_train, Y_train, batch_size=BATCH_SIZE, subset="training")
|
||||||
valid_generator = gen.flow(X_train, Y_train, batch_size=128, subset="validation")
|
valid_generator = gen.flow(X_train, Y_train, batch_size=BATCH_SIZE, subset="validation")
|
||||||
|
|
||||||
|
|
||||||
# In[ ]:
|
|
||||||
|
|
||||||
|
|
||||||
# Max 20 epoch
|
# Max 20 epoch
|
||||||
for model in models:
|
for trained in models:
|
||||||
model.fit(
|
trained.fit(
|
||||||
train_generator,
|
train_generator,
|
||||||
steps_per_epoch=40000 // 128,
|
steps_per_epoch=TRAIN_IMAGES // BATCH_SIZE,
|
||||||
epochs=20,
|
epochs=EPOCHS,
|
||||||
verbose=1,
|
verbose=1,
|
||||||
validation_data=valid_generator,
|
validation_data=valid_generator,
|
||||||
validation_steps=10000 // 128,
|
validation_steps=VALID_IMAGES // BATCH_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ## Test
|
# ## Test
|
||||||
|
|
||||||
# In[ ]:
|
for evaluated in models:
|
||||||
|
score = evaluated.evaluate(X_test, Y_test)
|
||||||
for model in models:
|
sys.stdout.write(f"Test score: {score[0]}\n")
|
||||||
score = model.evaluate(X_test, Y_test)
|
sys.stdout.write(f"Test accuracy: {score[1]}\n")
|
||||||
print("Test score:", score[0])
|
|
||||||
print("Test accuracy:", score[1])
|
|
||||||
|
|
||||||
# The predict_classes function outputs the highest probability class
|
# The predict_classes function outputs the highest probability class
|
||||||
# according to the trained classifier for each input example.
|
# according to the trained classifier for each input example.
|
||||||
predicted = model.predict(X_test)
|
predicted = evaluated.predict(X_test)
|
||||||
predicted_classes = np.argmax(predicted, axis=1)
|
predicted_classes = np.argmax(predicted, axis=1)
|
||||||
|
results = Predictions(
|
||||||
|
scores=predicted, images=X_test, labels=y_test, names=cifar_names
|
||||||
|
)
|
||||||
|
|
||||||
# Check which items we got right / wrong
|
# Check which items we got right / wrong
|
||||||
correct_indices = np.nonzero(predicted_classes == y_test)[0]
|
correct_indices = np.nonzero(predicted_classes == y_test)[0]
|
||||||
@ -327,7 +359,7 @@ for model in models:
|
|||||||
|
|
||||||
cnf_matrix = confusion_matrix(y_test, predicted_classes)
|
cnf_matrix = confusion_matrix(y_test, predicted_classes)
|
||||||
|
|
||||||
class_names = [str(i) for i in range(10)]
|
class_names = [str(i) for i in range(NUM_CLASSES)]
|
||||||
|
|
||||||
# Plot non-normalized confusion matrix
|
# Plot non-normalized confusion matrix
|
||||||
plt.figure()
|
plt.figure()
|
||||||
@ -338,42 +370,38 @@ for model in models:
|
|||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
# In[ ]:
|
def show_samples_rgb(
|
||||||
|
indices: np.ndarray, results: Predictions, *, count: int = 3
|
||||||
|
) -> None:
|
||||||
def show_samples_rgb(indices, preds, images, labels, count=3, names=[]):
|
"""Plot a `count` x `count` grid of samples, titled predicted vs expected."""
|
||||||
plt.figure()
|
plt.figure()
|
||||||
for i, sample in enumerate(indices[: count**2]):
|
for i, sample in enumerate(indices[: count**2]):
|
||||||
pred_id = int(np.argmax(preds[sample]))
|
pred_id = int(np.argmax(results.scores[sample]))
|
||||||
real_id = int(labels[sample])
|
real_id = int(results.labels[sample])
|
||||||
pred_score = preds[sample][pred_id]
|
pred_score = results.scores[sample][pred_id]
|
||||||
real_score = preds[sample][real_id]
|
real_score = results.scores[sample][real_id]
|
||||||
plt.subplot(count, count, i + 1)
|
plt.subplot(count, count, i + 1)
|
||||||
plt.imshow(images[sample], interpolation="none")
|
plt.imshow(results.images[sample], interpolation="none")
|
||||||
plt.axis("off")
|
plt.axis("off")
|
||||||
if len(names) > 0:
|
if results.names:
|
||||||
plt.title(
|
predicted_label: str | int = results.names[pred_id]
|
||||||
f"P: {names[pred_id]} ({pred_score:.2f})\nE: {names[real_id]} ({real_score:.2f})"
|
real_label: str | int = results.names[real_id]
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
plt.title(
|
predicted_label = pred_id
|
||||||
f"P: {pred_id} ({pred_score:.2f})\nE: {real_id} ({real_score:.2f})"
|
real_label = real_id
|
||||||
)
|
plt.title(
|
||||||
|
f"P: {predicted_label} ({pred_score:.2f})\n"
|
||||||
|
f"E: {real_label} ({real_score:.2f})"
|
||||||
|
)
|
||||||
|
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
|
|
||||||
|
|
||||||
# ## Poprawne klasyfikacje
|
# ## Poprawne klasyfikacje
|
||||||
|
|
||||||
# In[ ]:
|
show_samples_rgb(correct_indices, results, count=5)
|
||||||
|
|
||||||
|
|
||||||
show_samples_rgb(correct_indices, predicted, X_test, y_test, 5, cifar_names)
|
|
||||||
|
|
||||||
|
|
||||||
# ## Błędne klasyfikacje
|
# ## Błędne klasyfikacje
|
||||||
|
|
||||||
# In[ ]:
|
show_samples_rgb(incorrect_indices, results, count=5)
|
||||||
|
|
||||||
|
|
||||||
show_samples_rgb(incorrect_indices, predicted, X_test, y_test, 5, cifar_names)
|
|
||||||
|
|||||||
7
Programming/twm_4/twm.py
Normal file → Executable file
7
Programming/twm_4/twm.py
Normal file → Executable file
@ -1,3 +1,8 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Smoke check: report which TensorFlow the notebook will actually run against."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
print(tf.__version__)
|
sys.stdout.write(f"{tf.__version__}\n")
|
||||||
|
|||||||
@ -32,23 +32,36 @@ own docs and the rest of this fleet carry. A test asserts by raising.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Baseline, before any of this | 9,281 |
|
| Baseline, before any of this | 9,281 |
|
||||||
| After the format + safe-fix pass and excluding `TRAK/sightpy` | 3,686 |
|
| After the format + safe-fix pass and excluding `TRAK/sightpy` | 3,686 |
|
||||||
| **Now** | **651** |
|
| **Now** | **423** |
|
||||||
|
|
||||||
Courses at zero: `WDWR`, `SPD`, `MOM`, `ECRYPT`, `ECRYPT_PROJECT`, `ECOTE`,
|
Courses at zero: `WDWR`, `SPD`, `MOM`, `ECRYPT`, `ECRYPT_PROJECT`, `ECOTE`,
|
||||||
`NLP`, `EARIN`, `PORR`, `TRAK`. What is left, by course:
|
`NLP`, `EARIN`, `PORR`, `TRAK`, `twm_4`. What is left, by course:
|
||||||
|
|
||||||
| Course | ruff | Can it be verified by running? |
|
| Course | ruff | Can it be verified by running? |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `twm_4` | 228 | No: `jupyter` on PATH is a dead pipx shim. `pip install jupyter` in a venv would fix that |
|
|
||||||
| `ERSMS-project` | 156 | No: docker-compose course |
|
| `ERSMS-project` | 156 | No: docker-compose course |
|
||||||
| `PSD` | 152 | No: `run.sh` predates the harness and installs packages with sudo |
|
| `PSD` | 152 | No: `run.sh` predates the harness and installs packages with sudo |
|
||||||
| `PBAD` | 115 | No: same dead jupyter shim |
|
| `PBAD` | 115 | No: same dead jupyter shim |
|
||||||
|
|
||||||
**Before running the notebook courses, fix `scripts/course_run.sh` first.**
|
`course_run.sh`'s notebook branch is fixed: it executes to a temp
|
||||||
Its notebook branch is `jupyter nbconvert --execute --inplace`, and all four
|
`--output-dir`, probes jupyter by running it rather than by `command -v`, and
|
||||||
notebooks are tracked WITH their outputs -- that would overwrite a committed
|
`course_main` now fails if a run changed anything git can see under the course
|
||||||
deliverable. This exact bug has already been found twice (TRAK's
|
root. `PBAD` is still blocked on jupyter itself -- the binary on PATH is a
|
||||||
`outputs/output.png`, SPD's `results_*.png`); execute to a temp path instead.
|
pipx shim whose interpreter is gone, so `--probe` reports "needs jupyter".
|
||||||
|
|
||||||
|
**A notebook does not need to be executed to be linted.** ruff reads `.ipynb`
|
||||||
|
natively and rewrites `source` only, so the check that the deliverable is
|
||||||
|
intact is a hash of everything EXCEPT `source`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
stripped = {k: v for k, v in nb.items() if k != "cells"}
|
||||||
|
stripped["cells"] = [{k: v for k, v in c.items() if k != "source"} for c in nb["cells"]]
|
||||||
|
```
|
||||||
|
|
||||||
|
That hash was identical across all four passes on `twm_4`. The notebook also
|
||||||
|
round-trips byte-for-byte through
|
||||||
|
`json.dumps(nb, indent=1, ensure_ascii=False) + "\n"`, which is what makes
|
||||||
|
editing cells programmatically safe -- check that before touching PBAD's three.
|
||||||
|
|
||||||
## mypy has NOT been run against any of this
|
## mypy has NOT been run against any of this
|
||||||
|
|
||||||
@ -64,6 +77,7 @@ RUFF findings unless it says otherwise**; only `TRAK` has been type-checked.
|
|||||||
| `NLP` | 29 |
|
| `NLP` | 29 |
|
||||||
| `EARIN/lab1` alone | 46 |
|
| `EARIN/lab1` alone | 46 |
|
||||||
| `TRAK` | **0** -- cleared 2026-08-29 |
|
| `TRAK` | **0** -- cleared 2026-08-29 |
|
||||||
|
| `twm_4` | **0** -- cleared 2026-08-29 |
|
||||||
| `PSD/zin1` | 5 |
|
| `PSD/zin1` | 5 |
|
||||||
|
|
||||||
Two structural notes for whoever runs it next:
|
Two structural notes for whoever runs it next:
|
||||||
@ -90,7 +104,7 @@ Two structural notes for whoever runs it next:
|
|||||||
| Type | Count | Note |
|
| Type | Count | Note |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `.txt` | 27 | Blocked -- see `TODO-file-length-250.md` |
|
| `.txt` | 27 | Blocked -- see `TODO-file-length-250.md` |
|
||||||
| `.py` | 10 | Several GREW during the lint pass, because docstrings and annotations are lines: `NLP/gpt_chunks.py` 911 -> 973, `EARIN/lab2/main.py` 581 -> 647, `PORR/.../linear_algebra_utils.py` 422 -> 553. Split them AFTER the lint work, not during. TRAK is already clear: its three over-cap files were split in the same pass that linted them, each verified by a byte-identical render |
|
| `.py` | 10 | Several GREW during the lint pass, because docstrings and annotations are lines: `NLP/gpt_chunks.py` 911 -> 973, `EARIN/lab2/main.py` 581 -> 647, `PORR/.../linear_algebra_utils.py` 422 -> 553. Split them AFTER the lint work, not during. TRAK is already clear: its three over-cap files were split in the same pass that linted them, each verified by a byte-identical render. `twm_4/TWM_KerasIntro.py` joined the list at 407 lines and should NOT be split: it is an nbconvert export, and a partial one |
|
||||||
| `.cpp` | 9 | |
|
| `.cpp` | 9 | |
|
||||||
| `.c` | 4 | |
|
| `.c` | 4 | |
|
||||||
| `.java` | 2 | |
|
| `.java` | 2 | |
|
||||||
|
|||||||
@ -77,7 +77,16 @@ exclude = [
|
|||||||
# not a suppression of our own code: without it mypy cannot import them at all
|
# not a suppression of our own code: without it mypy cannot import them at all
|
||||||
# and reports nothing else about the file that uses them.
|
# and reports nothing else about the file that uses them.
|
||||||
[[tool.mypy.overrides]]
|
[[tool.mypy.overrides]]
|
||||||
module = ["Imath.*", "OpenEXR.*", "OpenGL.*", "matplotlib.*"]
|
module = [
|
||||||
|
"Imath.*",
|
||||||
|
"OpenEXR.*",
|
||||||
|
"OpenGL.*",
|
||||||
|
"matplotlib.*",
|
||||||
|
"keras.*",
|
||||||
|
"tensorflow.*",
|
||||||
|
"sklearn.*",
|
||||||
|
"seaborn.*",
|
||||||
|
]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user