import%20marimo%0A%0A__generated_with%20%3D%20%220.18.4%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Image%20Classification%20Without%20Convolutions%0A%0A%20%20%20%20%5BBlog%20post%5D(https%3A%2F%2Foswalt.dev%2F2025%2F12%2Fclassifying-mnist-handwritten-digits-with-a-fully-connected-neural-network%2F)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Load%20Data%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20torch%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20torchvision%0A%20%20%20%20import%20torchvision.transforms%20as%20transforms%0A%0A%20%20%20%20def%20load_data(batch_size%2C%20normalize%3DTrue%2C%20shuffle%3DTrue)%3A%0A%0A%20%20%20%20%20%20%20%20%23%20Transformations%20to%20apply%20to%20both%20datasets.%0A%20%20%20%20%20%20%20%20transform%3Dtransforms.Compose(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Converts%20a%20PIL%20Image%20or%20numpy.ndarray%20(H%20x%20W%20x%20C)%20in%20the%20range%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%5B0%2C%20255%5D%20to%20a%20torch.FloatTensor%20of%20shape%20(C%20x%20H%20x%20W)%20in%20the%20range%20%5B0.0%2C%201.0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20transforms.ToTensor()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20This%20standardizes%20each%20tensor%20with%20a%20given%20mean%20and%20standard%20deviation.%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20(the%20values%20provided%20here%20are%20the%20mean%20and%20std%20deviation%20for%20the%20MNIST%20dataset)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20transforms.Normalize((0.1307%2C)%2C%20(0.3081%2C))%0A%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%23%20Load%20the%20datasets%0A%20%20%20%20%20%20%20%20train_dataset%20%3D%20torchvision.datasets.MNIST(%0A%20%20%20%20%20%20%20%20%20%20%20%20root%3D'.%2Ftmp'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20train%3DTrue%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20download%3DTrue%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20transform%3Dtransform%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20test_dataset%20%3D%20torchvision.datasets.MNIST(%0A%20%20%20%20%20%20%20%20%20%20%20%20root%3D'.%2Ftmp'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20train%3DFalse%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20download%3DTrue%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20transform%3Dtransform%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Create%20DataLoaders%20for%20batching%20and%20shuffling%0A%20%20%20%20%20%20%20%20train_loader%20%3D%20torch.utils.data.DataLoader(%0A%20%20%20%20%20%20%20%20%20%20%20%20train_dataset%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3Dbatch_size%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20shuffle%3Dshuffle%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20test_loader%20%3D%20torch.utils.data.DataLoader(%0A%20%20%20%20%20%20%20%20%20%20%20%20test_dataset%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3Dbatch_size%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20shuffle%3DFalse%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20return%20train_loader%2C%20test_loader%0A%20%20%20%20return%20load_data%2C%20plt%2C%20torch%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Batch%20Visualization%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(load_data%2C%20plt)%3A%0A%20%20%20%20def%20visualize_batch(dataloader%2C%20batch_size%3D16)%3A%0A%20%20%20%20%20%20%20%20%23%20Get%20one%20batch%0A%20%20%20%20%20%20%20%20images%2C%20labels%20%3D%20next(iter(dataloader))%0A%0A%20%20%20%20%20%20%20%20num_rows%20%3D%204%0A%20%20%20%20%20%20%20%20num_cols%20%3D%20batch_size%20%2F%2F%20num_rows%0A%20%20%20%20%20%20%20%20fig%2C%20axes%20%3D%20plt.subplots(num_rows%2C%20num_cols%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20figsize%3D(12%2C%2012))%0A%20%20%20%20%20%20%20%20axes%20%3D%20axes.flatten()%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(batch_size)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20image_np%20%3D%20images%5Bi%5D.squeeze().numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20image%2C%20label%20%3D%20images%5Bi%5D%2C%20labels%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20axes%5Bi%5D.imshow(image_np%2C%20cmap%3D'gray')%0A%20%20%20%20%20%20%20%20%20%20%20%20axes%5Bi%5D.set_title(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Label%3A%20%7Blabel.item()%7D%5Cn'%20%2B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Interesting%20-%20label%20is%20also%20a%20tensor!%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Label%20type%3A%20%7Btype(label)%7D%5Cn'%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Image%20type%3A%20%7Btype(image)%7D%5Cn'%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Image%20dimensions%3A%20%7Bimage.dim()%7D%5Cn'%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Image%20shape%3A%20%7Bimage.shape%7D%5Cn'%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Image%20dtype%3A%20%7Bimage.dtype%7D%5Cn'%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Value%20range%3A%20%5B%7Bimage.min()%3A.3f%7D%2C%20%7Bimage.max()%3A.3f%7D%5D%5Cn'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fontsize%3D10)%0A%20%20%20%20%20%20%20%20%20%20%20%20axes%5Bi%5D.axis('off')%0A%0A%20%20%20%20%20%20%20%20plt.suptitle('Random%20Batch%20from%20DataLoader'%2C%20fontsize%3D14)%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20plt.show()%0A%20%20%20%20%20%20%20%20plt.close()%0A%0A%20%20%20%20tmp_train_loader%2C%20_%20%3D%20load_data(64)%0A%20%20%20%20visualize_batch(tmp_train_loader)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Network%20Definition%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20from%20torch%20import%20nn%0A%0A%20%20%20%20class%20Network(nn.Module)%3A%0A%20%20%20%20%20%20%20%20def%20__init__(self%2C%20layer_sizes)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20super().__init__()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20We're%20using%20a%20fully%20connected%20network%20here%2C%20which%20will%20use%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20https%3A%2F%2Fdocs.pytorch.org%2Fdocs%2Fstable%2Fgenerated%2Ftorch.nn.Linear.html%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20This%20requires%20a%201D%20vector%2C%20so%20we%20need%20to%20convert%20our%2028x28%20grid%20to%20a%201D%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20vector%20of%20784%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20self.flatten%20%3D%20nn.Flatten()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Input%20size%20is%20fixed%20for%20MNIST%20(28x28)%0A%20%20%20%20%20%20%20%20%20%20%20%20input_size%20%3D%2028%20*%2028%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Output%20size%20is%20also%20fixed%20(numbers%200-9)%0A%20%20%20%20%20%20%20%20%20%20%20%20output_size%20%3D%2010%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Combine%20input%20size%2C%20hidden%20layers%2C%20and%20output%20size%20into%20one%20list%0A%20%20%20%20%20%20%20%20%20%20%20%20sizes%20%3D%20%5Binput_size%5D%20%2B%20layer_sizes%20%2B%20%5Boutput_size%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Construct%20hidden%20layers%0A%20%20%20%20%20%20%20%20%20%20%20%20layers%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i%20in%20range(len(sizes)%20-%202)%3A%20%20%23%20all%20except%20the%20last%20layer%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20layers.append(nn.Linear(sizes%5Bi%5D%2C%20sizes%5Bi%20%2B%201%5D))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20layers.append(nn.ReLU())%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20the%20final%20output%20layer%20(no%20activation)%0A%20%20%20%20%20%20%20%20%20%20%20%20layers.append(nn.Linear(sizes%5B-2%5D%2C%20sizes%5B-1%5D))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Wrap%20in%20a%20Sequential%20block%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20I%20like%20this%20pattern%20from%20https%3A%2F%2Fdocs.pytorch.org%2Ftutorials%2Fbeginner%2Fbasics%2Fbuildmodel_tutorial.html%23define-the-class%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Allows%20me%20to%20define%20it%20here%20in%20the%20constructor%20and%20simply%20call%20it%20in%20the%20forward%20pass.%0A%20%20%20%20%20%20%20%20%20%20%20%20self.linear_relu_stack%20%3D%20nn.Sequential(*layers)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Remember%20each%20call%20to%20Linear%20is%20describing%20the%20edges%20of%20the%20network%2C%20so%20we'll%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20have%20three%20calls%20-%20two%20for%20hidden%2C%20one%20for%20output.%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%3D%3D%3DNetwork%3D%3D%3D%5Cn%22%20%2B%20str(self.linear_relu_stack)%20%2B%20%22%5Cn%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%22)%0A%0A%20%20%20%20%20%20%20%20def%20forward(self%2C%20x)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20x%20%3D%20self.flatten(x)%0A%20%20%20%20%20%20%20%20%20%20%20%20logits%20%3D%20self.linear_relu_stack(x)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20logits%0A%20%20%20%20return%20Network%2C%20nn%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Training%20Loop%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nn%2C%20torch)%3A%0A%20%20%20%20def%20train_loop(dataloader%2C%20model%2C%20learning_rate%2C%20batch_size%3D64)%3A%0A%0A%20%20%20%20%20%20%20%20%23%20Defaults%20to%20reduction%3D'mean'%20which%20means%20it%20will%20provide%20an%20average%0A%20%20%20%20%20%20%20%20%23%20loss%20for%20the%20entire%20batch%0A%20%20%20%20%20%20%20%20loss_fn%20%3D%20nn.CrossEntropyLoss()%0A%0A%20%20%20%20%20%20%20%20%23%20model.parameters()%20gives%20the%20optimizer%20a%20generator%20containing%20references%0A%20%20%20%20%20%20%20%20%23%20to%20the%20tensors%20of%20the%20model%20containing%20the%20weights%20and%20biases.%0A%20%20%20%20%20%20%20%20optimizer%20%3D%20torch.optim.SGD(model.parameters()%2C%20lr%3Dlearning_rate)%0A%0A%20%20%20%20%20%20%20%20%23%20So%20we%20can%20show%20%25%20progress%20as%20we%20iterate%20over%20the%20dataset%0A%20%20%20%20%20%20%20%20total_size%20%3D%20len(dataloader.dataset)%0A%0A%20%20%20%20%20%20%20%20%23%20Set%20the%20model%20to%20training%20mode%20-%20important%20for%20batch%20normalization%20and%0A%20%20%20%20%20%20%20%20%23%20dropout%20layers.%20Unnecessary%20in%20this%20particular%20situation%20but%20added%0A%20%20%20%20%20%20%20%20%23%20for%20best%20practices%0A%20%20%20%20%20%20%20%20model.train()%0A%20%20%20%20%20%20%20%20for%20batch_idx%2C%20(images%2C%20labels)%20in%20enumerate(dataloader)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Forward%20pass%20-%20the%20model%20(and%20loss_fn)%20both%20receive%20the%20entire%20batch%20at%20once%0A%20%20%20%20%20%20%20%20%20%20%20%20pred%20%3D%20model(images)%0A%20%20%20%20%20%20%20%20%20%20%20%20loss%20%3D%20loss_fn(pred%2C%20labels)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20PyTorch%20by%20default%20accumulates%20gradients%20on%20each%20tensor.%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20We%20don't%20want%2Fneed%20that%20for%20this%2C%20so%20we%20make%20sure%20we%20zero%20out%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20the%20gradients%20before%20calling%20%60loss.backward()%60%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20https%3A%2F%2Fdocs.pytorch.org%2Ftutorials%2Frecipes%2Frecipes%2Fzeroing_out_gradients.html%0A%20%20%20%20%20%20%20%20%20%20%20%20optimizer.zero_grad()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20This%20calculates%20the%20gradient%20for%20each%20weight%20tensor%20and%20stores%20the%20result%20on%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20its%20%60.grad%60%20attribute%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20https%3A%2F%2Fdocs.pytorch.org%2Fdocs%2Fstable%2Fgenerated%2Ftorch.Tensor.backward.html%23torch.Tensor.backward%0A%20%20%20%20%20%20%20%20%20%20%20%20loss.backward()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20This%20actually%20applies%20the%20gradient%20calculated%20above%20by%20%60loss.backward()%60%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20https%3A%2F%2Fdocs.pytorch.org%2Fdocs%2Fstable%2Fgenerated%2Ftorch.optim.SGD.html%23torch.optim.SGD.step%0A%20%20%20%20%20%20%20%20%20%20%20%20optimizer.step()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Print%20progress%2Floss%20every%20100%20batches%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20batch_idx%20%25%20100%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20loss%2C%20current%20%3D%20loss.item()%2C%20batch_idx%20*%20batch_size%20%2B%20len(images)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22loss%3A%20%7Bloss%3A%3E7f%7D%20%20%5B%7Bcurrent%3A%3E5d%7D%2F%7Btotal_size%3A%3E5d%7D%5D%22)%0A%20%20%20%20return%20(train_loop%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Evaluation%20Loop%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nn%2C%20torch)%3A%0A%20%20%20%20def%20eval_loop(dataloader%2C%20model)%3A%0A%0A%20%20%20%20%20%20%20%20loss_fn%20%3D%20nn.CrossEntropyLoss()%0A%0A%20%20%20%20%20%20%20%20%23%20Set%20the%20model%20to%20evaluation%20mode%20-%20important%20for%20batch%20normalization%0A%20%20%20%20%20%20%20%20%23%20and%20dropout%20layers.%20Not%20totally%20required%20in%20this%20situation%20but%20added%20for%20best%0A%20%20%20%20%20%20%20%20%23%20practices%0A%20%20%20%20%20%20%20%20model.eval()%0A%20%20%20%20%20%20%20%20total_size%20%3D%20len(dataloader.dataset)%0A%20%20%20%20%20%20%20%20num_batches%20%3D%20len(dataloader)%0A%0A%20%20%20%20%20%20%20%20%23%20We%20will%20be%20returning%20the%20average%20loss%20and%20accuracy%20for%0A%20%20%20%20%20%20%20%20%23%20the%20predictions%20in%20this%20loop%20-%20start%20the%20counters%20at%200%0A%20%20%20%20%20%20%20%20test_loss%2C%20correct%20%3D%200%2C%200%0A%0A%20%20%20%20%20%20%20%20%23%20Evaluating%20the%20model%20with%20torch.no_grad()%20ensures%20that%20no%20gradients%20are%0A%20%20%20%20%20%20%20%20%23%20computed%20during%20test%20mode.%20Also%20serves%20to%20reduce%20unnecessary%20gradient%0A%20%20%20%20%20%20%20%20%23%20computations%20and%20memory%20usage%20for%20tensors%20with%20requires_grad%3DTrue%0A%20%20%20%20%20%20%20%20with%20torch.no_grad()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20images%2C%20labels%20in%20dataloader%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred%20%3D%20model(images)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test_loss%20%2B%3D%20loss_fn(pred%2C%20labels).item()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20correct%20%2B%3D%20(pred.argmax(1)%20%3D%3D%20labels).type(torch.float).sum().item()%0A%0A%20%20%20%20%20%20%20%20%23%20convert%20to%20averages%20and%20return%0A%20%20%20%20%20%20%20%20test_loss%20%2F%3D%20num_batches%0A%20%20%20%20%20%20%20%20correct%20%2F%3D%20total_size%0A%20%20%20%20%20%20%20%20return%20(test_loss%2C%20correct)%0A%20%20%20%20return%20(eval_loop%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Confusion%20Matrix%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plt%2C%20torch)%3A%0A%20%20%20%20from%20sklearn.metrics%20import%20confusion_matrix%0A%20%20%20%20import%20seaborn%20as%20sn%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20numpy%20as%20np%0A%0A%20%20%20%20%23%20This%20helps%20us%20understand%20which%20inputs%20are%20classified%20more%20accurately.%0A%20%20%20%20%23%20Meant%20to%20be%20run%20at%20the%20end%20of%20an%20entire%20epoch%20loop%2C%20so%20we%20can%20get%20the%20final%20results%0A%20%20%20%20%23%20against%20the%20test%20dataset.%0A%20%20%20%20%23%0A%20%20%20%20%23%20This%20is%20_interesting_%2C%20but%20not%20much%20more%20than%20that%20for%20now%20in%20this%20fully-connected%20network.%0A%20%20%20%20%23%20I'm%20wondering%20if%20it%20will%20be%20more%20handy%20when%20I%20move%20to%20convolutions.%0A%20%20%20%20%23%0A%20%20%20%20%23%20Simplified%20from%20https%3A%2F%2Fchristianbernecker.medium.com%2Fhow-to-create-a-confusion-matrix-in-pytorch-38d06a7f04b7%0A%20%20%20%20def%20display_confusion_matrix(model%2C%20loader)%3A%0A%20%20%20%20%20%20%20%20y_pred%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20y_true%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20inputs%2C%20labels%20in%20loader%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20output%20%3D%20model(inputs)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20QUESTION%20-%20Specifically%20what%20does%20this%20line%20do%3F%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20output%20%3D%20(torch.max(torch.exp(output)%2C%201)%5B1%5D).data.cpu().numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_pred.extend(output)%20%23%20Save%20Prediction%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labels%20%3D%20labels.data.cpu().numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_true.extend(labels)%0A%0A%20%20%20%20%20%20%20%20classes%20%3D%20('0'%2C%20'1'%2C%20'2'%2C%20'3'%2C%20'4'%2C%20'5'%2C%20'6'%2C%20'7'%2C%20'8'%2C%20'9')%0A%0A%20%20%20%20%20%20%20%20%23%20Build%20confusion%20matrix%0A%20%20%20%20%20%20%20%20cf_matrix%20%3D%20confusion_matrix(y_true%2C%20y_pred)%0A%20%20%20%20%20%20%20%20df_cm%20%3D%20pd.DataFrame(cf_matrix%20%2F%20np.sum(cf_matrix%2C%20axis%3D1)%5B%3A%2C%20None%5D%2C%20index%20%3D%20%5Bi%20for%20i%20in%20classes%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20columns%20%3D%20%5Bi%20for%20i%20in%20classes%5D)%0A%20%20%20%20%20%20%20%20fig%20%3D%20plt.figure(figsize%20%3D%20(12%2C7))%0A%20%20%20%20%20%20%20%20ax%20%3D%20sn.heatmap(df_cm%2C%20annot%3DTrue)%0A%20%20%20%20%20%20%20%20ax.set_xlabel(%22Predicted%22)%0A%20%20%20%20%20%20%20%20ax.set_ylabel(%22Actual%22)%0A%20%20%20%20%20%20%20%20plt.show()%0A%20%20%20%20return%20(display_confusion_matrix%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Plotting%20Training%20Performance%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plt)%3A%0A%20%20%20%20%23%20This%20will%20plot%20the%20loss%20as%20well%20as%20the%20prediction%20accuracy%20of%20the%20model%0A%20%20%20%20%23%20against%20both%20the%20training%20and%20test%20datasets.%0A%20%20%20%20%23%0A%20%20%20%20%23%20Training%20and%20test%20accuracy%20important%20to%20compare%20to%20detect%20overfitting.%0A%20%20%20%20%23%20Should%20be%20called%20at%20the%20end%20of%20an%20epoch%20loop%20which%20gathers%20these%20accuracies%0A%20%20%20%20%23%20and%20losses%20across%20all%20iterations.%0A%20%20%20%20def%20plot_performance(losses%2C%20train_accuracies%2C%20test_accuracies%2C%20layer_sizes%2C%20learning_rate%2C%20batch_size)%3A%0A%20%20%20%20%20%20%20%20fig%2C%20ax1%20%3D%20plt.subplots(figsize%3D(12%2C5))%0A%20%20%20%20%20%20%20%20fig.suptitle(f'Training%20performance%20for%20network%20with%20%7Blen(layer_sizes)%7D%20hidden%20layers%20(%7Blayer_sizes%7D)%20--%20batch_size%3D%7Bbatch_size%7D%2C%20learning_rate%3D%7Blearning_rate%7D'%2C%20fontsize%3D14)%0A%0A%20%20%20%20%20%20%20%20%23%20Left%20y-axis%20for%20loss%0A%20%20%20%20%20%20%20%20ax1.plot(list(range(len(losses)))%2C%20losses%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Loss%22%2C%20color%3D%22red%22%2C%20marker%3D%22o%22)%0A%20%20%20%20%20%20%20%20ax1.set_xlabel(%22Epoch%22)%0A%20%20%20%20%20%20%20%20ax1.set_ylabel(%22Loss%22%2C%20color%3D%22red%22)%0A%20%20%20%20%20%20%20%20ax1.tick_params(axis%3D%22y%22%2C%20labelcolor%3D%22red%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Right%20y-axis%20(blue)%20for%20test%20accuracy%0A%20%20%20%20%20%20%20%20ax2%20%3D%20ax1.twinx()%0A%20%20%20%20%20%20%20%20ax2.set_ylabel(%22Accuracy%22%2C%20color%3D%22black%22)%0A%20%20%20%20%20%20%20%20ax2.plot(list(range(len(test_accuracies)))%2C%20test_accuracies%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Test%20Accuracy%22%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22blue%22%2C%20marker%3D%22o%22)%0A%20%20%20%20%20%20%20%20ax2.tick_params(axis%3D%22y%22%2C%20labelcolor%3D%22blue%22)%0A%20%20%20%20%20%20%20%20ax2.set_ylim(0.8%2C%201)%0A%0A%20%20%20%20%20%20%20%20%23%20Right%20y-axis%20(green)%20for%20training%20accuracy%0A%20%20%20%20%20%20%20%20ax2.plot(list(range(len(train_accuracies)))%2C%20train_accuracies%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Training%20Accuracy%22%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22green%22%2C%20marker%3D%22o%22)%0A%20%20%20%20%20%20%20%20ax2.tick_params(axis%3D%22y%22%2C%20labelcolor%3D%22green%22)%0A%0A%20%20%20%20%20%20%20%20fig.tight_layout()%0A%20%20%20%20%20%20%20%20plt.legend()%0A%20%20%20%20%20%20%20%20plt.show()%0A%20%20%20%20return%20(plot_performance%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Epoch%20Loop%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Network%2C%0A%20%20%20%20display_confusion_matrix%2C%0A%20%20%20%20eval_loop%2C%0A%20%20%20%20load_data%2C%0A%20%20%20%20plot_performance%2C%0A%20%20%20%20train_loop%2C%0A)%3A%0A%20%20%20%20def%20epoch_loop(layer_sizes%2C%20num_epochs%2C%20learning_rate%2C%20batch_size)%3A%0A%0A%20%20%20%20%20%20%20%20print(f%22Starting%20new%20epoch%20loop%20with%20layer_sizes%3A%20%7Blayer_sizes%7D%2C%20%22%20%2B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22num_epochs%3A%20%7Bnum_epochs%7D%2C%20learning_rate%3A%20%7Blearning_rate%7D%2C%20batch_size%3A%20%7Bbatch_size%7D%22)%0A%0A%20%20%20%20%20%20%20%20model%20%3D%20Network(layer_sizes)%0A%20%20%20%20%20%20%20%20train_loader%2C%20test_loader%20%3D%20load_data(batch_size)%0A%0A%20%20%20%20%20%20%20%20%23%20For%20tracking%2Fvisualization%20purposes%0A%20%20%20%20%20%20%20%20losses%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20train_accuracies%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20test_accuracies%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20epoch%20in%20range(num_epochs)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Epoch%20%7Bepoch%2B1%7D%5Cn-------------------------------%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20train_loop(train_loader%2C%20model%2C%20learning_rate)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20To%20help%20detect%20overfitting%2C%20we%20want%20to%20run%20the%20same%20eval%20loop%20which%20is%20used%20for%20the%20test%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20dataset%20against%20the%20training%20dataset%20as%20well%2C%20but%20only%20after%20the%20training%20loop%20has%20completed.%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20This%20ensures%20we're%20comparing%20the%20two%20equally%20-%20overfitting%20is%20indicated%20if%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20training%20data%20prediction%20accuracy%20keeps%20increasing%20but%20not%20test%20accuracy.%0A%20%20%20%20%20%20%20%20%20%20%20%20train_loss%2C%20train_acc%20%3D%20eval_loop(train_loader%2C%20model)%0A%20%20%20%20%20%20%20%20%20%20%20%20test_loss%2C%20test_acc%20%3D%20eval_loop(test_loader%2C%20model)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%5CnTrain%20-%20Accuracy%3A%20%7B(100*train_acc)%3A%3E0.1f%7D%25%2C%20Avg%20loss%3A%20%7Btrain_loss%3A%3E8f%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Test%20-%20Accuracy%3A%20%7B(100*test_acc)%3A%3E0.1f%7D%25%2C%20Avg%20loss%3A%20%7Btest_loss%3A%3E8f%7D%20%5Cn%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20losses.append(train_loss)%0A%20%20%20%20%20%20%20%20%20%20%20%20train_accuracies.append(train_acc)%0A%20%20%20%20%20%20%20%20%20%20%20%20test_accuracies.append(test_acc)%0A%0A%20%20%20%20%20%20%20%20display_confusion_matrix(model%2C%20test_loader)%0A%20%20%20%20%20%20%20%20plot_performance(losses%2C%20train_accuracies%2C%20test_accuracies%2C%20layer_sizes%2C%20learning_rate%2C%20batch_size)%0A%20%20%20%20return%20(epoch_loop%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Iterations%0A%0A%20%20%20%20Trying%20a%20few%20runs%20of%20the%20epoch%20loop%20with%20different%20hyperparameters.%0A%0A%20%20%20%20Only%20the%20first%20is%20uncommented%20-%20uncomment%20subsequent%20invocations%20to%20see%20their%20results.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(epoch_loop)%3A%0A%20%20%20%20%23%20Accuracy%20gets%20up%20to%2091.2%25%2C%20and%20could%20go%20higher%20with%20higher%20%60num_epochs%60%20but%20is%20starting%20to%20plateau%20for%20sure.%0A%20%20%20%20%23%20No%20overfitting%20appears%20to%20be%20taking%20place%20-%20training%20accuracy%20consistently%20lags%20%20behind%20test%20accuracy%20by%20about%200.4%25%20-%20I%20am%20hypothesizing%20that%20this%20has%20to%20do%20with%20the%20training%20set%20being%20much%20larger%2C%20with%20more%20difficult%20edge%20cases.%20So%20this%20feels%20to%20me%20the%20right%20direction%20-%20the%20model%20is%20generalizing%20as%20desired.%0A%20%20%20%20epoch_loop(layer_sizes%20%3D%20%5B512%2C%20512%5D%2C%20num_epochs%3D20%2C%20learning_rate%3D1e-3%2C%20batch_size%3D64)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Trying%20shallower%20layer%20size%0A%20%20%20%20%23%20epoch_loop(layer_sizes%20%3D%20%5B128%2C%20128%5D%2C%20num_epochs%3D20%2C%20learning_rate%3D1e-3%2C%20batch_size%3D64)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Trying%20a%20faster%20learning%20rate%0A%20%20%20%20%23%20epoch_loop(layer_sizes%20%3D%20%5B512%2C%20512%5D%2C%20num_epochs%3D20%2C%20learning_rate%3D1e-2%2C%20batch_size%3D64)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Trying%20one%20more%20hidden%20layer%0A%20%20%20%20%23%20epoch_loop(layer_sizes%20%3D%20%5B512%2C%20512%2C%20512%5D%2C%20num_epochs%3D20%2C%20learning_rate%3D1e-3%2C%20batch_size%3D64)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Smaller%20batch%20size%0A%20%20%20%20%23%20epoch_loop(layer_sizes%20%3D%20%5B512%2C%20512%5D%2C%20num_epochs%3D20%2C%20learning_rate%3D1e-3%2C%20batch_size%3D32)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Larger%20batch%20size%0A%20%20%20%20%23%20epoch_loop(layer_sizes%20%3D%20%5B512%2C%20512%5D%2C%20num_epochs%3D20%2C%20learning_rate%3D1e-3%2C%20batch_size%3D128)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20This%20batch%20size%20produced%20a%20little%20higher%20accuracy%20-%20so%20let's%20increase%20epochs%20to%20see%20how%20far%20it%20gets.%0A%20%20%20%20%23%20epoch_loop(layer_sizes%20%3D%20%5B512%2C%20512%5D%2C%20num_epochs%3D25%2C%20learning_rate%3D1e-3%2C%20batch_size%3D32)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Seems%20like%20the%20most%20effective%20changes%20was%20a%20faster%20learning%20rate%20and%20a%20smaller%20batch%20size.%0A%20%20%20%20%23%20epoch_loop(layer_sizes%20%3D%20%5B512%2C%20512%5D%2C%20num_epochs%3D25%2C%20learning_rate%3D5e-3%2C%20batch_size%3D32)%0A%20%20%20%20%23%20Observations%3A%0A%20%20%20%20%23%20-%20Definitely%20the%20best%20accuracy%20so%20far%2C%20and%20surprisingly%20high.%20I%20wasn't%20expecting%20the%20fully%20connected%20approach%20to%20get%20as%20high%20as%20~98%25%0A%20%20%20%20%23%20-%20At%20first%20I%20was%20worried%20there%20was%20overfitting%20since%20training%20accuracy%20got%20even%20higher.%20But%20based%20on%20my%20understanding%20it's%20not%20overfitting.%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
14452e601ae566159e1e51344ddc243c