%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.10%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%22marimo%22%2C%0A%23%20%20%20%22torch%22%2C%0A%23%20%20%20%22numpy%22%2C%0A%23%20%20%20%22scikit-learn%22%2C%0A%23%20%20%20%22llvmlite%3E%3D0.42%22%2C%0A%23%20%20%20%22numba%3E%3D0.59%22%2C%0A%23%20%20%20%22umap-learn%3E%3D0.5.5%22%2C%0A%23%20%20%20%22plotly%22%2C%0A%23%20%20%20%22parx%20%40%20file%3A%2F%2F%2Fhome%2Fjohan%2FDocuments%2Fphd%2Fparx%22%2C%0A%23%20%5D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.23.8%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%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20torch%0A%20%20%20%20import%20torch.nn%20as%20nn%0A%20%20%20%20from%20sklearn.datasets%20import%20make_moons%0A%20%20%20%20import%20parx%0A%0A%20%20%20%20return%20make_moons%2C%20nn%2C%20np%2C%20parx%2C%20torch%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Do%20linear%20regions%20survive%20into%20feature%20space%3F%0A%0A%20%20%20%20A%20ReLU%20network%20partitions%20its%20input%20space%20into%20**linear%20regions**%20%E2%80%94%20maximal%0A%20%20%20%20connected%20subsets%20where%20the%20network%20is%20a%20fixed%20affine%20map.%20%60parx%60%20computes%0A%20%20%20%20this%20partition%20exactly.%0A%0A%20%20%20%20**The%20question%20explored%20here%3A**%20if%20you%20project%20the%20network's%20internal%0A%20%20%20%20activations%20at%20some%20hidden%20layer%20down%20to%202D%20with%20tSNE%20or%20UMAP%2C%20do%20points%0A%20%20%20%20from%20the%20*same%20linear%20region*%20cluster%20together%2C%20and%20do%20*neighbouring%20regions*%0A%20%20%20%20land%20nearby%3F%0A%0A%20%20%20%20The%20answer%20is%20yes%20in%20a%20precise%20sense.%20Within%20each%20linear%20region%20the%20whole%0A%20%20%20%20network%20is%20affine%2C%20so%20activations%20at%20any%20layer%20are%20an%20affine%20function%20of%20the%0A%20%20%20%202D%20input.%20Points%20in%20the%20same%20region%20therefore%20lie%20on%20the%20same%202-dimensional%0A%20%20%20%20affine%20subspace%20of%20the%20activation%20space%20%E2%80%94%20they%20*must*%20cluster.%20The%20notebook%0A%20%20%20%20makes%20this%20visible%20across%20different%20architectures%20and%20extraction%20layers.%0A%0A%20%20%20%20Use%20the%20controls%20below%20to%20pick%20an%20architecture%20and%20a%20layer.%20The%20partition%2C%0A%20%20%20%20feature%20extraction%2C%20and%20both%20embeddings%20update%20reactively.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(make_moons%2C%20np)%3A%0A%20%20%20%20X%2C%20y%20%3D%20make_moons(n_samples%3D600%2C%20noise%3D0.12%2C%20random_state%3D0)%0A%20%20%20%20X%20%3D%20X.astype(np.float64)%0A%20%20%20%20return%20X%2C%20y%0A%0A%0A%40app.cell%0Adef%20_(X%2C%20go%2C%20mo%2C%20y)%3A%0A%20%20%20%20_colors%20%3D%20%5B%22%234878d0%22%20if%20yi%20%3D%3D%200%20else%20%22%23ee854a%22%20for%20yi%20in%20y%5D%0A%20%20%20%20_fig%20%3D%20go.Figure(go.Scatter(%0A%20%20%20%20%20%20%20%20x%3DX%5B%3A%2C%200%5D%2C%20y%3DX%5B%3A%2C%201%5D%2C%20mode%3D%22markers%22%2C%0A%20%20%20%20%20%20%20%20marker%3Ddict(color%3D_colors%2C%20size%3D5%2C%20opacity%3D0.7)%2C%0A%20%20%20%20%20%20%20%20text%3D%5Bf%22class%20%7Byi%7D%22%20for%20yi%20in%20y%5D%2C%0A%20%20%20%20%20%20%20%20hovertemplate%3D%22%25%7Btext%7D%3Cextra%3E%3C%2Fextra%3E%22%2C%0A%20%20%20%20%20%20%20%20showlegend%3DFalse%2C%0A%20%20%20%20))%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20title%3D%22Two-moons%20dataset%20(600%20points%2C%202D%20input)%22%2C%0A%20%20%20%20%20%20%20%20xaxis%3Ddict(title%3D%22x%E2%82%81%22%2C%20scaleanchor%3D%22y%22%2C%20scaleratio%3D1)%2C%0A%20%20%20%20%20%20%20%20yaxis_title%3D%22x%E2%82%82%22%2C%20width%3D460%2C%20height%3D400%2C%0A%20%20%20%20)%0A%20%20%20%20mo.center(_fig)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20plotly.graph_objects%20as%20go%0A%0A%20%20%20%20return%20(go%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20%23%20hidden%20layer%20sizes%3B%20model%20is%20always%20input(2)%20%E2%86%92%20hidden%20layers%20%E2%86%92%20output(2)%0A%20%20%20%20_arch_options%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22Tiny%20%20%20%20%202%20%E2%86%92%208%20%E2%86%92%208%20%E2%86%92%202%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B8%2C%208%5D%2C%0A%20%20%20%20%20%20%20%20%22Small%20%20%20%202%20%E2%86%92%2016%20%E2%86%92%2016%20%E2%86%92%202%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B16%2C%2016%5D%2C%0A%20%20%20%20%20%20%20%20%22Medium%20%20%202%20%E2%86%92%2032%20%E2%86%92%2032%20%E2%86%92%202%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B32%2C%2032%5D%2C%0A%20%20%20%20%20%20%20%20%22Wide%20%20%20%20%202%20%E2%86%92%2064%20%E2%86%92%2064%20%E2%86%92%202%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B64%2C%2064%5D%2C%0A%20%20%20%20%20%20%20%20%22Deep-narrow%20%20%20%202%20%E2%86%92%2016%20%E2%86%92%2016%20%E2%86%92%2016%20%E2%86%92%202%22%3A%20%20%5B16%2C%2016%2C%2016%5D%2C%0A%20%20%20%20%20%20%20%20%22Deep-medium%20%20%20%202%20%E2%86%92%2032%20%E2%86%92%2032%20%E2%86%92%2032%20%E2%86%92%202%22%3A%20%20%5B32%2C%2032%2C%2032%5D%2C%0A%20%20%20%20%20%20%20%20%22Bottleneck%20%20%20%20%202%20%E2%86%92%2032%20%E2%86%92%208%20%E2%86%92%2032%20%E2%86%92%202%22%3A%20%20%20%5B32%2C%208%2C%2032%5D%2C%0A%20%20%20%20%7D%0A%20%20%20%20arch_dropdown%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20options%3D_arch_options%2C%0A%20%20%20%20%20%20%20%20value%3D%22Medium%20%20%202%20%E2%86%92%2032%20%E2%86%92%2032%20%E2%86%92%202%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Architecture%22%2C%0A%20%20%20%20)%0A%20%20%20%20arch_dropdown%0A%20%20%20%20return%20(arch_dropdown%2C)%0A%0A%0A%40app.cell%0Adef%20_(X%2C%20arch_dropdown%2C%20nn%2C%20torch%2C%20y)%3A%0A%20%20%20%20def%20_build(hidden_sizes)%3A%0A%20%20%20%20%20%20%20%20sizes%20%3D%20%5B2%5D%20%2B%20hidden_sizes%20%2B%20%5B2%5D%0A%20%20%20%20%20%20%20%20layers%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(len(sizes)%20-%201)%3A%0A%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%20if%20i%20%3C%20len(sizes)%20-%202%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20layers.append(nn.ReLU())%0A%20%20%20%20%20%20%20%20return%20nn.Sequential(*layers)%0A%0A%20%20%20%20def%20_train(model%2C%20epochs%3D500%2C%20lr%3D5e-3)%3A%0A%20%20%20%20%20%20%20%20opt%20%3D%20torch.optim.Adam(model.parameters()%2C%20lr%3Dlr)%0A%20%20%20%20%20%20%20%20loss_fn%20%3D%20nn.CrossEntropyLoss()%0A%20%20%20%20%20%20%20%20Xt%20%3D%20torch.tensor(X%2C%20dtype%3Dtorch.float32)%0A%20%20%20%20%20%20%20%20yt%20%3D%20torch.tensor(y%2C%20dtype%3Dtorch.long)%0A%20%20%20%20%20%20%20%20model.train()%0A%20%20%20%20%20%20%20%20for%20_%20in%20range(epochs)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20opt.zero_grad()%0A%20%20%20%20%20%20%20%20%20%20%20%20loss_fn(model(Xt)%2C%20yt).backward()%0A%20%20%20%20%20%20%20%20%20%20%20%20opt.step()%0A%20%20%20%20%20%20%20%20model.eval()%0A%0A%20%20%20%20torch.manual_seed(0)%0A%20%20%20%20model%20%3D%20_build(arch_dropdown.value)%0A%20%20%20%20_train(model)%0A%0A%20%20%20%20with%20torch.no_grad()%3A%0A%20%20%20%20%20%20%20%20_logits%20%3D%20model(torch.tensor(X%2C%20dtype%3Dtorch.float32))%0A%20%20%20%20%20%20%20%20_acc%20%3D%20(_logits.argmax(dim%3D1).numpy()%20%3D%3D%20y).mean()%0A%0A%20%20%20%20%23%20label%20each%20extractable%20layer%0A%20%20%20%20_linears%20%3D%20%5Bm%20for%20m%20in%20model.modules()%20if%20isinstance(m%2C%20nn.Linear)%5D%0A%20%20%20%20_layer_opts%20%3D%20%7B%7D%0A%20%20%20%20for%20_i%20in%20range(1%2C%20len(_linears))%3A%0A%20%20%20%20%20%20%20%20_dim%20%3D%20_linears%5B_i%5D.in_features%0A%20%20%20%20%20%20%20%20if%20_i%20%3D%3D%20len(_linears)%20-%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_label%20%3D%20f%22Before%20output%20head%20%20(%7B_dim%7DD%20activations)%22%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_label%20%3D%20f%22After%20hidden%20layer%20%7B_i%7D%20%20(%7B_dim%7DD%20activations)%22%0A%20%20%20%20%20%20%20%20_layer_opts%5B_label%5D%20%3D%20_i%0A%0A%20%20%20%20arch_summary%20%3D%20(%0A%20%20%20%20%20%20%20%20f%22**Architecture%3A**%20%60%7Barch_dropdown.value%7D%60%20%20%E2%86%92%20%20%22%0A%20%20%20%20%20%20%20%20f%22%7Blen(_linears)%20-%201%7D%20hidden%20layer(s)%2C%20%22%0A%20%20%20%20%20%20%20%20f%22%7Bsum(p.numel()%20for%20p%20in%20model.parameters())%7D%20parameters%20%20%7C%20%20%22%0A%20%20%20%20%20%20%20%20f%22training%20accuracy%20**%7B_acc%3A.1%25%7D**%22%0A%20%20%20%20)%0A%20%20%20%20layer_options%20%3D%20_layer_opts%0A%20%20%20%20return%20arch_summary%2C%20layer_options%2C%20model%0A%0A%0A%40app.cell%0Adef%20_(arch_summary%2C%20layer_options%2C%20mo)%3A%0A%20%20%20%20layer_dropdown%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20options%3Dlayer_options%2C%0A%20%20%20%20%20%20%20%20value%3Dlist(layer_options.keys())%5B-1%5D%2C%20%20%20%23%20default%3A%20before%20output%20head%0A%20%20%20%20%20%20%20%20label%3D%22Extract%20features%20from%22%2C%0A%20%20%20%20)%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(arch_summary)%2C%0A%20%20%20%20%20%20%20%20layer_dropdown%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%20(layer_dropdown%2C)%0A%0A%0A%40app.cell%0Adef%20_(X%2C%20mo%2C%20model%2C%20parx)%3A%0A%20%20%20%20with%20mo.status.spinner(title%3D%22Computing%20partition%20%E2%80%A6%22)%3A%0A%20%20%20%20%20%20%20%20partition%20%3D%20parx.compute_partition(model%2C%20X%2C%20method%3D%22sparse_julia%22)%0A%20%20%20%20return%20(partition%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20colour_dropdown%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20options%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Spatial%20(HSV%20%E2%80%94%20angle%20%2B%20radius)%22%3A%20%22spatial%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Frobenius%20norm%20(Viridis)%22%3A%20%22frobenius%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Random%20(Turbo)%22%3A%20%22random%22%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20value%3D%22Spatial%20(HSV%20%E2%80%94%20angle%20%2B%20radius)%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Region%20colour%20scheme%22%2C%0A%20%20%20%20)%0A%20%20%20%20colour_dropdown%0A%20%20%20%20return%20(colour_dropdown%2C)%0A%0A%0A%40app.cell%0Adef%20_(colour_dropdown%2C%20partition%2C%20parx)%3A%0A%20%20%20%20region_colors%20%3D%20parx.region_palette(partition%2C%20colour_dropdown.value)%0A%20%20%20%20return%20(region_colors%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Linear%20regions%20in%20input%20space%0A%0A%20%20%20%20Each%20polygon%20is%20one%20linear%20region%2C%20clipped%20to%20the%20data%20bounding%20box.%20The%0A%20%20%20%20colour%20scheme%20is%20selected%20above%3A%0A%0A%20%20%20%20-%20**Spatial%20(HSV)**%20%E2%80%94%20hue%20encodes%20the%20angle%20of%20the%20region's%20centroid%20from%0A%20%20%20%20%20%20the%20partition%20centre%3B%20saturation%20encodes%20radial%20distance.%20Adjacent%20regions%0A%20%20%20%20%20%20in%20input%20space%20get%20visually%20adjacent%20colours.%0A%20%20%20%20-%20**Frobenius%20norm**%20%E2%80%94%20colour%20encodes%20%E2%80%96A%E2%80%96_F%2C%20the%20local%20affine%20map's%0A%20%20%20%20%20%20Frobenius%20norm.%20Dense%20small%20regions%20near%20the%20decision%20boundary%20tend%20to%0A%20%20%20%20%20%20have%20larger%20norms.%0A%20%20%20%20-%20**Random%20(Turbo)**%20%E2%80%94%20each%20region%20gets%20a%20distinct%20hue%20from%20the%20Turbo%0A%20%20%20%20%20%20palette%20in%20region-index%20order.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X%2C%20mo%2C%20partition%2C%20parx%2C%20region_colors)%3A%0A%20%20%20%20_pad%20%3D%200.35%0A%20%20%20%20_xr%20%3D%20(float(X%5B%3A%2C%200%5D.min())%20-%20_pad%2C%20float(X%5B%3A%2C%200%5D.max())%20%2B%20_pad)%0A%20%20%20%20_yr%20%3D%20(float(X%5B%3A%2C%201%5D.min())%20-%20_pad%2C%20float(X%5B%3A%2C%201%5D.max())%20%2B%20_pad)%0A%20%20%20%20_fig%20%3D%20parx.viz.plot_partition_2d(partition%2C%20domain%3D(_xr%2C%20_yr)%2C%20colors%3Dregion_colors)%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20title%3Df%22Partition%20%E2%80%94%20%7Blen(partition)%7D%20regions%22%2C%0A%20%20%20%20%20%20%20%20width%3D500%2C%20height%3D460%2C%0A%20%20%20%20)%0A%20%20%20%20mo.center(_fig)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Feature%20extraction%0A%0A%20%20%20%20%60parx.extract_features%60%20attaches%20a%20forward%20hook%20to%20the%20chosen%20Linear%20layer%0A%20%20%20%20and%20captures%20its%20**input%20tensor**%20%E2%80%94%20the%20post-ReLU%20activations%20flowing%20into%0A%20%20%20%20that%20layer.%20Within%20each%20linear%20region%20the%20network%20is%20affine%20end-to-end%2C%20so%0A%20%20%20%20these%20activations%20are%20an%20affine%20function%20of%20the%202D%20input.%20Points%20in%20the%20same%0A%20%20%20%20region%20lie%20on%20the%20same%202D%20affine%20subspace%20of%20the%20(higher-dimensional)%0A%20%20%20%20activation%20space.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X%2C%20layer_dropdown%2C%20mo%2C%20model%2C%20parx)%3A%0A%20%20%20%20with%20mo.status.spinner(title%3D%22Extracting%20features%20%E2%80%A6%22)%3A%0A%20%20%20%20%20%20%20%20features%20%3D%20parx.extract_features(model%2C%20X%2C%20layer_index%3Dlayer_dropdown.value)%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22Extracted%20**%7Bfeatures.shape%5B1%5D%7DD**%20activations%20from%20%22%0A%20%20%20%20%20%20%20%20f%22%60layer_index%3D%7Blayer_dropdown.value%7D%60%20%22%0A%20%20%20%20%20%20%20%20f%22%E2%86%92%20array%20shape%20%60%7Bfeatures.shape%7D%60%22%0A%20%20%20%20)%0A%20%20%20%20return%20(features%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20tSNE%20and%20UMAP%20of%20the%20extracted%20features%0A%0A%20%20%20%20Both%20methods%20project%20the%20high-dimensional%20activations%20to%202D.%20Points%20are%0A%20%20%20%20coloured%20two%20ways%3A%0A%0A%20%20%20%20-%20**By%20linear%20region**%20(left%20pair)%20%E2%80%94%20same%20colour%20%3D%20same%20affine%20map.%20Tight%0A%20%20%20%20%20%20monochromatic%20clusters%20confirm%20that%20the%20region%20structure%20is%20preserved%20in%0A%20%20%20%20%20%20feature%20space.%0A%20%20%20%20-%20**By%20class%20label**%20(right%20pair)%20%E2%80%94%20comparing%20this%20with%20the%20region%20colouring%0A%20%20%20%20%20%20shows%20how%20many%20affine%20pieces%20the%20network%20used%20per%20class%20and%20where%20class%0A%20%20%20%20%20%20boundaries%20fall%20relative%20to%20region%20boundaries.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X%2C%20features%2C%20mo%2C%20np%2C%20partition%2C%20parx%2C%20region_colors%2C%20y)%3A%0A%20%20%20%20def%20_embed_fig(method%2C%20color_by%2C%20title)%3A%0A%20%20%20%20%20%20%20%20return%20parx.plot_feature_embedding(%0A%20%20%20%20%20%20%20%20%20%20%20%20features%2C%20partition%2C%20X%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20method%3Dmethod%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color_by%3Dcolor_by%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Dtitle%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3D0%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20with%20mo.status.spinner(title%3D%22Running%20tSNE%20%E2%80%A6%22)%3A%0A%20%20%20%20%20%20%20%20_tsne_region%20%3D%20_embed_fig(%22tsne%22%2C%20region_colors%2C%20%22tSNE%20%E2%80%94%20by%20region%22)%0A%20%20%20%20%20%20%20%20_tsne_class%20%20%3D%20_embed_fig(%22tsne%22%2C%20y.astype(np.float64)%2C%20%22tSNE%20%E2%80%94%20by%20class%22)%0A%0A%20%20%20%20with%20mo.status.spinner(title%3D%22Running%20UMAP%20%E2%80%A6%22)%3A%0A%20%20%20%20%20%20%20%20_umap_region%20%3D%20_embed_fig(%22umap%22%2C%20region_colors%2C%20%22UMAP%20%E2%80%94%20by%20region%22)%0A%20%20%20%20%20%20%20%20_umap_class%20%20%3D%20_embed_fig(%22umap%22%2C%20y.astype(np.float64)%2C%20%22UMAP%20%E2%80%94%20by%20class%22)%0A%0A%20%20%20%20for%20_f%20in%20%5B_tsne_region%2C%20_tsne_class%2C%20_umap_region%2C%20_umap_class%5D%3A%0A%20%20%20%20%20%20%20%20_f.update_layout(width%3D400%2C%20height%3D380%2C%20margin%3Ddict(t%3D40%2C%20b%3D20%2C%20l%3D20%2C%20r%3D20))%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.hstack(%5B_tsne_region%2C%20_tsne_class%5D%2C%20justify%3D%22center%22)%2C%0A%20%20%20%20%20%20%20%20mo.hstack(%5B_umap_region%2C%20_umap_class%5D%2C%20justify%3D%22center%22)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Reading%20the%20plots%0A%0A%20%20%20%20**Tight%20monochromatic%20clusters%20in%20the%20region%20plot**%20confirm%20the%20geometric%0A%20%20%20%20argument%3A%20the%20affine%20map%20within%20each%20region%20collapses%20the%202D%20affine%20subspace%0A%20%20%20%20to%20a%20single%20point%20in%20tSNE%2FUMAP%20space.%0A%0A%20%20%20%20**Smooth%20colour%20gradients%20at%20cluster%20edges**%20indicate%20neighbouring%20regions%0A%20%20%20%20%E2%80%94%20sharing%20a%20face%20in%20input%20space%20means%20differing%20by%20one%20ReLU%20flip%2C%20so%20their%0A%20%20%20%20affine%20maps%20are%20close%20and%20their%20feature%20clouds%20land%20nearby%20in%20the%20embedding.%0A%0A%20%20%20%20**One%20class%20colour%2C%20multiple%20region%20colours**%20is%20expected%20and%20informative.%0A%20%20%20%20A%20curved%20decision%20boundary%20requires%20multiple%20affine%20pieces%3B%20the%20number%20of%0A%20%20%20%20region-colour%20blobs%20per%20class%20is%20a%20direct%20count%20of%20how%20many%20pieces%20the%0A%20%20%20%20network%20allocated%20to%20that%20class.%0A%0A%20%20%20%20**tSNE%20vs%20UMAP**%20%E2%80%94%20tSNE%20preserves%20local%20structure%20(nearby%20clusters%20are%0A%20%20%20%20reliable)%20but%20distorts%20global%20distances.%20UMAP%20better%20preserves%20the%20large-scale%0A%20%20%20%20topology%20of%20how%20region%20groups%20relate%20to%20each%20other.%20Using%20both%20gives%20a%20more%0A%20%20%20%20complete%20picture.%0A%0A%20%20%20%20**Changing%20the%20extraction%20layer**%20shows%20how%20the%20representation%20evolves%0A%20%20%20%20through%20the%20network.%20Early%20layers%20produce%20coarser%20clusters%20(fewer%20active%0A%20%20%20%20neurons%2C%20simpler%20affine%20maps)%3B%20later%20layers%20produce%20tighter%2C%20more%0A%20%20%20%20class-aligned%20clusters%20as%20the%20network%20funnels%20towards%20the%20decision.%0A%20%20%20%20%22%22%22)%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
f5d335767ff66703c13e7d72669277f7