{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "provenance": [], "machine_shape": "hm", "gpuType": "A100" }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" }, "accelerator": "GPU" }, "cells": [ { "cell_type": "code", "source": [ "try:\n", " !pip uninstall -qy geometricvocab\n", "except:\n", " pass\n", "\n", "!pip install -q git+https://github.com/AbstractEyes/lattice_vocabulary.git" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "iHaIH_8OI38S", "outputId": "361524ac-8ef7-419f-bb7b-05fea1062443" }, "execution_count": 1, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\u001b[33mWARNING: Skipping geometricvocab as it is not installed.\u001b[0m\u001b[33m\n", "\u001b[0m Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n", " Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n", " Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n", " Building wheel for geometricvocab (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n" ] } ] }, { "cell_type": "code", "source": [ "#@title True Cayley-Menger KSimplex Linear\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import math\n", "from typing import List, Tuple, Optional\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "# ============================================================================\n", "# TRUE CAYLEY-MENGER KSIMPLEX PRIMITIVES\n", "# ============================================================================\n", "\n", "class SimplexEdgeLayer(nn.Module):\n", " \"\"\"\n", " Routes information along k-simplex edges.\n", " For k-simplex: k+1 vertices, each connected to k others.\n", "\n", " Input: vertex features [N, B, D, V, E]\n", " Output: updated vertex features [N, B, D, V, E]\n", " \"\"\"\n", "\n", " def __init__(self, n: int, num_vertices: int, vertex_dim: int):\n", " super().__init__()\n", " self.n = n\n", " self.num_vertices = num_vertices # V = k+1\n", " self.vertex_dim = vertex_dim # E\n", " self.num_edges = num_vertices - 1 # k edges per vertex\n", "\n", " # Edge message weights: for each vertex, k incoming edge transforms\n", " self.edge_weight = nn.Parameter(torch.empty(n, num_vertices, self.num_edges, vertex_dim, vertex_dim))\n", "\n", " # Routing: which neighbors to attend to\n", " self.route_weight = nn.Parameter(torch.empty(n, num_vertices, self.num_edges, vertex_dim))\n", " self.route_bias = nn.Parameter(torch.zeros(n, num_vertices, self.num_edges))\n", "\n", " # Output projection per vertex\n", " self.out_weight = nn.Parameter(torch.empty(n, num_vertices, vertex_dim, vertex_dim))\n", " self.out_bias = nn.Parameter(torch.zeros(n, num_vertices, vertex_dim))\n", "\n", " self._init_weights()\n", " self._build_neighbor_indices()\n", "\n", " def _init_weights(self):\n", " for i in range(self.n):\n", " for v in range(self.num_vertices):\n", " nn.init.orthogonal_(self.out_weight[i, v])\n", " for e in range(self.num_edges):\n", " nn.init.kaiming_uniform_(self.edge_weight[i, v, e], a=math.sqrt(5))\n", " nn.init.kaiming_uniform_(self.route_weight[i, v, e].unsqueeze(0), a=math.sqrt(5))\n", "\n", " def _build_neighbor_indices(self):\n", " \"\"\"For each vertex, list its k neighbors (all other vertices in simplex).\"\"\"\n", " neighbors = []\n", " for v in range(self.num_vertices):\n", " v_neighbors = [j for j in range(self.num_vertices) if j != v]\n", " neighbors.append(v_neighbors)\n", " self.register_buffer('neighbor_idx', torch.tensor(neighbors)) # [V, k]\n", "\n", " def forward(self, vertices: torch.Tensor) -> torch.Tensor:\n", " \"\"\"\n", " vertices: [N, B, D, V, E]\n", " returns: [N, B, D, V, E]\n", " \"\"\"\n", " N, B, D, V, E = vertices.shape\n", "\n", " # Gather neighbor features for each vertex\n", " # neighbor_idx: [V, k] -> expand to [N, B, D, V, k, E]\n", " idx = self.neighbor_idx.view(1, 1, 1, V, -1, 1).expand(N, B, D, -1, -1, E)\n", " neighbors = vertices.unsqueeze(-2).expand(-1, -1, -1, -1, self.num_edges, -1)\n", " neighbor_feats = torch.gather(\n", " vertices.unsqueeze(3).expand(-1, -1, -1, V, -1, -1),\n", " dim=4,\n", " index=idx\n", " ) # [N, B, D, V, k, E]\n", "\n", " # Compute edge messages\n", " # edge_weight: [N, V, k, E, E]\n", " messages = torch.einsum('nvkeo,nbdvke->nbdvko', self.edge_weight, neighbor_feats)\n", " # messages: [N, B, D, V, k, E]\n", "\n", " # Compute routing weights\n", " route_logits = torch.einsum('nvke,nbdvke->nbdvk', self.route_weight, neighbor_feats)\n", " route_logits = route_logits + self.route_bias.view(N, 1, 1, V, -1)\n", " route_weights = F.softmax(route_logits, dim=-1) # [N, B, D, V, k]\n", "\n", " # Aggregate messages\n", " aggregated = (messages * route_weights.unsqueeze(-1)).sum(dim=-2) # [N, B, D, V, E]\n", "\n", " # Output transform with residual\n", " out = torch.einsum('nveo,nbdve->nbdvo', self.out_weight, aggregated)\n", " out = out + self.out_bias.view(N, 1, 1, V, E)\n", "\n", " return vertices + out # Residual connection\n", "\n", "\n", "class CayleyMengerExit(nn.Module):\n", " \"\"\"\n", " Computes true Cayley-Menger geometry from vertex positions.\n", "\n", " - Projects accumulated features to vertex coordinates\n", " - Computes TRUE Euclidean distances via Gram matrix\n", " - Computes CM determinant (guaranteed valid!)\n", " - Outputs energy based on geometry\n", " \"\"\"\n", "\n", " def __init__(self, n: int, vertex_dim: int, num_layers: int, k: int):\n", " super().__init__()\n", " self.n = n\n", " self.k = k\n", " self.num_vertices = k + 1 # V\n", " self.vertex_dim = vertex_dim # E (embedding dim per vertex)\n", " self.coord_dim = k # Minimum dims to embed k-simplex\n", " self.num_pairs = (self.num_vertices * (self.num_vertices - 1)) // 2\n", "\n", " # Layer accumulation weights (holographic: all layers contribute)\n", " inverse_init = torch.linspace(1.0, 0.1, num_layers).unsqueeze(0).expand(n, -1).clone()\n", " self.inverse_weights = nn.Parameter(inverse_init)\n", "\n", " # Project vertex features → coordinates in R^k\n", " self.to_coords = nn.Parameter(torch.empty(n, self.num_vertices, self.coord_dim, vertex_dim))\n", " self.coord_bias = nn.Parameter(torch.zeros(n, self.num_vertices, self.coord_dim))\n", "\n", " # Energy from geometry: distances + volume → scalar\n", " self.energy_weight = nn.Parameter(torch.empty(n, 1, self.num_pairs + 1))\n", " self.energy_bias = nn.Parameter(torch.zeros(n, 1))\n", "\n", " self._init_weights()\n", " self._register_pair_indices()\n", "\n", " def _init_weights(self):\n", " for i in range(self.n):\n", " for v in range(self.num_vertices):\n", " nn.init.kaiming_uniform_(self.to_coords[i, v], a=math.sqrt(5))\n", " nn.init.kaiming_uniform_(self.energy_weight[i], a=math.sqrt(5))\n", "\n", " def _register_pair_indices(self):\n", " pair_i, pair_j = [], []\n", " for i in range(self.num_vertices):\n", " for j in range(i + 1, self.num_vertices):\n", " pair_i.append(i)\n", " pair_j.append(j)\n", " self.register_buffer('pair_i', torch.tensor(pair_i))\n", " self.register_buffer('pair_j', torch.tensor(pair_j))\n", "\n", " def compute_distances_sq(self, coords: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:\n", " \"\"\"\n", " Compute true Euclidean squared distances via Gram matrix.\n", " coords: [N, B, D, V, C] - V vertices in C-dimensional space\n", " returns: d_sq_pairs [N, B, D, num_pairs], d_sq_full [N, B, D, V, V]\n", " \"\"\"\n", " # Gram matrix: G_ij = \n", " gram = torch.einsum('nbdvc,nbdwc->nbdvw', coords, coords)\n", "\n", " # Squared norms (diagonal)\n", " norms_sq = torch.diagonal(gram, dim1=-2, dim2=-1) # [N, B, D, V]\n", "\n", " # d²(i,j) = ||v_i||² + ||v_j||² - 2\n", " d_sq_full = norms_sq.unsqueeze(-1) + norms_sq.unsqueeze(-2) - 2 * gram\n", "\n", " # Ensure non-negative (numerical stability)\n", " d_sq_full = F.relu(d_sq_full)\n", "\n", " # Extract upper triangle pairs\n", " d_sq_pairs = d_sq_full[..., self.pair_i, self.pair_j]\n", "\n", " return d_sq_pairs, d_sq_full\n", "\n", " def cayley_menger_volume_sq(self, d_sq_full: torch.Tensor) -> torch.Tensor:\n", " \"\"\"\n", " Compute squared volume from CM determinant.\n", " d_sq_full: [N, B, D, V, V]\n", " returns: [N, B, D]\n", " \"\"\"\n", " N, B, D, V, _ = d_sq_full.shape\n", "\n", " # CM matrix is (V+1) x (V+1)\n", " cm_size = V + 1\n", " cm = torch.zeros(N, B, D, cm_size, cm_size, device=d_sq_full.device, dtype=d_sq_full.dtype)\n", "\n", " # Border: first row/col = 1 (except [0,0] = 0)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", "\n", " # Interior: squared distances\n", " cm[..., 1:, 1:] = d_sq_full\n", "\n", " # Determinant\n", " det = torch.linalg.det(cm)\n", "\n", " # Volume² = (-1)^(k+1) * det / (2^k * (k!)²)\n", " sign = (-1.0) ** (self.k + 1)\n", " factorial_k = math.factorial(self.k)\n", " prefactor = sign / ((2.0 ** self.k) * (factorial_k ** 2))\n", "\n", " vol_sq = prefactor * det\n", "\n", " return vol_sq\n", "\n", " def forward(self, layer_outputs: List[torch.Tensor]) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:\n", " \"\"\"\n", " layer_outputs: list of [N, B, D, V, E] tensors\n", " returns: energy, d_sq_pairs, vol_sq, coords\n", " \"\"\"\n", " # Stack layers: [N, B, D, V, E, L]\n", " stacked = torch.stack(layer_outputs, dim=-1)\n", "\n", " # Weighted accumulation (holographic)\n", " weights = F.softmax(self.inverse_weights, dim=1) # [N, L]\n", " weights = weights.view(self.n, 1, 1, 1, 1, -1)\n", " accumulated = (stacked * weights).sum(dim=-1) # [N, B, D, V, E]\n", "\n", " # Project to coordinates in R^k\n", " # to_coords: [N, V, C, E], accumulated: [N, B, D, V, E]\n", " coords = torch.einsum('nvce,nbdve->nbdvc', self.to_coords, accumulated)\n", " coords = coords + self.coord_bias.view(self.n, 1, 1, self.num_vertices, self.coord_dim)\n", " # coords: [N, B, D, V, C]\n", "\n", " # True distances\n", " d_sq_pairs, d_sq_full = self.compute_distances_sq(coords)\n", "\n", " # CM volume (guaranteed valid!)\n", " vol_sq = self.cayley_menger_volume_sq(d_sq_full)\n", "\n", " # Energy from geometry\n", " combined = torch.cat([d_sq_pairs, vol_sq.unsqueeze(-1)], dim=-1)\n", " energy = torch.einsum('nep,nbdp->nbde', self.energy_weight, combined)\n", " energy = energy.squeeze(-1) + self.energy_bias.view(self.n, 1, 1)\n", "\n", " return energy, d_sq_pairs, vol_sq, coords\n", "\n", "\n", "class WideKSimplexLinear(nn.Module):\n", " \"\"\"\n", " True Cayley-Menger KSimplex linear layer.\n", "\n", " Replaces standard linear with simplex geometry:\n", " - Input projected to k+1 vertex features\n", " - Vertices communicate along simplex edges\n", " - CM geometry (distances, volume) drives output\n", " \"\"\"\n", "\n", " def __init__(self, n: int, input_dim: int, output_dim: int, k: int = 4, vertex_dim: int = 8):\n", " super().__init__()\n", " self.n = n\n", " self.input_dim = input_dim\n", " self.output_dim = output_dim\n", " self.k = k\n", " self.num_vertices = k + 1\n", " self.vertex_dim = vertex_dim\n", " self.depth = k + 1 # Layers = vertices (simplex depth)\n", "\n", " # Entry: project input to vertex features\n", " self.entry = nn.Parameter(torch.empty(n, self.num_vertices, vertex_dim, input_dim))\n", " self.entry_bias = nn.Parameter(torch.zeros(n, self.num_vertices, vertex_dim))\n", "\n", " # Simplex edge layers\n", " self.layers = nn.ModuleList([\n", " SimplexEdgeLayer(n, self.num_vertices, vertex_dim)\n", " for _ in range(self.depth)\n", " ])\n", "\n", " # CM exit\n", " self.exit = CayleyMengerExit(n, vertex_dim, self.depth, k)\n", "\n", " # Output projection\n", " self.out_proj = nn.Parameter(torch.empty(n, output_dim, self.exit.num_pairs + 1))\n", " self.out_bias = nn.Parameter(torch.zeros(n, output_dim))\n", "\n", " self._init_weights()\n", "\n", " def _init_weights(self):\n", " for i in range(self.n):\n", " for v in range(self.num_vertices):\n", " nn.init.kaiming_uniform_(self.entry[i, v], a=math.sqrt(5))\n", " nn.init.kaiming_uniform_(self.out_proj[i], a=math.sqrt(5))\n", "\n", " def forward(self, x: torch.Tensor, return_geometry: bool = False):\n", " \"\"\"\n", " x: [N, B, D] - N parallel, B batch, D input dim\n", " returns: [N, B, O] output, optionally geometry\n", " \"\"\"\n", " N, B, D = x.shape\n", "\n", " # Project to vertex features: [N, B, D] → [N, B, 1, V, E]\n", " # Treat D as a batch dimension\n", " h = torch.einsum('nvei,nbd->nbdve', self.entry, x)\n", " h = h + self.entry_bias.view(N, 1, 1, self.num_vertices, self.vertex_dim)\n", " # h: [N, B, D, V, E] but D=input_dim, we want spatial dim\n", "\n", " # Actually, let's treat it correctly:\n", " # x: [N, B, input_dim] → vertices: [N, B, V, E]\n", " h = torch.einsum('nvei,nbi->nbve', self.entry, x)\n", " h = h + self.entry_bias.view(N, 1, self.num_vertices, self.vertex_dim)\n", " # h: [N, B, V, E]\n", "\n", " # Add dummy spatial dim for compatibility (D=1)\n", " h = h.unsqueeze(2) # [N, B, 1, V, E]\n", "\n", " # Process through simplex layers\n", " layer_outputs = []\n", " for layer in self.layers:\n", " h = layer(h)\n", " layer_outputs.append(h)\n", "\n", " # CM exit\n", " energy, d_sq, vol_sq, coords = self.exit(layer_outputs)\n", " # energy: [N, B, 1], d_sq: [N, B, 1, pairs], vol_sq: [N, B, 1]\n", "\n", " # Squeeze spatial dim\n", " energy = energy.squeeze(2) # [N, B]\n", " d_sq = d_sq.squeeze(2) # [N, B, pairs]\n", " vol_sq = vol_sq.squeeze(2) # [N, B]\n", "\n", " # Output from full geometry\n", " geom = torch.cat([d_sq, vol_sq.unsqueeze(-1)], dim=-1) # [N, B, pairs+1]\n", " out = torch.einsum('noi,nbi->nbo', self.out_proj, geom)\n", " out = out + self.out_bias.view(N, 1, -1)\n", "\n", " if return_geometry:\n", " return out, {\n", " 'energy': energy,\n", " 'd_sq': d_sq,\n", " 'vol_sq': vol_sq,\n", " 'coords': coords.squeeze(2), # [N, B, V, C]\n", " }\n", " return out\n", "\n", "\n", "# ============================================================================\n", "# FASHIONMNIST MODEL\n", "# ============================================================================\n", "\n", "class FashionKSimplexNet(nn.Module):\n", " \"\"\"KSimplex classifier for FashionMNIST.\"\"\"\n", "\n", " def __init__(self, k: int = 4, vertex_dim: int = 16, num_classes: int = 10):\n", " super().__init__()\n", " self.k = k\n", "\n", " # Simple conv stem\n", " self.stem = nn.Sequential(\n", " nn.Conv2d(1, 32, 3, padding=1),\n", " nn.BatchNorm2d(32),\n", " nn.GELU(),\n", " nn.Conv2d(32, 64, 3, stride=2, padding=1), # 14x14\n", " nn.BatchNorm2d(64),\n", " nn.GELU(),\n", " nn.Conv2d(64, 128, 3, stride=2, padding=1), # 7x7\n", " nn.BatchNorm2d(128),\n", " nn.GELU(),\n", " )\n", "\n", " self.pool = nn.AdaptiveAvgPool2d(1)\n", " self.flat_dim = 128\n", "\n", " # KSimplex replaces final linear\n", " # n=1 (single parallel), input=128, output=num_classes\n", " self.ksimplex = WideKSimplexLinear(\n", " n=1,\n", " input_dim=self.flat_dim,\n", " output_dim=num_classes,\n", " k=k,\n", " vertex_dim=vertex_dim\n", " )\n", "\n", " def forward(self, x: torch.Tensor, return_geometry: bool = False):\n", " B = x.size(0)\n", "\n", " # Conv features\n", " h = self.stem(x)\n", " h = self.pool(h).flatten(1) # [B, 128]\n", "\n", " # Add N dimension for ksimplex (n=1)\n", " h = h.unsqueeze(0) # [1, B, 128]\n", "\n", " # KSimplex forward\n", " if return_geometry:\n", " out, geom = self.ksimplex(h, return_geometry=True)\n", " return out.squeeze(0), geom # [B, classes]\n", "\n", " out = self.ksimplex(h)\n", " return out.squeeze(0)\n", "\n", "\n", "# ============================================================================\n", "# TRAINING\n", "# ============================================================================\n", "\n", "def train():\n", " # Data\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_data = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_data = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", "\n", " train_loader = DataLoader(train_data, batch_size=128, shuffle=True, num_workers=2)\n", " test_loader = DataLoader(test_data, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " # Model\n", " model = FashionKSimplexNet(k=2, vertex_dim=16, num_classes=10).to(device)\n", "\n", " total_params = sum(p.numel() for p in model.parameters())\n", " print(f\"Model params: {total_params:,}\")\n", " print(f\"k={model.k}, vertices={model.k+1}, pairs={(model.k+1)*model.k//2}\")\n", "\n", " # Training\n", " optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30)\n", "\n", " best_acc = 0\n", "\n", " print(\"\\nTraining...\")\n", " print(\"=\"*70)\n", "\n", " for epoch in range(30):\n", " # Train\n", " model.train()\n", " train_loss, correct, total = 0, 0, 0\n", "\n", " for images, labels in train_loader:\n", " images, labels = images.to(device), labels.to(device)\n", "\n", " optimizer.zero_grad()\n", " logits = model(images)\n", " loss = F.cross_entropy(logits, labels)\n", " loss.backward()\n", " optimizer.step()\n", "\n", " train_loss += loss.item() * images.size(0)\n", " correct += (logits.argmax(1) == labels).sum().item()\n", " total += images.size(0)\n", "\n", " train_acc = correct / total\n", " train_loss = train_loss / total\n", "\n", " # Eval\n", " model.eval()\n", " correct, total = 0, 0\n", " vol_stats = []\n", "\n", " with torch.no_grad():\n", " for images, labels in test_loader:\n", " images, labels = images.to(device), labels.to(device)\n", " logits, geom = model(images, return_geometry=True)\n", " correct += (logits.argmax(1) == labels).sum().item()\n", " total += images.size(0)\n", " vol_stats.append(geom['vol_sq'].cpu())\n", "\n", " test_acc = correct / total\n", " scheduler.step()\n", "\n", " if test_acc > best_acc:\n", " best_acc = test_acc\n", "\n", " # Volume stats\n", " vol_all = torch.cat(vol_stats, dim=1).squeeze(0) # [B_total]\n", " vol_mean = vol_all.mean().item()\n", " vol_std = vol_all.std().item()\n", " vol_pos = (vol_all > 0).float().mean().item()\n", "\n", " if epoch % 5 == 0 or epoch == 29:\n", " print(f\"Epoch {epoch+1:2d} | Loss: {train_loss:.4f} | Train: {train_acc:.2%} | Test: {test_acc:.2%} | Best: {best_acc:.2%}\")\n", " print(f\" | Vol²: μ={vol_mean:.4f}, σ={vol_std:.4f}, valid={vol_pos:.1%}\")\n", "\n", " print(\"=\"*70)\n", " print(f\"Best Accuracy: {best_acc:.2%}\")\n", "\n", " return model\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRY ANALYSIS\n", "# ============================================================================\n", "\n", "def analyze_geometry(model):\n", " print(\"\\n\" + \"=\"*70)\n", " print(\"GEOMETRY ANALYSIS\")\n", " print(\"=\"*70)\n", "\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", " test_data = datasets.FashionMNIST('./data', train=False, transform=transform)\n", " test_loader = DataLoader(test_data, batch_size=256, shuffle=False)\n", "\n", " model.eval()\n", "\n", " all_d_sq = []\n", " all_vol_sq = []\n", " all_coords = []\n", " all_labels = []\n", "\n", " with torch.no_grad():\n", " for images, labels in test_loader:\n", " images = images.to(device)\n", " _, geom = model(images, return_geometry=True)\n", " all_d_sq.append(geom['d_sq'].cpu())\n", " all_vol_sq.append(geom['vol_sq'].cpu())\n", " all_coords.append(geom['coords'].cpu())\n", " all_labels.append(labels)\n", "\n", " d_sq = torch.cat(all_d_sq, dim=1).squeeze(0) # [B, pairs]\n", " vol_sq = torch.cat(all_vol_sq, dim=1).squeeze(0) # [B]\n", " coords = torch.cat(all_coords, dim=1).squeeze(0) # [B, V, C]\n", " labels = torch.cat(all_labels, dim=0) # [B]\n", "\n", " print(f\"\\nDistances² shape: {list(d_sq.shape)}\")\n", " print(f\"Volume² shape: {list(vol_sq.shape)}\")\n", " print(f\"Coords shape: {list(coords.shape)}\")\n", "\n", " print(f\"\\nDistance² stats:\")\n", " for p in range(d_sq.shape[1]):\n", " print(f\" Pair {p}: μ={d_sq[:, p].mean():.4f}, σ={d_sq[:, p].std():.4f}\")\n", "\n", " print(f\"\\nVolume² stats:\")\n", " print(f\" μ={vol_sq.mean():.4f}, σ={vol_sq.std():.4f}\")\n", " print(f\" min={vol_sq.min():.4f}, max={vol_sq.max():.4f}\")\n", " print(f\" valid (>0): {(vol_sq > 0).float().mean():.1%}\")\n", "\n", " # Per-class volume\n", " print(f\"\\nPer-class Volume²:\")\n", " class_names = ['T-shirt', 'Trouser', 'Pullover', 'Dress', 'Coat',\n", " 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Boot']\n", " for c in range(10):\n", " mask = labels == c\n", " vol_c = vol_sq[mask]\n", " print(f\" {class_names[c]:10s}: μ={vol_c.mean():.4f}, σ={vol_c.std():.4f}\")\n", "\n", " # Visualization\n", " fig, axes = plt.subplots(2, 3, figsize=(15, 10))\n", "\n", " # Distance distributions\n", " ax = axes[0, 0]\n", " for p in range(min(5, d_sq.shape[1])):\n", " ax.hist(d_sq[:, p].numpy(), bins=50, alpha=0.5, label=f'Pair {p}')\n", " ax.set_xlabel('Distance²')\n", " ax.set_ylabel('Count')\n", " ax.set_title('Pairwise Distance² Distributions')\n", " ax.legend()\n", "\n", " # Volume distribution\n", " ax = axes[0, 1]\n", " ax.hist(vol_sq.numpy(), bins=50, color='purple', alpha=0.7)\n", " ax.axvline(x=0, color='red', linestyle='--', label='Valid threshold')\n", " ax.set_xlabel('Volume²')\n", " ax.set_ylabel('Count')\n", " ax.set_title('CM Volume² Distribution')\n", " ax.legend()\n", "\n", " # Per-class volume\n", " ax = axes[0, 2]\n", " vol_by_class = [vol_sq[labels == c].mean().item() for c in range(10)]\n", " ax.bar(range(10), vol_by_class, color='steelblue')\n", " ax.set_xticks(range(10))\n", " ax.set_xticklabels([n[:6] for n in class_names], rotation=45, ha='right')\n", " ax.set_ylabel('Mean Volume²')\n", " ax.set_title('Volume² by Class')\n", "\n", " # Vertex coords (first 2 dims of first vertex)\n", " ax = axes[1, 0]\n", " for c in range(10):\n", " mask = labels == c\n", " ax.scatter(coords[mask, 0, 0].numpy(), coords[mask, 0, 1].numpy(),\n", " alpha=0.3, s=5, label=class_names[c])\n", " ax.set_xlabel('Vertex 0, Dim 0')\n", " ax.set_ylabel('Vertex 0, Dim 1')\n", " ax.set_title('Vertex 0 Coordinates by Class')\n", "\n", " # Distance vs volume\n", " ax = axes[1, 1]\n", " mean_d = d_sq.mean(dim=1)\n", " ax.scatter(mean_d.numpy(), vol_sq.numpy(), alpha=0.2, s=5)\n", " ax.set_xlabel('Mean Distance²')\n", " ax.set_ylabel('Volume²')\n", " ax.set_title('Distance vs Volume Relationship')\n", "\n", " # Simplex shape analysis\n", " ax = axes[1, 2]\n", " # Normalized distances (relative to first)\n", " d_normalized = d_sq / (d_sq[:, 0:1] + 1e-6)\n", " for p in range(1, min(5, d_sq.shape[1])):\n", " ax.hist(d_normalized[:, p].numpy(), bins=50, alpha=0.5, label=f'd{p}/d0')\n", " ax.set_xlabel('Normalized Distance')\n", " ax.set_ylabel('Count')\n", " ax.set_title('Simplex Shape (Normalized Distances)')\n", " ax.legend()\n", "\n", " plt.tight_layout()\n", " plt.savefig('ksimplex_geometry.png', dpi=150, bbox_inches='tight')\n", " plt.show()\n", "\n", " return d_sq, vol_sq, coords, labels\n", "\n", "\n", "# ============================================================================\n", "# RUN\n", "# ============================================================================\n", "\n", "if __name__ == \"__main__\":\n", " model = train()\n", " d_sq, vol_sq, coords, labels = analyze_geometry(model)" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "cellView": "form", "id": "2bupmS5D7E_T", "outputId": "49122bf9-4662-4f32-f55c-9ad458abc6e8" }, "execution_count": 4, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "Model params: 106,834\n", "k=2, vertices=3, pairs=3\n", "\n", "Training...\n", "======================================================================\n", "Epoch 1 | Loss: 1.3680 | Train: 48.35% | Test: 50.96% | Best: 50.96%\n", " | Vol²: μ=16.1708, σ=24.7744, valid=100.0%\n", "Epoch 6 | Loss: 0.4053 | Train: 85.61% | Test: 83.02% | Best: 83.02%\n", " | Vol²: μ=36.1256, σ=47.2951, valid=100.0%\n", "Epoch 11 | Loss: 0.3009 | Train: 89.18% | Test: 86.09% | Best: 86.37%\n", " | Vol²: μ=38.3405, σ=48.9495, valid=99.9%\n", "Epoch 16 | Loss: 0.2307 | Train: 91.56% | Test: 88.37% | Best: 88.37%\n", " | Vol²: μ=62.7198, σ=94.9429, valid=100.0%\n", "Epoch 21 | Loss: 0.1651 | Train: 93.91% | Test: 88.42% | Best: 88.69%\n", " | Vol²: μ=76.7864, σ=104.4838, valid=99.9%\n", "Epoch 26 | Loss: 0.1153 | Train: 95.79% | Test: 88.45% | Best: 88.73%\n", " | Vol²: μ=88.7963, σ=108.6138, valid=100.0%\n", "Epoch 30 | Loss: 0.0981 | Train: 96.48% | Test: 88.69% | Best: 88.73%\n", " | Vol²: μ=104.2887, σ=127.8587, valid=100.0%\n", "======================================================================\n", "Best Accuracy: 88.73%\n", "\n", "======================================================================\n", "GEOMETRY ANALYSIS\n", "======================================================================\n", "\n", "Distances² shape: [10000, 3]\n", "Volume² shape: [10000]\n", "Coords shape: [10000, 3, 2]\n", "\n", "Distance² stats:\n", " Pair 0: μ=96.5467, σ=107.5725\n", " Pair 1: μ=142.0466, σ=182.9677\n", " Pair 2: μ=185.9473, σ=176.7757\n", "\n", "Volume² stats:\n", " μ=104.2887, σ=127.8587\n", " min=-0.0006, max=930.7391\n", " valid (>0): 100.0%\n", "\n", "Per-class Volume²:\n", " T-shirt : μ=28.1097, σ=19.5074\n", " Trouser : μ=379.4676, σ=151.7682\n", " Pullover : μ=48.7069, σ=35.9273\n", " Dress : μ=68.1964, σ=27.3748\n", " Coat : μ=129.3032, σ=77.7689\n", " Sandal : μ=7.4617, σ=12.0020\n", " Shirt : μ=115.8989, σ=81.0484\n", " Sneaker : μ=7.1859, σ=9.3125\n", " Bag : μ=148.3312, σ=120.7901\n", " Boot : μ=110.2260, σ=56.2804\n" ] }, { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/png": "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Hh4eevnll7Nt+48//tAnn3yiNm3aZEqgS/8M7PrXv/6lChUqZNvG999/r+DgYJUvX16Ojo6qUqWKJk+enGm6mjtdt0u3/6wB5BYj0VEgjh8/LkmW26hmzpypp59+Wt27d1dycrK+/vprvfDCC1q9erWCg4Pv2N4rr7yismXLavz48bp69apq166tEiVKaPv27ZZOcseOHbKzs9O+ffuUmJgod3d3paena9euXRo0aFC2bX/22WcaPny4nn/+eb366qu6ceOG9u/fr927d6tbt26SpAsXLqhx48aWC56yZctq3bp16t+/vxITE7PsNHLKzc1Nzz33nObPn6/Dhw+rVq1aWdYbPHiwvv32Ww0dOlT+/v7666+/tHPnTv3666969NFHNW7cOCUkJOiPP/6wfOOb8TCRxMREff755+ratasGDhyoy5cva/78+QoKCtLPP/+s+vXrW+0rPDxcly9f1ssvvyyTyaSpU6eqU6dOOnHihGXU1f79+9W0aVM5ODho0KBBqlSpko4fP65Vq1bpnXfeuevztmfPHr344otasGCB5YI1twICAlSlShVFRkZmWyc5OVlBQUFKSkrSsGHD5O3trT///FOrV69WfHy8PDw89NVXX2nAgAFq2LCh5W+oSpUqVu288MILevjhh/Xuu+9mukC+1bZt2/TNN99o+PDhcnR01Jw5c9SuXTv9/PPPql279l0dY05iu9mECRM0ceJEtW7dWkOGDNHRo0c1d+5c7dmzRz/++KPVaLq///5b7dq1U6dOnfTiiy/q22+/1dixY1WnTh21b99eUs7+3wDA7axatUqVK1fWE088cVfbdevWTW3bttXx48ct73vh4eF6/vnnczwyuHv37po4caLCw8OtvnxPS0vT0qVL1bRpU1WsWFHXr19XYGCgjh07pqFDh8rPz0/Lli1Tnz59FB8fnynJfy927NihH374QSEhIZKkKVOmqEOHDhozZozmzJmjV155RX///bemTp2qfv36afPmzZZtN2/erPbt26tBgwZ66623ZGdnpwULFqhly5basWOHGjZsaKn7ySefaO7cudq6das8PDzuKeaePXvq9ddf14YNGzRw4MAs6xw6dEgdOnRQ3bp1NWnSJDk6OurYsWOWBH/NmjU1adIkjR8/XoMGDVLTpk0lyerv4q+//lL79u3VpUsX9ejRQ15eXreN6513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WDGbzfL397d1GAAAFAkk0W0g6sRfVq8DKpe2USQAACDHkpKkDh3UXNLSdl8oLRdJdAAA8ku/fv1yVO+LL77I50gAACh6SKIDAAAAAFDIhYWFydfXV4888ogMw7B1OAAAFCkk0QEAAAAAKOSGDBmiJUuW6OTJk+rbt6969OihUqVK2TosAACKBDtbBwAAAAAAAO7N7Nmzdf78eY0ZM0arVq2Sj4+PXnzxRUVERDAyHQCAe0QSHQAAAACAIsDR0VFdu3ZVZGSkDh8+rFq1aumVV15RpUqVdOXKFVuHBwBAoUUSHQAAAACAIsbOzk4mk0mGYSgtLc3W4QAAUKiRRAcAALmyfft2dezYUeXLl5fJZNJ3331nVW4YhsaPH69y5crJ2dlZrVu31u+//25V59KlS+revbvc3d1VokQJ9e/fP9NIuf3796tp06ZycnKSj4+Ppk6dmt+HBgBAoZSUlKQlS5aoTZs2qlatmg4cOKCPP/5YZ86ckZub2123d6e+vk+fPjKZTFZLu3btrOrkpK8HAOB+RxIdAADkytWrV1WvXj3Nnj07y/KpU6fqo48+0rx587R79265uroqKChIN27csNTp3r27Dh06pMjISK1evVrbt2/XoEGDLOWJiYlq27atfH19FR0drffff18TJkzQp59+mu/Hl4nZLH38sX6p3UfpdjybHQBwf3nllVdUrlw5/ec//1GHDh109uxZLVu2TE899ZTs7HJ36X+nvl6S2rVrp/Pnz1uWJUuWWJXfqa8HAKAw4AoQAADkSvv27dW+ffssywzD0Icffqg33nhDzzzzjCTpyy+/lJeXl7777jt16dJFv/76q9avX689e/bosccekyTNmjVLTz31lD744AOVL19eixcvVnJysr744guZzWbVqlVLMTExmj59erYX4ElJSUpKSrK8TkxMzJsDdnCQQkL0+/old64LAEABmzdvnipWrKjKlStr27Zt2rZtW5b1VqxYkeM2b9fXZ3B0dJS3t3eWZTnp62+Vb/04AAD3gJHoAAAgz508eVKxsbFq3bq1ZZ2Hh4caNWqkqKgoSVJUVJRKlChhuaiWpNatW8vOzk67d++21GnWrJnMZrOlTlBQkI4ePaq///47y31PmTJFHh4elsXHxyc/DhEAgPtKr1691KJFC5UoUcKqH7x1yWtbt26Vp6enqlevriFDhuivv/6y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pVJh8uTJmDx5crJlsmfPjnXr1qW4ntKlS+Po0aOfHGdm8G/EBiwOzJFoep+yfQwQDREREREREREREdGXIcP2iU5EREREREREREREZGhMomci/0ZsSPZFRESU0UycOBEqlUrr5erqqsx/+/Yt+vbtixw5csDS0hLNmzfH06dPtZZx7949NGzYEBYWFsidOzeGDRuG2NhYfVeFiIiIiIiIvmIG7c6FiIiIvmwlSpTAvn37lPcmJv+79Bg8eDB27NiBjRs3wtraGv369UOzZs1w/PhxAEBcXBwaNmwIe3t7nDhxAo8fP0aHDh2QJUsW/Pjjj3qvCxEREREREX2dmEQnIiIinTExMYG9vX2i6eHh4fj999+xbt061KlTBwCwYsUKFCtWDKdOnUKVKlWwd+9eXLlyBfv27YOdnR3Kli2LKVOmYMSIEZg4cSJMTU31WxljY6BBAzw89wii4sN8REREREREXwveARIREZHOBAcHw8HBAQULFkS7du1w7949AEBAQADevXsHT09Ppayrqyvy58+PkydPAgBOnjyJUqVKwc7OTinj5eWFiIgIXL58Odl1RkdHIyIiQuuVLszMgB07cKTycMQb6zmBT0RERERERAbDJDoRERHphJubG1auXIndu3djyZIluH37NmrUqIFXr17hyZMnMDU1hY2NjdZn7Ozs8OTJEwDAkydPtBLomvmaecnx9fWFtbW18nJ0dEzfihEREREREdFXhd25EBERkU54e3sr/y9dujTc3Nzg5OSEv/76C+bm5jpb76hRozBkyBDlfUREBBPpRERERERE9MmYRM+gTt58YegQiIiI0pWNjQ2KFCmCGzduoG7duoiJiUFYWJhWa/SnT58qfajb29vjzJkzWst4+vSpMi85arUaarU6/SsQGQnkzo2W0XHYVHcJ4kzM0n8dRERERERElOEwif6FWxy4ONl5fcr20WMkRET0tXv9+jVu3ryJ9u3bo0KFCsiSJQv279+P5s2bAwCuXbuGe/fuwd3dHQDg7u6OadOmISQkBLlz5wYA+Pv7w8rKCsWLFzdMJaKiePFERERERET0leF9IBEREenEDz/8gEaNGsHJyQmPHj3ChAkTYGxsDB8fH1hbW6Nr164YMmQIsmfPDisrK/Tv3x/u7u6oUqUKAKBevXooXrw42rdvjxkzZuDJkycYO3Ys+vbtq5uW5kRE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u3YNv//+O2xtbXHw4EEUK1bM0CFlGI6OjnB0dNSapmmxf/z4cUybNo3Jc9IpdudCRERERERERESUAYSEhECtVsPa2trQoWRoH7bYL1eunKFDoi8cW6ITERERERERERFlALlz5zZ0CBkeW+yTIbAlOhEREREREREREWUabLFP+sYkOhERERERERERERFRMtjjPhERERERERERERFRMphEJyIiIiIiIiIiIiJKBpPoRERERERERERERETJYBKdiIiIiIiIiIiIiCgZTKITERERERERERERESWDSXQiIiIiIiIiIiIiomQwiU5ERERERERERERElAwm0YmIiIiIiIiIiIiIksEkOhERERERERERERFRMphEJyIiIiIiIiIiIiJKBpPoRERERERERERERETJYBKdiIiIiIiIiIiIiCgZTKITERERERERERERESWDSXQiIiIiIiIiIiIiomQwiU5ERERERERERERElAwm0YmIiIiIiIiIiIiIksEkOhERERERERERERFRMphEJ6J0MXHiRKhUKq1pBQoUQKdOnQwTkB6oVCr069fP0GFoqV27NmrXrm3oMIjoC5fUMZ8ytkOHDkGlUuHQoUOGDiVD0NX2UKlUmDhxYrouM63u3LkDlUqFWbNmfbQs/5aJMgZD3jdl9OPAypUroVKpcO7cOUOH8skaNGiA7t27GzoMvenUqRMKFCigNc0Q50dd7tu879Z25coVmJiY4NKlS4YORaeYRCe9ady4MSwsLPDq1atky7Rr1w6mpqZ48eLFZ6/v0aNHmDhxIgIDAz97WZ8qLCwMPXr0QK5cuZA1a1Z88803+Pfff9O0jM2bN8Pb2xs5c+aEqakpHBwc0KpVKxw4cEBHUWcu69atw9y5cw0dhs49ffoUP/zwA1xdXWFhYYGsWbOiQoUKmDp1KsLCwgwdHhFlYpqbU83LzMwMDg4O8PLywvz581M8b6dFRjgvZxalS5dG/vz5ISLJlqlWrRrs7OwQGxurx8gM48N91MTEBHnz5kWnTp3w8OFDvcezc+dOgyfKiShzCAoKQosWLeDk5AQzMzPkzZsXdevWxYIFCwwdmsHFx8dj9erVcHNzQ/bs2ZEtWzYUKVIEHTp0wKlTpwwdXro5fvw49u7dixEjRijTND/kqlQqBAQEJPpMp06dYGlpqc8wv2qdOnXSus6wtLREwYIF0aJF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Q/ilS5cetH7sCy+8kOrqaq699lpuvPFGDMPgpz/96ZTfhJ599tn86le/4sMf/jDnnnsuyWSSV7/61fzjP/4jd955J+9973t56KGHuOiii/B9n7Vr13LnnXfyhz/8gXPOOYcvfOELPPLII1x11VW0trbS19fH9773PVpaWiZt1DqdU045hSuuuIIbb7yRSCQy/k3EVJ/jt73tbbzxjW8E4Itf/OKMrkV1dTW/+93veOUrX8kZZ5zBP/zDP4x/U/fss8/yy1/+kgsuuGDax7/qVa/it7/9La973eu46qqr6Ojo4LbbbuPkk0+e9M3IP/3TPzE0NMRLXvISWlpa2LJlC9/5znc444wzxjvzTz75ZC699FLOPvtsampqePrpp7nrrru44YYbZvRahBBHt/vuu4+1a9fieR69vb388Y9/ZPny5bS2tnLvvfeOb4w9lZn8XTrd1+XFixczf/58/uVf/oUdO3ZQWVnJb37zm33eiHOiyy67jH/8x3/k29/+Nhs2bODKK68kCAL+/Oc/c9lll3HDDTcwf/58vvSlL/Hxj3+czs5Orr76aioqKujo6OB3v/sd7373u/mXf/mXPT5POBzm9a9/Pf/1X/9FPp/n61//+qT7169fz0tf+lLe/OY3c/LJJxMKhfjd735Hb2/vjCtZLrnkElpaWrjnnnuIxWK8/vWvn3T/l770JZYvX86yZct43/veRygU4gc/+AHlcpmvfvWrezz3P/3TP3HXXXdx5ZVX8uY3v5lNmzbxs5/9bHz662A5GNd6OjfddBNvetOb+PGPf8x73/tevvvd77Js2TJOPfVU3vWudzFv3jx6e3t54okn2L59O88///yU59mXP4djX4dvvPFGrrjiij1W7BzI52cqd9xxB9/73vd43etex/z58xkZGeGHP/whlZWVvPKVr9zn801nX76/EUJM7QMf+ACFQoHXve51LF68GMdxePzxx/nVr35FW1vbtBs/HkwLFixg2bJlXH/99ZTLZW655RZqa2unrU2Fmf+d3dfXx/XXXz/+dRXg1ltv5aGHHuLtb387jz766LS1Ltu3b+e8887jJS95CS996Utpamqir6+PX/7ylzz//PN86EMf2uc6iH35NyToetWbb76Zzs5OFi1axK9+9StWrFjB7bffPr6J9kz/TTmdq666ilAoxAMPPMC73/3uvb6GD37wg/z7v/87zz///KQJ+fr6ej7+8Y/z+c9/niuvvJLXvOY1rFu3ju9973uce+65473kh9K+Xt89+dvf/sYXv/hFXvziF9PX17fbT+b9wz/8A6Zpzii3gYOTUXieN76OUqnEli1buPfee1m5ciWXXXYZt99++x4f//KXv5ympiYuuugiGhsbWbNmDbfeeitXXXXVeM4y9v3DJz/5Sa655hrC4TCvfvWrefnLXz7+U+Hvec97yOVy/PCHP6ShoWG/f2Llpptu4q677uJNb3oT73znOzn77LMZGhri3nvv5bbbbuP0008/6JmJ67o8/PDDvO9979uvNR8TlBBHyHe/+10FqPPOO2/K+0dGRtTHP/5xtWDBAmXbtqqrq1MXXnih+vrXv64cx1FKKdXR0aEA9bWvfW3Kc9xzzz3q5JNPVqFQSAHqRz/60fh9zz33nHr961+vamtrVSQSUa2trerNb36zevDBB5VSSn3rW99SgPrNb34z6Zxbt25VlZWV6pWvfOVeX+PQ0JC67rrrVG1trYrH4+qSSy5Rf/3rX2dyecbddddd6uUvf7mqqalRoVBIzZo1S/3d3/2d+tOf/jTpuE2bNqk3vvGNKpVKqWg0qs477zz1+9//frfz9fb2qne84x2qrq5O2batTj311EnXRam9X9eHH35YnX322cq2bTVv3jx12223qc9+9rNq179SWltb1bXXXjv+8Y9+9CMF7HYNHnroIQWohx56aPy2xx57TL3oRS9SsVhMNTc3q4985CPqD3/4w27H5XI59fd///cqlUopQLW2to7f5ziOuvnmm9XSpUtVJBJR1dXV6uyzz1af//znVSaTUUop9eCDD6rXvva1qrm5Wdm2rZqbm9Vb3vIWtX79+ilf+0SAev/7369+9rOfqYULF6pIJKLOPPPMSeubqFwuq+rqalVVVaWKxeJezz9RV1eX+ud//me1aNEiFY1GVTweV2effbb68pe/PP5alFLqkksuUZdccsn4x0EQqH/9139Vra2t4+v7/e9/r6699tpJ12rsz1lDQ4OybVvNnTtXvec971Hd3d3jx3zpS19S5513nkqlUioWi6nFixerL3/5y+P/Pwohjk1jfzeP/bJtWzU1NamXvexl6lvf+pbKZrO7PWbXv/Nn+nfpdF+XV69erS6//HKVTCZVXV2dete73qWef/753b52X3vttSqRSOx1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wUmbvR2OoEMIIY4H//yyRUd6CdNKJpNcd9113HTTTdTW1tLQ0MAnP/lJTHPnvPbQ0BBbt26lq6sLgHXr1gH6Ddampibuv/9+Fi1aRFtb2/hjbrzxRi666CK+/vWv89rXvpY//OEPk/rQAd773vdy66238pGPfIR3vvOd/PGPf+TOO+/kf/7nfw7paz4hJtELhcKkTyKAZVkEQXCEVnSYTexB3/QAYMDc83U1yxQT50opCoUO0ulnKBT2Y/pbKRjYCFuf1L8fwPS4n04TuC6R9nYC18HvfOGgnFcIIYQQQgghjle5XI4VK1awYsUKQG8mumLFCrZu3YphGHzoQx/iS1/6Evfeey9/+9vfeNvb3kZzczNXX301oCcBr7zySt71rnfx1FNP8dhjj3HDDTdwzTXX0NzcDMDf//3fY9s21113HatWreJXv/oV3/rWtyZVo37wgx/k/vvv5xvf+AZr167lc5/7HE8//TQ33HDD4b4kQgghDrKvfe1rXHzxxbz61a/m8ssvZ9myZeNvvIKuajnzzDO56qqrALjmmms488wzue222wC45557dqtyedGLXsQPf/hDvvWtb3H66afzf//3f3zqU5+adEx7ezv/8z//w/Llyzn99NP5xje+wf/7f/+PK6644pC+XkOdAP0Yb3/723nggQf4wQ9+wNKlS3nuued497vfzTvf+U5uvvnmvT4+m81SVVVFJpM5Kqtd9mrrkzpAr1uI6l9PsakVp27OePf5rtUthUIHA4MPEwQuphmmrvYS4vF9mP4e2KhDe9/VlTELXjbapb7vyps7yP/hdwT5DKYRkJgfI1KfPODziukppXA6O/HTaaxUCrtt9z8jQoiD65j/OnOUk+srhBDiUDoav8786U9/4rLLLtvt9muvvZYf//jHKKX47Gc/y+233046nWbZsmV873vfY9GinVOPQ0ND3HDDDfz3f/83pmnyhje8gW9/+9skk8nxY1auXMn73/9+/vrXv1JXV8cHPvABPvrRj056zl//+td86lOforOzk4ULF/LVr36VV77ylTN+LQf7+j7xH/8y7X0XXPf1Az6/EELsi1KpREdHB+3t7USj0SO9nMPG8zwaGxu57777OO+88w758+3pOs/068wJUefyne98h09/+tO8733vo6+vj+bmZt7znvfwmc985kgv7fCY0INeNHMMuBsJMv2j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Yfz48YTD4WNY09abNGkS3333HYsXL+aDDz7g008/5YYbbjjiccfre/LNN99k+vTp3HPPPXz55ZcMHTqU8ePHU15e3mD5FStWcNVVVzFlyhS++uorLrnkEi655BK+/fbbdq5587W0jQBpaWn1nq+dO3e2Y41bLhAIMHToUJ566qlmld++fTsTJ07khz/8IevWreO2227jV7/6FR9++OExrqloqda8fkV9n3zyCVOnTuU///kPixcvJhaLccEFFxAIBJJljjSuMAyDiRMnEo1GWbFiBS+99BILFy5kzpw5HdGk49rq1at59tlnGTJkSL3t0sdto7q6mrFjx2Kz2fjXv/7Fhg0beOyxx8jMzEyWac7YqrXjmZPBQw89xNNPP80f//hHioqKeOihh3j44Yd58sknk2Wkj0VDToZxdXOcDGPvI5GxeUJL+6HOpk2b6r0eunTpcoxq2D6aMxY9VGf8fGhNP0Dn+3xoNSVa7OGHH1a9e/dussw555yjpk2b1j4VOgaO1Maamhpls9nUW2+9ldxWVFSkALVy5cr2qGKrLViwQKWnpzer7OTJk9XFF198TOtzrDS3naZpqvz8fPXII48kt9XU1CiHw6Fef/31Y1jD1tmwYYMC1OrVq5Pb/vWvfylN09TevXsbPe54fk+eeeaZaurUqcn7hmGorl27qnnz5jVY/oorrlATJ06st23UqFHqxhtvPKb1PBotbWNL3qfHI0C9++67TZa544471KBBg+ptu/LKK9X48eOPYc1Ea7T09SuOrLy8XAHqk08+UUo1b1zxz3/+U+m6rkpLS5Nlnn76aZWWlqYikUj7NuA45vP5VL9+/dTixYvrXfukj9vOnXfeqX7wgx80ur85Y6vWjmdOFhMnTlTXXXddvW2XXXaZmjRpklJK+lg07mQYVzfHyTb2PhIZmyc0px+WLl2qAFVdXd0udeooh45FG9JZPx8O1px+6OyfDy0hM9Fboba2lqysrCOWe/XVV8nJyeH0009n1qxZBIPBdqhd2zhSG9euXUssFmPcuHHJbQMHDqRnz56sXLmyParYbpYtW0aXLl0YMGAAN998M5WVlR1dpTa1fft2SktL6z2X6enpjBo16rh8LleuXElGRgYjR45Mbhs3bhy6rrNq1aomjz0e35PRaJS1a9fW639d1xk3blyj/b9y5cp65QHGjx9/XD5f0Lo2Avj9fgoLC+nRowcXX3wx3333XXtUt92caM/jyaq1r1/RtNraWoDkWKM544qVK1cyePBg8vLykmXGjx+P1+vtdJ8PR2Pq1KlMnDjxsM8X6eO287e//Y2RI0dy+eWX06VLF4YNG8bzzz+f3N+csdXRjGdOBmPGjGHJkiVs3rwZgK+//prPP/+cCy+8EJA+Fg07GcbVzSFj79bpjK+Fo3HGGWdQUFDA+eefz/Llyzu6Om3u0LFoQ06G10Rz+gHk86GOtaMrcKLZunUrTz75JI8++miT5X7+859TWFhI165d+eabb7jzzjvZtGkT77zzTjvVtPWa08bS0lLsdvthObfz8vJO+FxZB5swYQKXXXYZvXv3pri4mLvvvpsLL7yQlStXYrFYOrp6baLu+Tr4C3Pd/ePxuSwtLT3sT8msVitZWVlN1vd4fU9WVFRgGEaD/b9x48YGjyktLT1hni9oXRsHDBjAiy++yJAhQ6itreXRRx9lzJgxfPfdd3Tv3r09qn3MNfY8er1eQqEQLperg2omDtaa169ommma3HbbbYwdO5bTTz8daN64orH3TN0+AW+88QZffvklq1evPmyf9HHb2bZtG08//TTTp0/n7rvvZvXq1dx6663Y7XYmT57crLFVa8czJ4u77roLr9fLwIEDsVgsGIbB7373OyZNmgQ0b/wqfXzyORnG1c0hY+/WkbF5QkFBAc888wwjR44kEonw5z//mXPPPZdVq1YxfPjwjq5em2hoLNqQzvj5cLDm9oN8PnzvpA2i33XXXTz00ENNlikqKmLgwIHJ+3v37mXChAlcfvnlXH/99U0ee3CuvcGDB1NQUMB5551HcXExffv2PbrKN9OxbuPxoDVtbImf/exnyf8PHjyYIUOG0LdvX5YtW8Z5553XqnO2xrFu5/GguW1srePhPSmab/To0YwePTp5f8yYMZx66qk8++yzzJ07twNrJoQ4WlOnTuXbb7/l888/7+iqdCq7d+9m2rRpLF68+KRd7Km9mKbJyJEjeeCBBwAYNmwY3377Lc888wyTJ0/u4Np1Dn/5y1949dVXee211xg0aFAyP3HXrl2lj4U4BmTsLeoMGDCAAQMGJO+PGTOG4uJifv/73/PKK690YM3ajoxFE5rbD/L58L2TNoh+++23c8011zRZpk+fPsn/79u3jx/+8IeMGTOG5557rsWPN2rUKCAxy7u9AnbHso35+flEo1FqamrqzWgqKysjPz//aKrdIi1t49Hq06cPOTk5bN26tV2D6MeynXXPV1lZGQUFBcntZWVlnHHGGa06Z2s0t435+fmHLYgTj8epqqpq0WuvI96TDcnJycFisVBWVlZve1Pvpfz8/BaV72itaeOhbDYbw4YNY+vWrceiih2isecxLS3tpJnpciJoi9ev+N4tt9ySXNzv4JkrzRlX5Ofn88UXX9Q7X93zIs9FIl1LeXl5vVlihmHw6aef8sc//pEPP/xQ+riNFBQUcNppp9Xbduqpp/L2228DzRtbtdV4prOaOXMmd911V3JCy+DBg9m5cyfz5s1j8uTJ0seiQSfDuLo5ZOzdOjI2b9yZZ57ZaQLOjY1FG9IZPx/qtKQfDnUyfj7UOWlzoufm5jJw4MAmb3a7HUjMzj733HMZMWIECxYsQNdb3m3r1q0DqDfIO9aOZRtHjBiBzWZjyZIlyW2bNm1i165d9X6hOtZa0sa2sGfPHiorK9v1eYRj287evXuTn59f77n0er2sWrXquHwuR48eTU1NDWvXrk0e+/HHH2OaZjIw3hwd8Z5siN1uZ8SIEfX63zRNlixZ0mj/jx49ul55gMWLF7fr89USrWnjoQzDYP369R3+fLWlE+15PFm1xetXgFKKW265hXfffZePP/6Y3r1719vfnHHF6NGjWb9+fb2g2OLFi0lLSzssoHkyOu+881i/fj3r1q1L3kaOHMmkSZOS/5c+bhtjx45l06ZN9bZt3ryZwsJCoHljq7Yaz3RWwWDwsO8jFosF0zQB6WPRsJNhXN0cMvZunc74Wmgr69atO+FfC0caizakM74mWtMPhzoZPx+SOnhh0+Penj171CmnnKLOO+88tWfPHlVSUpK8HVxmwIABatWqVUoppbZu3aruv/9+tWbNGrV9+3b1/vvvqz59+qizzz67o5rRpNa0USmlbrrpJtWzZ0/18ccfqzVr1qjRo0er0aNHd0QTmmXnzp3qq6++Uvfdd5/yeDzqq6++Ul999ZXy+XzJMgMGDFDvvPOOUkopn8+nZsyYoVauXKm2b9+uPvroIzV8+HDVr18/FQ6HO6oZR9TSdiql1IMPPqgyMjLU+++/r7755ht18cUXq969e6tQKNQRTTiiCRMmqGHDhqlVq1apzz//XPXr109dddVVyf0n2nvyjTfeUA6HQy1cuFBt2LBB3XDDDSojI0OVlpYqpZT65S9/qe66665k+eXLlyur1aoeffRRVVRUpO655x5ls9nU+vXrO6oJR9TSNt53333qww8/VMXFxWrt2rXqZz/7mXI6neq7777rqCYckc/nS77fAPX444+rr776Su3cuVMppdRdd92lfvnLXybLb9u2TbndbjVz5kxVVFSknnrqKWWxWNSiRYs6qgmiEUd6/Yoju/nmm1V6erpatmxZvXFGMBhMljnSuCIej6vTTz9dXXDBBWrdunVq0aJFKjc3V82aNasjmnRCOOecc9S0adOS96WP28YXX3yhrFar+t3vfqe2bNmiXn31VeV2u9X/+3//L1mmOWOrI41nTmaTJ09W3bp1Ux988IHavn27euedd1ROTo664447kmWkj0VDToZxdXOcDGPvI5GxeUJL++H3v/+9eu+999SWLVvU+vXr1bRp05Su6+qjjz7qqCa0ieaMRU+Gz4fW9ENn/HxoLQmiH8GCBQsU0OCtzvbt2xWgli5dqpRSateuXerss89WWVlZyuFwqFNOOUXNnDlT1dbWdlArmtaaNiqlVCgUUr/+9a9VZmamcrvd6tJLL60XeD/eTJ48ucE2HtwmQC1YsEAppVQwGFQXXHCBys3NVTabTRUWFqrrr7/+uA+atLSdSillmqaaPXu2ysvLUw6HQ5133nlq06ZN7V/5ZqqsrFRXXXWV8ng8Ki0tTV177bX1fiQ4Ed+TTz75pOrZs6ey2+3qzDPPVP/5z3+S+8455xw1efLkeuX/8pe/qP79+yu73a4GDRqk/vGPf7RzjVuuJW287bbbkmXz8vLUf//3f6svv/yyA2rdfEuXLm3wvVfXrsmTJ6tzzjnnsGPOOOMMZbfbVZ8+feq9L8XxpanXrziyxsYZB7/mmzOu2LFjh7rwwguVy+VSOTk56vbbb1exWKydW3PiODSILn3cdv7+97+r008/XTkcDjVw4ED13HPP1dvfnLHVkcYzJzOv16umTZumevbsqZxOp+rTp4/67W9/qyKRSLKM9LFozMkwrm6Ozj72PhIZmye0tB8eeugh1bdvX+V0OlVWVpY699xz1ccff9wxlW9DzRmLngyfD63ph874+dBamlJKtcGEdiGEEEIIIYQQQgghhBCi0zlpc6ILIYQQQgghhBBCCCGEEEciQXQhhBBCCCGEEEIIIYQQohESRBdCCCGEEEIIIYQQQgghGiFBdCGEEEIIIYQQQgghhBCiERJEF0IIIYQQQgghhBBCCCEaIUF0IYQQQgghhBBCCCGEEKIREkQXQgghhBBCCCGEEEIIIRohQXQhhBBCCCGEEEIIIYQQohESRBdCnJDuvfdezjjjjI6uhhBCCNEpnXvuudx2220dXQ0hhBCi01m2bBmaplFTUwPAwoULycjIOKaPec0113DJJZccd+cSojk+/fRTLrroIrp27Yqmabz33nstPodSikcffZT+/fvjcDjo1q0bv/vd71p0DgmiC9HBLrroIiZMmNDgvs8++wxN0/jmm29ade4dO3agaRrr1q07iho231NPPUWvXr1wOp2MGjWKL774okXH19W37paamsqgQYOYOnUqW7ZsqVd2xowZLFmypC2rn3S07RBCCHFyueaaa9A0jZtuuumwfVOnTkXTNK655pr2r9ghFi5cmLzGWiwWMjMzGTVqFPfffz+1tbX1yr7zzjvMnTu3Wec9XgPu//nPfxg3bhxjx45l+PDhrFmzpqOrJIQQog3VXX8ffPDBetvfe+89NE3roFq13JVXXsnmzZs7tA51gX1N09B1nfT0dIYNG8Ydd9xBSUlJvbJ/+MMfWLhwYbPOKwF30RYCgQBDhw7lqaeeavU5pk2bxp///GceffRRNm7cyN/+9jfOPPPMFp1DguhCdLApU6awePFi9uzZc9i+BQsWMHLkSIYMGdLi80aj0baoXrO9+eabTJ8+nXvuuYcvv/ySoUOHMn78eMrLy1t8ro8++oiSkhK+/vprHnjgAYqKihg6dGi9oLnH4yE7O7stmwC0bTuEEEKcPHr06MEbb7xBKBRKbguHw7z22mv07NmzA2tWX1paGiUlJezZs4cVK1Zwww038PLLL3PGGWewb9++ZLmsrCxSU1M7sKZHb/jw4Xz00UcsX76cyy67jLfeequjqySEEKKNOZ1OHnroIaqrq9v0vO35fdrlctGlS5d2e7ymbNq0iX379rF69WruvPNOPvroI04//XTWr1+fLJOenn7MZ84LcbALL7yQ//t//y+XXnppg/sjkQgzZsygW7dupKSkMGrUKJYtW5bcX1RUxNNPP83777/Pj3/8Y3r37s2IESM4//zzW1QPCaIL0cH+53/+h9zc3MN+yfX7/bz11ltMmTIFgM8//5yzzjoLl8tFjx49uPXWWwkEAsnyvXr1Yu7cuVx99dWkpaVxww030Lt3bwCGDRuGpmmce+65yfJ//vOfOfXUU3E6nQwcOJA//elPyX3XXXcdQ4YMIRKJAIkBxLBhw7j66qsbbcfjjz/O9ddfz7XXXstpp53GM888g9vt5sUXX2xxn2RnZ5Ofn0+fPn24+OKL+eijjxg1ahRTpkzBMAzg8HQudb9wP/DAA+Tl5ZGRkcH9999PPB5n5syZZGVl0b17dxYsWNDkY7dlO4QQQpw8hg8fTo8ePXjnnXeS29555x169uzJsGHD6pU1TZN58+bRu3dvXC4XQ4cO5a9//Wtyv2EYTJkyJbl/wIAB/OEPf6h3jrrr3qOPPkpBQQHZ2dlMnTqVWCzWZD01TSM/P5+CggJOPfVUpkyZwooVK/D7/dxxxx3JcofOLv/Tn/5Ev379cDqd5OXl8dOf/jRZj08++YQ//OEPyRlsO3bsaLM2RCIR7rzzTnr06IHD4eCUU07hhRdeSO7/9ttvufDCC/F4POTl5fHLX/6SiooKAOx2OwCrV6/m3//+NzNnzmyyb4QQQpx4xo0bR35+PvPmzWuy3Ntvv82gQYNwOBz06tWLxx57rN7+hr5P16VZ+eCDDxgwYABut5uf/vSnBINBXnrpJXr16kVmZia33npr8nsqwCuvvMLIkSNJTU0lPz+fn//8501Oyjo0nUuvXr3q/YV23a3O7t27ueKKK8jIyCArK4uLL76YHTt2JPcbhsH06dPJyMggOzubO+64A6VUs/qzS5cu5Ofn079/f372s5+xfPlycnNzufnmm5NlDp1d/te//pXBgwfjcrnIzs5m3LhxBAIB7r33Xl566SXef//9ZBvqApt33nkn/fv3x+1206dPH2bPnl3v+l/3ff+VV16hV69epKen87Of/Qyfz5csY5omDz/8MKeccgoOh4OePXvWS89xpH4Sncctt9zCypUreeONN/jmm2+4/PLLmTBhQjKjwd///nf69OnDBx98QO/evenVqxe/+tWvqKqqatHjSBBdiA5mtVq5+uqrWbhwYb0L21tvvYVhGFx11VUUFxczYcIEfvKTn/DNN9/w5ptv8vnnn3PLLbfUO9ejjz7K0KFD+eqrr5g9e3YyDUndzO66L/avvvoqc+bM4Xe/+x1FRUU88MADzJ49m5deegmA+fPnEwgEuOuuuwD47W9/S01NDX/84x8bbEM0GmXt2rWMGzcuuU3XdcaNG8fKlSuPuo90XWfatGns3LmTtWvXNlru448/Zt++fXz66ac8/vjj3HPPPfzP//wPmZmZrFq1iptuuokbb7yxwVn/7dEOIYQQndt1111X78faF198kWuvvfawcvPmzePll1/mmWee4bvvvuM3v/kNv/jFL/jkk0+AxJfC7t2789Zbb7FhwwbmzJnD3XffzV/+8pd651m6dCnFxcUsXbqUl156iYULFzb7z6sP1qVLFyZNmsTf/va3ekGAOmvWrOHWW2/l/vvvZ9OmTSxatIizzz4bSPxJ9+jRo7n++uspKSmhpKSEHj16tFkbrr76al5//XXmz59PUVERzz77LB6PB4Camhp+9KMfMWzYMNasWcOiRYsoKyvjiiuuSB7/wgsv8OCDD/L++++Tk5PT4r4RQghxfLNYLDzwwAM8+eSTjX7PW7t2LVdccQU/+9nPWL9+Pffeey+zZ88+7Jp56PdpgGAwyPz583njjTdYtGgRy5Yt49JLL+Wf//wn//znP3nllVd49tln6/0YHovFmDt3Ll9//TXvvfceO3bsaFFat9WrVyevqXv27OG//uu/OOuss5LnHj9+PKmpqXz22WcsX74cj8fDhAkTkrPnH3vsMRYuXMiLL77I559/TlVVFe+++24LevV7LpeLm266ieXLlzf4Q0BJSQlXXXUV1113HUVFRSxbtozLLrsMpRQzZszgiiuuYMKECcn2jBkzBoDU1FQWLlzIhg0b+MMf/sDzzz/P73//+3rnLi4u5r333uODDz7ggw8+4JNPPqmXumfWrFk8+OCDzJ49mw0bNvDaa6+Rl5fX7H4SncOuXbtYsGABb731FmeddRZ9+/ZlxowZ/OAHP0iOy7dt28bOnTt56623ePnll1m4cCFr165NTgppNiWE6HBFRUUKUEuXLk1uO+uss9QvfvELpZRSU6ZMUTfccEO9Yz777DOl67oKhUJKKaUKCwvVJZdcUq/M9u3bFaC++uqretv79u2rXnvttXrb5s6dq0aPHp28v2LFCmWz2dTs2bOV1WpVn332WaP137t3rwLUihUr6m2fOXOmOvPMM5tufDPqq9T3ffTmm28qpZS655571NChQ5P7J0+erAoLC5VhGMltAwYMUGeddVbyfjweVykpKer1118/pu0QQghxcpk8ebK6+OKLVXl5uXI4HGrHjh1qx44dyul0qv3796uLL75YTZ48WSmlVDgcVm63+7BrzZQpU9RVV13V6GNMnTpV/eQnP6n3mIWFhSoejye3XX755erKK69s9BwLFixQ6enpDe57+umnFaDKysqUUkqdc845atq0aUoppd5++22VlpamvF5vg8ceXLYpLW3Dpk2bFKAWL17c4Pnmzp2rLrjggnrbdu/erQC1adMm9c477yiLxaLOPPNMNWrUKDVr1qwj1lEIIcSJo+76q5RS//Vf/6Wuu+46pZRS7777rjo43PXzn/9cnX/++fWOnTlzpjrttNOS9xv6Pr1gwQIFqK1btya33Xjjjcrtdiufz5fcNn78eHXjjTc2Ws/Vq1crIHnM0qVLFaCqq6uTj9PY9fnWW29VhYWFqry8XCml1CuvvKIGDBigTNNMlolEIsrlcqkPP/xQKaVUQUGBevjhh5P7Y7GY6t69e7KvGnJonQ72r3/9SwFq1apVSqn6/b527VoFqB07djR43oPLNuWRRx5RI0aMSN6/5557lNvtrjf2mDlzpho1apRSSimv16scDod6/vnnGzxfc/pJnJgA9e677ybvf/DBBwpQKSkp9W5Wq1VdccUVSimlrr/++uT4sE7da3fjxo3NfmzrUQb8hRBtYODAgYwZM4YXX3yRc889l61bt/LZZ59x//33A/D111/zzTff8OqrryaPUUphmibbt2/n1FNPBWDkyJFHfKxAIEBxcTFTpkzh+uuvT26Px+Okp6cn748ePZoZM2Ywd+5c7rzzTn7wgx+0VXNbRR2Ypd/UAjGDBg1C17//A5u8vDxOP/305H2LxUJ2drbkNxdCCHFM5ObmMnHixORfl02cOPGw2c9bt24lGAweloOxLnVanaeeeooXX3yRXbt2EQqFiEaj9dKYQeK6Z7FYkvcLCgrq5Sxtiaaus+effz6FhYX06dOHCRMmMGHCBC699FLcbneT5zzaNqxbtw6LxcI555zT4Pm//vprli5dmpyZfrDi4mIuvfRS4vF4k3UUQgjROTz00EP86Ec/YsaMGYftKyoq4uKLL663bezYsTzxxBMYhpG8DjX0fdrtdtO3b9/k/by8PHr16lXv2pOXl1fvO+batWu59957+frrr6mursY0TSAxY/a0005rdpuee+45XnjhBVasWEFubi6QuPZt3br1sHVLwuEwxcXF1NbWUlJSwqhRo5L7rFYrI0eObHZKl0M1NUYYOnQo5513HoMHD2b8+PFccMEF/PSnPyUzM7PJc7755pvMnz+f4uJi/H4/8XictLS0emV69epVr50FBQXJfi4qKiISiXDeeec1eP4j9ZPoPPx+PxaLhbVr19YbUwLJ92lBQQFWq5X+/fsn99XF0Xbt2sWAAQOa9VgSRBfiODFlyhT+93//l6eeeooFCxbQt2/f5JdGv9/PjTfeyK233nrYcQcvVpaSknLEx/H7/QA8//zz9S6sQL0PHNM0Wb58ORaLha1btzZ5zpycHCwWC2VlZfW2l5WVkZ+ff8Q6NUdRURFAMs97Q2w2W737mqY1uK1uEHOo9miHEEKIzu26665Lplt76qmnDttfdx3+xz/+Qbdu3ertczgcALzxxhvMmDGDxx57jNGjR5OamsojjzzCqlWr6pVvyTXuSIqKikhLS2tw0e7U1FS+/PJLli1bxr///W/mzJnDvffey+rVqxtdWKwt2uByuZqss9/v56KLLuKhhx46bF9BQUGTxwohhOhczj77bMaPH8+sWbNalDrlYA19n27pd8xAIMD48eMZP348r776Krm5uezatYvx48e3KI3I0qVL+d///V9ef/11hgwZktzu9/sZMWJEvQl2deoC7W2t7rt4r169DttnsVhYvHgxK1as4N///jdPPvkkv/3tb1m1alWj391XrlzJpEmTuO+++xg/fjzp6em88cYbh+WpP9oxQnv3k+gYw4YNwzAMysvLk2mPDjV27Fji8TjFxcXJH8U2b94MQGFhYbMfS4LoQhwnrrjiCqZNm8Zrr73Gyy+/zM0335z8pXf48OFs2LCBU045pUXnrFtQ6+D8pnl5eXTt2pVt27YxadKkRo995JFH2LhxI5988gnjx49nwYIFDeZ1rXucESNGsGTJkuQCI6ZpsmTJksPytreGaZrMnz+f3r17H7Y4W1s61u0QQgjR+dXl2tQ0jfHjxx+2/7TTTsPhcLBr165GZ1gvX76cMWPG8Otf/zq57VjOmiovL+e1117jkksuqfcXXQezWq2MGzeOcePGcc8995CRkcHHH3/MZZddht1uPyyXelu0YfDgwZimySeffFJvvZI6w4cP5+2336ZXr15YrfK1RgghTnYPPvggZ5xxxmGzSk899VSWL19eb9vy5cvp37//YTNX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\n" }, "metadata": {} } ] }, { "cell_type": "code", "source": [ "#@title Hierarchical K-Simplex with Depth-Wise Attenuation\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import math\n", "from typing import List, Tuple, Dict, Optional\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "# ============================================================================\n", "# HIERARCHICAL SIMPLEX COMPLEX\n", "# ============================================================================\n", "\n", "class SimplicialLevel(nn.Module):\n", " \"\"\"\n", " Operates on k-simplices at a specific level.\n", "\n", " k=0: vertices (points)\n", " k=1: edges (pairs)\n", " k=2: faces (triangles)\n", " k=3: tetrahedra\n", " ...\n", "\n", " Each level:\n", " - Receives features from (k-1)-simplices (boundary)\n", " - Computes k-simplex features via boundary aggregation\n", " - Computes CM volume at this level\n", " - Outputs attenuation based on geometric validity\n", " \"\"\"\n", "\n", " def __init__(self, k: int, num_vertices: int, feature_dim: int):\n", " super().__init__()\n", " self.k = k\n", " self.num_vertices = num_vertices\n", " self.feature_dim = feature_dim\n", "\n", " # Number of k-simplices in complete simplex on num_vertices\n", " # C(num_vertices, k+1)\n", " self.num_simplices = math.comb(num_vertices, k + 1)\n", "\n", " # Boundary has (k+1) faces, each a (k-1)-simplex\n", " self.boundary_size = k + 1\n", "\n", " # Build simplex indices\n", " self._build_simplex_indices()\n", "\n", " if k == 0:\n", " # Vertices: just project input\n", " self.proj = nn.Linear(feature_dim, feature_dim)\n", " else:\n", " # k > 0: aggregate from boundary (k+1 faces)\n", " self.boundary_agg = nn.Linear(self.boundary_size * feature_dim, feature_dim)\n", " self.boundary_gate = nn.Linear(self.boundary_size * feature_dim, self.boundary_size)\n", "\n", " # Per-simplex transform\n", " self.simplex_transform = nn.Sequential(\n", " nn.Linear(feature_dim, feature_dim),\n", " nn.LayerNorm(feature_dim),\n", " nn.GELU(),\n", " nn.Linear(feature_dim, feature_dim),\n", " )\n", "\n", " # Coordinate projection for CM computation (k-simplex needs k+1 vertices in R^k)\n", " self.to_coords = nn.Linear(feature_dim, k + 1) if k > 0 else None\n", "\n", " # Validity gate: attenuate based on volume\n", " self.validity_scale = nn.Parameter(torch.tensor(1.0))\n", " self.validity_bias = nn.Parameter(torch.tensor(0.0))\n", "\n", " def _build_simplex_indices(self):\n", " \"\"\"Build index tensors for k-simplices and their boundaries.\"\"\"\n", " # All k-simplices (combinations of k+1 vertices)\n", " simplices = list(combinations(range(self.num_vertices), self.k + 1))\n", " self.register_buffer('simplex_idx', torch.tensor(simplices)) # [num_simplices, k+1]\n", "\n", " if self.k > 0:\n", " # Boundary: for each k-simplex, its (k+1) boundary (k-1)-simplices\n", " # Boundary of [v0, v1, ..., vk] = [v1,...,vk], [v0,v2,...,vk], ...\n", " boundary_indices = []\n", " lower_simplices = list(combinations(range(self.num_vertices), self.k))\n", " lower_to_idx = {s: i for i, s in enumerate(lower_simplices)}\n", "\n", " for simplex in simplices:\n", " boundaries = []\n", " for i in range(self.k + 1):\n", " # Remove vertex i to get boundary face\n", " face = tuple(v for j, v in enumerate(simplex) if j != i)\n", " boundaries.append(lower_to_idx[face])\n", " boundary_indices.append(boundaries)\n", "\n", " self.register_buffer('boundary_idx', torch.tensor(boundary_indices)) # [num_simplices, k+1]\n", "\n", " def compute_cm_volume_sq(self, features: torch.Tensor) -> torch.Tensor:\n", " \"\"\"\n", " Compute CM volume² for each k-simplex.\n", " features: [B, num_simplices, feature_dim]\n", " returns: [B, num_simplices]\n", " \"\"\"\n", " if self.k == 0:\n", " # 0-simplices (points) have no volume, return 1 (always valid)\n", " return torch.ones(features.shape[0], features.shape[1], device=features.device)\n", "\n", " B, S, F = features.shape\n", "\n", " # Project to coordinates: [B, S, k+1] (k+1 coords for k-simplex vertices)\n", " coords = self.to_coords(features) # [B, S, k+1]\n", "\n", " # For k-simplex: we need distances between k+1 \"virtual\" vertices\n", " # Treat each coordinate as a vertex position in R^1, stack to get R^(k+1)\n", " # Actually: interpret the k+1 outputs as positions of k+1 vertices in R^1\n", " # Then compute pairwise distances\n", "\n", " # Reshape: each simplex has k+1 vertices, each in R^1\n", " # For proper CM: vertices in R^k minimum\n", " # Simpler: use the k+1 features as vertex positions in R^1\n", "\n", " # Pairwise squared distances via broadcasting\n", " # coords: [B, S, k+1] where k+1 = num vertices\n", " v = coords.unsqueeze(-1) # [B, S, k+1, 1]\n", " d_sq = (v - v.transpose(-1, -2)).pow(2).squeeze(-1).squeeze(-1) # Doesn't work for R^1\n", "\n", " # Better: embed in R^k properly\n", " # Let's do it correctly with feature_dim as embedding space\n", "\n", " # Actually, let's gather vertex features and compute real distances\n", " return self._compute_cm_from_vertices(features)\n", "\n", " def _compute_cm_from_vertices(self, features: torch.Tensor) -> torch.Tensor:\n", " \"\"\"\n", " Compute CM volume from simplex vertex features.\n", " Uses feature vectors as vertex positions.\n", " \"\"\"\n", " B, S, F = features.shape\n", " k = self.k\n", "\n", " if k == 0:\n", " return torch.ones(B, S, device=features.device)\n", "\n", " # For k=1 (edges): volume = distance\n", " # For k=2 (triangles): volume = area\n", " # etc.\n", "\n", " # Use learned projection to k-dimensional space\n", " coords = self.to_coords(features) # [B, S, k+1]\n", "\n", " # Interpret as k+1 points in R^1, compute pairwise distances\n", " # For proper k-volume, we need k+1 points in R^k\n", " # Simplification: use scalar coords, volume = product of differences (degenerate)\n", "\n", " # Better approach: compute Gram determinant\n", " # For vectors v1, ..., vk, volume² = det(G) where G_ij = \n", "\n", " # Coords: [B, S, k+1] - treat as k+1 scalar \"positions\"\n", " # Shift to origin: subtract first vertex\n", " if k >= 1:\n", " shifted = coords[..., 1:] - coords[..., 0:1] # [B, S, k]\n", "\n", " if k == 1:\n", " # Edge: length² = (v1 - v0)²\n", " vol_sq = shifted.pow(2).squeeze(-1) # [B, S]\n", " else:\n", " # Gram matrix: [B, S, k, k]\n", " gram = torch.einsum('bsi,bsj->bsij', shifted, shifted)\n", " vol_sq = torch.linalg.det(gram) # [B, S]\n", "\n", " return vol_sq\n", "\n", " return torch.ones(B, S, device=features.device)\n", "\n", " def forward(self, vertex_features: torch.Tensor, lower_features: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n", " \"\"\"\n", " vertex_features: [B, num_vertices, feature_dim]\n", " lower_features: [B, num_lower_simplices, feature_dim] (k-1 level output)\n", "\n", " returns:\n", " simplex_features: [B, num_simplices, feature_dim]\n", " volume_sq: [B, num_simplices]\n", " attenuation: [B, num_simplices] (0-1 validity gate)\n", " \"\"\"\n", " B = vertex_features.shape[0]\n", "\n", " if self.k == 0:\n", " # Vertices: project input\n", " features = self.proj(vertex_features) # [B, V, F]\n", " vol_sq = torch.ones(B, self.num_simplices, device=features.device)\n", " attn = torch.ones(B, self.num_simplices, device=features.device)\n", " return features, vol_sq, attn\n", "\n", " # k > 0: aggregate from boundary\n", " # Gather boundary (k-1)-simplex features\n", " # boundary_idx: [num_simplices, k+1]\n", " idx = self.boundary_idx.unsqueeze(0).unsqueeze(-1).expand(B, -1, -1, lower_features.shape[-1])\n", " boundary_feats = torch.gather(\n", " lower_features.unsqueeze(1).expand(-1, self.num_simplices, -1, -1),\n", " dim=2,\n", " index=idx\n", " ) # [B, num_simplices, k+1, feature_dim]\n", "\n", " # Flatten boundary\n", " boundary_flat = boundary_feats.flatten(-2) # [B, num_simplices, (k+1)*feature_dim]\n", "\n", " # Gated aggregation\n", " gates = F.softmax(self.boundary_gate(boundary_flat), dim=-1) # [B, S, k+1]\n", " gated_boundary = (boundary_feats * gates.unsqueeze(-1)).sum(dim=2) # [B, S, F]\n", "\n", " # Also use linear combination\n", " agg_features = self.boundary_agg(boundary_flat) # [B, S, F]\n", "\n", " # Combine with residual\n", " features = gated_boundary + agg_features\n", " features = self.simplex_transform(features)\n", "\n", " # Compute volume\n", " vol_sq = self._compute_cm_from_vertices(features) # [B, S]\n", "\n", " # Validity attenuation: sigmoid of scaled log-volume\n", " # Large positive volume -> attn near 1\n", " # Small/negative volume -> attn near 0\n", " log_vol = torch.log(vol_sq.abs() + 1e-8)\n", " attn = torch.sigmoid(self.validity_scale * log_vol + self.validity_bias)\n", "\n", " return features, vol_sq, attn\n", "\n", "\n", "class HierarchicalSimplexComplex(nn.Module):\n", " \"\"\"\n", " Full hierarchical simplex complex from k=0 to k=max_k.\n", "\n", " Each level builds on the previous:\n", " - k=0: vertices\n", " - k=1: edges (from vertex pairs)\n", " - k=2: triangles (from edge triples)\n", " - etc.\n", "\n", " Output combines all levels with learned weighting,\n", " attenuated by geometric validity at each level.\n", " \"\"\"\n", "\n", " def __init__(self, num_vertices: int, feature_dim: int, max_k: int):\n", " super().__init__()\n", " self.num_vertices = num_vertices\n", " self.feature_dim = feature_dim\n", " self.max_k = max_k\n", "\n", " # Levels k=0 to k=max_k\n", " self.levels = nn.ModuleList([\n", " SimplicialLevel(k, num_vertices, feature_dim)\n", " for k in range(max_k + 1)\n", " ])\n", "\n", " # Level importance weights (learned)\n", " self.level_weights = nn.Parameter(torch.ones(max_k + 1) / (max_k + 1))\n", "\n", " # Output aggregation\n", " total_simplices = sum(math.comb(num_vertices, k + 1) for k in range(max_k + 1))\n", " self.output_proj = nn.Linear(total_simplices, feature_dim)\n", "\n", " # Store simplex counts for output gathering\n", " self.simplex_counts = [math.comb(num_vertices, k + 1) for k in range(max_k + 1)]\n", "\n", " def forward(self, vertex_features: torch.Tensor) -> Tuple[torch.Tensor, Dict]:\n", " \"\"\"\n", " vertex_features: [B, num_vertices, feature_dim]\n", "\n", " returns:\n", " output: [B, feature_dim]\n", " diagnostics: dict with per-level info\n", " \"\"\"\n", " B = vertex_features.shape[0]\n", "\n", " all_features = []\n", " all_volumes = []\n", " all_attenuations = []\n", "\n", " lower_features = None\n", "\n", " for k, level in enumerate(self.levels):\n", " features, vol_sq, attn = level(vertex_features, lower_features)\n", "\n", " all_features.append(features)\n", " all_volumes.append(vol_sq)\n", " all_attenuations.append(attn)\n", "\n", " lower_features = features\n", "\n", " # Weighted combination across levels\n", " level_w = F.softmax(self.level_weights, dim=0)\n", "\n", " # Pool each level: [B, num_simplices_k] -> [B, 1] weighted by attenuation\n", " level_outputs = []\n", " for k, (features, attn) in enumerate(zip(all_features, all_attenuations)):\n", " # Attenuation-weighted mean\n", " weighted = (features * attn.unsqueeze(-1)).mean(dim=1) # [B, F]\n", " level_outputs.append(level_w[k] * weighted)\n", "\n", " # Sum across levels\n", " output = sum(level_outputs) # [B, F]\n", "\n", " diagnostics = {\n", " 'features': all_features,\n", " 'volumes': all_volumes,\n", " 'attenuations': all_attenuations,\n", " 'level_weights': level_w.detach(),\n", " }\n", "\n", " return output, diagnostics\n", "\n", "\n", "class DeepSimplicialNetwork(nn.Module):\n", " \"\"\"\n", " Stack multiple hierarchical simplex complexes.\n", " Each complex operates on the output of the previous.\n", " \"\"\"\n", "\n", " def __init__(self, num_vertices: int, feature_dim: int, max_k: int, num_complexes: int = 3):\n", " super().__init__()\n", " self.num_vertices = num_vertices\n", " self.feature_dim = feature_dim\n", " self.max_k = max_k\n", " self.num_complexes = num_complexes\n", "\n", " # Input projection to vertices\n", " self.input_proj = nn.Linear(feature_dim, num_vertices * feature_dim)\n", "\n", " # Stacked complexes\n", " self.complexes = nn.ModuleList([\n", " HierarchicalSimplexComplex(num_vertices, feature_dim, max_k)\n", " for _ in range(num_complexes)\n", " ])\n", "\n", " # Inter-complex transforms\n", " self.inter_transforms = nn.ModuleList([\n", " nn.Sequential(\n", " nn.Linear(feature_dim, feature_dim),\n", " nn.LayerNorm(feature_dim),\n", " nn.GELU(),\n", " )\n", " for _ in range(num_complexes - 1)\n", " ])\n", "\n", " # Vertex reconstruction for next complex\n", " self.vertex_reconstructs = nn.ModuleList([\n", " nn.Linear(feature_dim, num_vertices * feature_dim)\n", " for _ in range(num_complexes - 1)\n", " ])\n", "\n", " def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, List[Dict]]:\n", " \"\"\"\n", " x: [B, feature_dim]\n", " returns: [B, feature_dim], list of diagnostics\n", " \"\"\"\n", " B = x.shape[0]\n", "\n", " # Project to initial vertices\n", " vertices = self.input_proj(x).view(B, self.num_vertices, self.feature_dim)\n", "\n", " all_diagnostics = []\n", "\n", " for i, complex_layer in enumerate(self.complexes):\n", " output, diag = complex_layer(vertices)\n", " all_diagnostics.append(diag)\n", "\n", " if i < self.num_complexes - 1:\n", " # Transform and reconstruct vertices for next complex\n", " transformed = self.inter_transforms[i](output)\n", " vertices = self.vertex_reconstructs[i](transformed).view(B, self.num_vertices, self.feature_dim)\n", " # Residual\n", " vertices = vertices + self.input_proj(x).view(B, self.num_vertices, self.feature_dim)\n", "\n", " return output, all_diagnostics\n", "\n", "\n", "# ============================================================================\n", "# FASHIONMNIST MODEL\n", "# ============================================================================\n", "\n", "class FashionHierarchicalSimplex(nn.Module):\n", " def __init__(self, num_vertices: int = 6, feature_dim: int = 32,\n", " max_k: int = 4, num_complexes: int = 2, num_classes: int = 10):\n", " super().__init__()\n", "\n", " self.stem = nn.Sequential(\n", " nn.Conv2d(1, 32, 3, padding=1),\n", " nn.BatchNorm2d(32),\n", " nn.GELU(),\n", " nn.Conv2d(32, 64, 3, stride=2, padding=1),\n", " nn.BatchNorm2d(64),\n", " nn.GELU(),\n", " nn.Conv2d(64, 128, 3, stride=2, padding=1),\n", " nn.BatchNorm2d(128),\n", " nn.GELU(),\n", " )\n", "\n", " self.pool = nn.AdaptiveAvgPool2d(1)\n", "\n", " # Project to simplex feature dim\n", " self.pre_simplex = nn.Linear(128, feature_dim)\n", "\n", " # Hierarchical simplex\n", " self.simplex = DeepSimplicialNetwork(\n", " num_vertices=num_vertices,\n", " feature_dim=feature_dim,\n", " max_k=max_k,\n", " num_complexes=num_complexes\n", " )\n", "\n", " # Classification head\n", " self.head = nn.Linear(feature_dim, num_classes)\n", "\n", " self.num_vertices = num_vertices\n", " self.max_k = max_k\n", "\n", " def forward(self, x: torch.Tensor, return_diagnostics: bool = False):\n", " B = x.shape[0]\n", "\n", " h = self.stem(x)\n", " h = self.pool(h).flatten(1)\n", " h = self.pre_simplex(h)\n", "\n", " simplex_out, diagnostics = self.simplex(h)\n", "\n", " logits = self.head(simplex_out)\n", "\n", " if return_diagnostics:\n", " return logits, diagnostics\n", " return logits\n", "\n", "\n", "# ============================================================================\n", "# TRAINING\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_data = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_data = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", "\n", " train_loader = DataLoader(train_data, batch_size=128, shuffle=True, num_workers=2)\n", " test_loader = DataLoader(test_data, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " model = FashionHierarchicalSimplex(\n", " num_vertices=6,\n", " feature_dim=32,\n", " max_k=4, # Will use k=0,1,2,3,4\n", " num_complexes=2,\n", " num_classes=10\n", " ).to(device)\n", "\n", " total_params = sum(p.numel() for p in model.parameters())\n", " print(f\"Model params: {total_params:,}\")\n", " print(f\"Vertices: {model.num_vertices}, Max k: {model.max_k}\")\n", " print(f\"Simplices per level: {[math.comb(model.num_vertices, k+1) for k in range(model.max_k+1)]}\")\n", "\n", " optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30)\n", "\n", " best_acc = 0\n", "\n", " print(\"\\nTraining...\")\n", " print(\"=\"*80)\n", "\n", " for epoch in range(30):\n", " model.train()\n", " train_loss, correct, total = 0, 0, 0\n", "\n", " for images, labels in train_loader:\n", " images, labels = images.to(device), labels.to(device)\n", "\n", " optimizer.zero_grad()\n", " logits = model(images)\n", " loss = F.cross_entropy(logits, labels)\n", " loss.backward()\n", " optimizer.step()\n", "\n", " train_loss += loss.item() * images.size(0)\n", " correct += (logits.argmax(1) == labels).sum().item()\n", " total += images.size(0)\n", "\n", " train_acc = correct / total\n", " train_loss = train_loss / total\n", "\n", " model.eval()\n", " correct, total = 0, 0\n", " level_stats = None\n", "\n", " with torch.no_grad():\n", " for images, labels in test_loader:\n", " images, labels = images.to(device), labels.to(device)\n", " logits, diagnostics = model(images, return_diagnostics=True)\n", " correct += (logits.argmax(1) == labels).sum().item()\n", " total += images.size(0)\n", "\n", " # Collect stats from first complex\n", " if level_stats is None:\n", " level_stats = {\n", " 'weights': diagnostics[0]['level_weights'].cpu(),\n", " 'volumes': [v.mean().item() for v in diagnostics[0]['volumes']],\n", " 'attenuations': [a.mean().item() for a in diagnostics[0]['attenuations']],\n", " }\n", "\n", " test_acc = correct / total\n", " scheduler.step()\n", "\n", " if test_acc > best_acc:\n", " best_acc = test_acc\n", "\n", " if epoch % 5 == 0 or epoch == 29:\n", " print(f\"Epoch {epoch+1:2d} | Loss: {train_loss:.4f} | Train: {train_acc:.2%} | Test: {test_acc:.2%} | Best: {best_acc:.2%}\")\n", "\n", " weights_str = \", \".join([f\"k{k}={w:.3f}\" for k, w in enumerate(level_stats['weights'])])\n", " print(f\" | Level weights: [{weights_str}]\")\n", "\n", " vol_str = \", \".join([f\"k{k}={v:.4f}\" for k, v in enumerate(level_stats['volumes'])])\n", " print(f\" | Volumes: [{vol_str}]\")\n", "\n", " attn_str = \", \".join([f\"k{k}={a:.3f}\" for k, a in enumerate(level_stats['attenuations'])])\n", " print(f\" | Attenuation: [{attn_str}]\")\n", "\n", " print(\"=\"*80)\n", " print(f\"Best Accuracy: {best_acc:.2%}\")\n", "\n", " return model\n", "\n", "\n", "# ============================================================================\n", "# ANALYSIS\n", "# ============================================================================\n", "\n", "def analyze(model):\n", " print(\"\\n\" + \"=\"*80)\n", " print(\"HIERARCHICAL SIMPLEX ANALYSIS\")\n", " print(\"=\"*80)\n", "\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", " test_data = datasets.FashionMNIST('./data', train=False, transform=transform)\n", " test_loader = DataLoader(test_data, batch_size=256, shuffle=False)\n", "\n", " model.eval()\n", "\n", " all_diagnostics = []\n", " all_labels = []\n", "\n", " with torch.no_grad():\n", " for images, labels in test_loader:\n", " images = images.to(device)\n", " _, diag = model(images, return_diagnostics=True)\n", " all_diagnostics.append(diag)\n", " all_labels.append(labels)\n", "\n", " labels = torch.cat(all_labels)\n", "\n", " # Aggregate per-level stats\n", " max_k = model.max_k\n", "\n", " fig, axes = plt.subplots(2, 3, figsize=(15, 10))\n", "\n", " # 1. Level weights\n", " ax = axes[0, 0]\n", " weights = all_diagnostics[0][0]['level_weights'].numpy()\n", " ax.bar(range(max_k + 1), weights, color='steelblue')\n", " ax.set_xlabel('k-level')\n", " ax.set_ylabel('Weight')\n", " ax.set_title('Learned Level Weights')\n", " ax.set_xticks(range(max_k + 1))\n", "\n", " # 2. Volume by level\n", " ax = axes[0, 1]\n", " vol_by_level = []\n", " for k in range(max_k + 1):\n", " vols = torch.cat([d[0]['volumes'][k].flatten() for d in all_diagnostics])\n", " vol_by_level.append(vols.cpu())\n", "\n", " ax.boxplot([v.numpy() for v in vol_by_level], labels=[f'k={k}' for k in range(max_k + 1)])\n", " ax.set_xlabel('k-level')\n", " ax.set_ylabel('Volume²')\n", " ax.set_title('Volume² Distribution by Level')\n", " ax.set_yscale('symlog')\n", "\n", " # 3. Attenuation by level\n", " ax = axes[0, 2]\n", " attn_by_level = []\n", " for k in range(max_k + 1):\n", " attns = torch.cat([d[0]['attenuations'][k].flatten() for d in all_diagnostics])\n", " attn_by_level.append(attns.mean().item())\n", "\n", " ax.bar(range(max_k + 1), attn_by_level, color='coral')\n", " ax.set_xlabel('k-level')\n", " ax.set_ylabel('Mean Attenuation')\n", " ax.set_title('Attenuation by Level (1=fully valid)')\n", " ax.set_xticks(range(max_k + 1))\n", " ax.set_ylim(0, 1)\n", "\n", " # 4. Volume by class (for highest k)\n", " ax = axes[1, 0]\n", " class_names = ['T-shirt', 'Trouser', 'Pullover', 'Dress', 'Coat',\n", " 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Boot']\n", "\n", " final_vols = vol_by_level[max_k]\n", " vol_by_class = []\n", " for c in range(10):\n", " mask = labels == c\n", " vol_by_class.append(final_vols[mask].mean().item())\n", "\n", " ax.bar(range(10), vol_by_class, color='purple', alpha=0.7)\n", " ax.set_xticks(range(10))\n", " ax.set_xticklabels([n[:6] for n in class_names], rotation=45, ha='right')\n", " ax.set_ylabel(f'Mean Volume² (k={max_k})')\n", " ax.set_title(f'Top-Level Volume by Class')\n", "\n", " # 5. Attenuation correlation with accuracy\n", " ax = axes[1, 1]\n", " # For each sample, get total attenuation\n", " total_attns = []\n", " for d in all_diagnostics:\n", " batch_attn = sum(d[0]['attenuations'][k].mean(dim=1) for k in range(max_k + 1))\n", " total_attns.append(batch_attn.cpu())\n", " total_attns = torch.cat(total_attns)\n", "\n", " ax.hist(total_attns.numpy(), bins=50, color='green', alpha=0.7)\n", " ax.set_xlabel('Total Attenuation')\n", " ax.set_ylabel('Count')\n", " ax.set_title('Distribution of Total Attenuation')\n", "\n", " # 6. Level importance across complexes\n", " ax = axes[1, 2]\n", " num_complexes = len(all_diagnostics[0])\n", " for c_idx in range(num_complexes):\n", " w = all_diagnostics[0][c_idx]['level_weights'].numpy()\n", " ax.plot(range(max_k + 1), w, 'o-', label=f'Complex {c_idx+1}')\n", " ax.set_xlabel('k-level')\n", " ax.set_ylabel('Weight')\n", " ax.set_title('Level Weights Across Complexes')\n", " ax.legend()\n", " ax.set_xticks(range(max_k + 1))\n", "\n", " plt.tight_layout()\n", " plt.savefig('hierarchical_simplex_analysis.png', dpi=150, bbox_inches='tight')\n", " plt.show()\n", "\n", " # Print summary\n", " print(f\"\\nLevel Summary:\")\n", " for k in range(max_k + 1):\n", " num_simp = math.comb(model.num_vertices, k + 1)\n", " vol_mean = vol_by_level[k].mean().item()\n", " vol_std = vol_by_level[k].std().item()\n", " attn = attn_by_level[k]\n", " print(f\" k={k}: {num_simp:3d} simplices | Vol²: μ={vol_mean:8.4f}, σ={vol_std:8.4f} | Attn: {attn:.3f}\")\n", "\n", "\n", "# ============================================================================\n", "# RUN\n", "# ============================================================================\n", "\n", "if __name__ == \"__main__\":\n", " model = train()\n", " analyze(model)" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 668 }, "cellView": "form", "id": "pCctl4MWB9XS", "outputId": "d476e980-59ec-4aab-ece0-8bb77ea8214a" }, "execution_count": 5, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "Model params: 172,640\n", "Vertices: 6, Max k: 4\n", "Simplices per level: [6, 15, 20, 15, 6]\n", "\n", "Training...\n", "================================================================================\n", "Epoch 1 | Loss: 1.0471 | Train: 58.45% | Test: 75.72% | Best: 75.72%\n", " | Level weights: [k0=0.193, k1=0.208, k2=0.199, k3=0.199, k4=0.199]\n", " | Volumes: [k0=1.0000, k1=11.4210, k2=0.0000, k3=-0.0000, k4=-0.0000]\n", " | Attenuation: [k0=1.000, k1=0.773, k2=0.000, k3=0.000, k4=0.000]\n", "Epoch 6 | Loss: 0.3745 | Train: 86.54% | Test: 85.42% | Best: 85.42%\n", " | Level weights: [k0=0.194, k1=0.204, k2=0.200, k3=0.200, k4=0.200]\n", " | Volumes: [k0=1.0000, k1=7.1449, k2=-0.0000, k3=-0.0000, k4=0.0000]\n", " | Attenuation: [k0=1.000, k1=0.665, k2=0.000, k3=0.000, k4=0.000]\n", "Epoch 11 | Loss: 0.2896 | Train: 89.61% | Test: 86.59% | Best: 86.59%\n", " | Level weights: [k0=0.202, k1=0.203, k2=0.198, k3=0.198, k4=0.198]\n", " | Volumes: [k0=1.0000, k1=5.1970, k2=0.0000, k3=-0.0000, k4=-0.0000]\n", " | Attenuation: [k0=1.000, k1=0.688, k2=0.000, k3=0.000, k4=0.000]\n" ] }, { "output_type": "error", "ename": "KeyboardInterrupt", "evalue": "", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipython-input-382490007.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 666\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 667\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"__main__\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 668\u001b[0;31m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 669\u001b[0m \u001b[0manalyze\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-382490007.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 477\u001b[0m \u001b[0mimages\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabels\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimages\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabels\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 478\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 479\u001b[0;31m \u001b[0moptimizer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mzero_grad\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 480\u001b[0m \u001b[0mlogits\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimages\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 481\u001b[0m \u001b[0mloss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcross_entropy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabels\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/_compile.py\u001b[0m in \u001b[0;36minner\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 51\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__dynamo_disable\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdisable_fn\u001b[0m \u001b[0;31m# type: ignore[attr-defined]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 53\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdisable_fn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 54\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0minner\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/_dynamo/eval_frame.py\u001b[0m in \u001b[0;36m_fn\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 1042\u001b[0m \u001b[0m_maybe_set_eval_frame\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_callback_from_stance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcallback\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1043\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1044\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1045\u001b[0m \u001b[0;32mfinally\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1046\u001b[0m \u001b[0mset_eval_frame\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/optim/optimizer.py\u001b[0m in \u001b[0;36mzero_grad\u001b[0;34m(self, set_to_none)\u001b[0m\n\u001b[1;32m 1033\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgrad\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1034\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mset_to_none\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1035\u001b[0;31m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgrad\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1036\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1037\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgrad\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgrad_fn\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ] }, { "cell_type": "code", "source": [ "#@title True CM KSimplex with Geometric Alignment\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from typing import List, Dict, Tuple\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER CORE\n", "# ============================================================================\n", "\n", "class CayleyMengerValidator(nn.Module):\n", " \"\"\"\n", " Computes true Cayley-Menger determinant for k-simplex validation.\n", "\n", " For k-simplex with k+1 vertices:\n", " - CM matrix is (k+2) × (k+2)\n", " - det(CM) relates to squared volume\n", " - Valid iff (-1)^(k+1) * det > 0\n", " \"\"\"\n", "\n", " def __init__(self, k: int):\n", " super().__init__()\n", " self.k = k\n", " self.num_vertices = k + 1\n", " self.cm_size = k + 2\n", "\n", " # Pairs for distance computation\n", " pairs = list(combinations(range(self.num_vertices), 2))\n", " self.num_pairs = len(pairs)\n", " self.register_buffer('pair_i', torch.tensor([p[0] for p in pairs]))\n", " self.register_buffer('pair_j', torch.tensor([p[1] for p in pairs]))\n", "\n", " # Volume prefactor: (-1)^(k+1) / (2^k * (k!)^2)\n", " sign = (-1.0) ** (self.k + 1)\n", " factorial_k = math.factorial(self.k)\n", " self.prefactor = sign / ((2.0 ** self.k) * (factorial_k ** 2))\n", "\n", " def pairwise_distances_sq(self, vertices: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:\n", " \"\"\"\n", " Compute TRUE Euclidean squared distances from vertex positions.\n", "\n", " vertices: [..., num_vertices, embed_dim]\n", " returns: d_sq_pairs [..., num_pairs], d_sq_matrix [..., V, V]\n", " \"\"\"\n", " # Gram matrix: G_ij = \n", " gram = torch.einsum('...ve,...we->...vw', vertices, vertices)\n", "\n", " # Squared norms on diagonal\n", " norms_sq = torch.diagonal(gram, dim1=-2, dim2=-1)\n", "\n", " # d²(i,j) = ||v_i||² + ||v_j||² - 2\n", " d_sq_matrix = norms_sq.unsqueeze(-1) + norms_sq.unsqueeze(-2) - 2 * gram\n", "\n", " # Numerical stability\n", " d_sq_matrix = F.relu(d_sq_matrix)\n", "\n", " # Extract pairs\n", " d_sq_pairs = d_sq_matrix[..., self.pair_i, self.pair_j]\n", "\n", " return d_sq_pairs, d_sq_matrix\n", "\n", " def cayley_menger_det(self, d_sq_matrix: torch.Tensor) -> torch.Tensor:\n", " \"\"\"\n", " Compute CM determinant from squared distance matrix.\n", "\n", " d_sq_matrix: [..., V, V]\n", " returns: [...] determinant\n", " \"\"\"\n", " shape = d_sq_matrix.shape[:-2]\n", " V = d_sq_matrix.shape[-1]\n", "\n", " # Build CM matrix: (V+1) × (V+1)\n", " cm = torch.zeros(*shape, V + 1, V + 1, device=d_sq_matrix.device, dtype=d_sq_matrix.dtype)\n", "\n", " # Border: first row/col = 1 (except [0,0] = 0)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", "\n", " # Interior: squared distances\n", " cm[..., 1:, 1:] = d_sq_matrix\n", "\n", " # Determinant\n", " det = torch.linalg.det(cm)\n", "\n", " return det\n", "\n", " def volume_squared(self, d_sq_matrix: torch.Tensor) -> torch.Tensor:\n", " \"\"\"Compute squared volume from CM determinant.\"\"\"\n", " det = self.cayley_menger_det(d_sq_matrix)\n", " return self.prefactor * det\n", "\n", " def is_valid(self, vol_sq: torch.Tensor, eps: float = 1e-8) -> torch.Tensor:\n", " \"\"\"Check if simplex is geometrically valid (positive volume²).\"\"\"\n", " return vol_sq > eps\n", "\n", " def forward(self, vertices: torch.Tensor) -> Dict[str, torch.Tensor]:\n", " \"\"\"\n", " Full CM computation.\n", "\n", " vertices: [..., num_vertices, embed_dim]\n", " \"\"\"\n", " d_sq_pairs, d_sq_matrix = self.pairwise_distances_sq(vertices)\n", " det = self.cayley_menger_det(d_sq_matrix)\n", " vol_sq = self.prefactor * det\n", " valid = self.is_valid(vol_sq)\n", "\n", " return {\n", " 'd_sq_pairs': d_sq_pairs,\n", " 'd_sq_matrix': d_sq_matrix,\n", " 'cm_det': det,\n", " 'vol_sq': vol_sq,\n", " 'valid': valid,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC SIMPLEX LEVEL\n", "# ============================================================================\n", "\n", "class GeometricSimplexLevel(nn.Module):\n", " \"\"\"\n", " Level k of the simplicial complex.\n", "\n", " Takes features from level k-1, constructs k-simplices,\n", " computes TRUE CM geometry, and outputs with validity attenuation.\n", "\n", " Key: vertices are ACTUAL positions in R^embed_dim, not learned proxies.\n", " \"\"\"\n", "\n", " def __init__(self, k: int, n_in: int, n_out: int, feature_dim: int, embed_dim: int = None):\n", " super().__init__()\n", " self.k = k\n", " self.n_in = n_in\n", " self.n_out = n_out\n", " self.feature_dim = feature_dim\n", " self.embed_dim = embed_dim or max(k + 1, 4) # At least k+1 dims for k-simplex\n", " self.num_vertices = k + 1\n", "\n", " # Cayley-Menger validator\n", " self.cm = CayleyMengerValidator(k)\n", "\n", " # Vertex selection: which inputs contribute to each output simplex\n", " # Each output selects k+1 vertices from n_in inputs\n", " self.vertex_logits = nn.Parameter(torch.randn(n_out, self.num_vertices, n_in) * 0.02)\n", "\n", " # Feature to embedding: project features to geometric coordinates\n", " self.to_embed = nn.Linear(feature_dim, self.embed_dim)\n", "\n", " # Feature transform (operates on aggregated features, not geometry)\n", " self.feature_mlp = nn.Sequential(\n", " nn.Linear(feature_dim, feature_dim * 2),\n", " nn.LayerNorm(feature_dim * 2),\n", " nn.GELU(),\n", " nn.Linear(feature_dim * 2, feature_dim),\n", " )\n", "\n", " # Geometry-conditioned gating\n", " # Input: distances + volume -> gate\n", " gate_input_dim = self.cm.num_pairs + 1 # pairs + volume\n", " self.geom_gate = nn.Sequential(\n", " nn.Linear(gate_input_dim, feature_dim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Validity-based attenuation (learned scaling)\n", " self.valid_scale = nn.Parameter(torch.tensor(1.0))\n", " self.valid_bias = nn.Parameter(torch.tensor(0.0))\n", "\n", " # Output projection if sizes differ\n", " if n_in != n_out:\n", " self.out_proj = nn.Linear(n_in * feature_dim, n_out * feature_dim)\n", " else:\n", " self.out_proj = None\n", "\n", " def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Dict]:\n", " \"\"\"\n", " x: [B, n_in, feature_dim]\n", " returns: [B, n_out, feature_dim], geometry_info\n", " \"\"\"\n", " B, N, F = x.shape\n", "\n", " # === STEP 1: Select vertices for each output simplex ===\n", " # vertex_logits: [n_out, num_vertices, n_in]\n", " # Softmax over inputs for each vertex position\n", " vertex_weights = F.softmax(self.vertex_logits, dim=-1) # [n_out, V, n_in]\n", "\n", " # Gather weighted input features for each simplex's vertices\n", " # x: [B, n_in, F] -> selected: [B, n_out, V, F]\n", " selected = torch.einsum('ovn,bnf->bovf', vertex_weights, x)\n", "\n", " # === STEP 2: Compute geometric embeddings ===\n", " # Project features to embedding space for CM computation\n", " embeddings = self.to_embed(selected) # [B, n_out, V, embed_dim]\n", "\n", " # === STEP 3: TRUE Cayley-Menger computation ===\n", " cm_results = self.cm(embeddings)\n", " d_sq = cm_results['d_sq_pairs'] # [B, n_out, num_pairs]\n", " vol_sq = cm_results['vol_sq'] # [B, n_out]\n", " valid = cm_results['valid'] # [B, n_out]\n", "\n", " # === STEP 4: Geometry-conditioned feature transform ===\n", " # Concatenate geometric info\n", " geom_info = torch.cat([d_sq, vol_sq.unsqueeze(-1)], dim=-1) # [B, n_out, pairs+1]\n", "\n", " # Compute geometry-based gate\n", " geom_gate = self.geom_gate(geom_info) # [B, n_out, F]\n", "\n", " # Aggregate vertex features (mean over vertices in each simplex)\n", " agg_features = selected.mean(dim=2) # [B, n_out, F]\n", "\n", " # Transform with geometric gating\n", " transformed = self.feature_mlp(agg_features) * geom_gate\n", "\n", " # === STEP 5: Validity-based attenuation ===\n", " # Attenuate based on geometric validity (vol_sq > 0)\n", " log_vol = torch.log(vol_sq.abs() + 1e-8)\n", " attenuation = torch.sigmoid(self.valid_scale * log_vol + self.valid_bias)\n", "\n", " # Apply attenuation\n", " out = transformed * attenuation.unsqueeze(-1)\n", "\n", " # === STEP 6: Residual connection ===\n", " if self.out_proj is not None:\n", " residual = self.out_proj(x.flatten(1)).view(B, self.n_out, F)\n", " else:\n", " residual = x\n", "\n", " out = out + 0.1 * residual\n", "\n", " return out, {\n", " 'k': self.k,\n", " 'd_sq': d_sq,\n", " 'vol_sq': vol_sq,\n", " 'valid': valid,\n", " 'attenuation': attenuation,\n", " 'vertex_weights': vertex_weights.detach(),\n", " }\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC ALIGNMENT LOSS\n", "# ============================================================================\n", "\n", "class GeometricAlignmentLoss(nn.Module):\n", " \"\"\"\n", " Ensures geometric coherence across levels.\n", "\n", " 1. Volume consistency: higher k should have proportional volumes\n", " 2. Validity maximization: encourage valid simplices\n", " 3. Distance regularity: prevent degenerate configurations\n", " \"\"\"\n", "\n", " def __init__(self, vol_weight: float = 0.1, valid_weight: float = 0.1, reg_weight: float = 0.01):\n", " super().__init__()\n", " self.vol_weight = vol_weight\n", " self.valid_weight = valid_weight\n", " self.reg_weight = reg_weight\n", "\n", " def forward(self, level_infos: List[Dict]) -> Dict[str, torch.Tensor]:\n", " losses = {}\n", "\n", " # 1. Validity loss: encourage vol_sq > 0\n", " validity_loss = 0\n", " for info in level_infos:\n", " vol_sq = info['vol_sq']\n", " # Penalize negative volumes (invalid simplices)\n", " validity_loss = validity_loss + F.relu(-vol_sq).mean()\n", " losses['validity'] = self.valid_weight * validity_loss\n", "\n", " # 2. Volume consistency: volumes should scale appropriately with k\n", " if len(level_infos) > 1:\n", " vol_consistency = 0\n", " for i in range(1, len(level_infos)):\n", " vol_prev = level_infos[i-1]['vol_sq'].abs().mean()\n", " vol_curr = level_infos[i]['vol_sq'].abs().mean()\n", " # Higher k should have smaller volume (more constrained)\n", " # But not too small (degenerate)\n", " ratio = vol_curr / (vol_prev + 1e-8)\n", " # Penalize extreme ratios\n", " vol_consistency = vol_consistency + (ratio - 0.5).pow(2)\n", " losses['consistency'] = self.vol_weight * vol_consistency\n", "\n", " # 3. Distance regularity: prevent all distances from collapsing\n", " reg_loss = 0\n", " for info in level_infos:\n", " d_sq = info['d_sq']\n", " # Penalize very small distances (degenerate)\n", " reg_loss = reg_loss + F.relu(0.1 - d_sq.mean()).pow(2)\n", " # Penalize very large variance (unstable)\n", " reg_loss = reg_loss + d_sq.var() * 0.01\n", " losses['regularity'] = self.reg_weight * reg_loss\n", "\n", " losses['total'] = sum(losses.values())\n", "\n", " return losses\n", "\n", "\n", "# ============================================================================\n", "# FULL COMPLEX WITH GEOMETRIC ALIGNMENT\n", "# ============================================================================\n", "\n", "class GeometricSimplexComplex(nn.Module):\n", " \"\"\"\n", " Full hierarchical complex with TRUE CM geometry and alignment.\n", "\n", " Each level k builds k-simplices from (k-1) outputs.\n", " Complexity grows: n_k > n_{k-1}\n", " \"\"\"\n", "\n", " def __init__(self, n_base: int, feature_dim: int, max_k: int,\n", " growth: float = 1.5, embed_dim: int = 8):\n", " super().__init__()\n", " self.n_base = n_base\n", " self.feature_dim = feature_dim\n", " self.max_k = max_k\n", " self.embed_dim = embed_dim\n", "\n", " # Compute growing sizes\n", " sizes = [n_base]\n", " for k in range(max_k):\n", " increment = max(1, int(growth * (k + 1)))\n", " sizes.append(sizes[-1] + increment)\n", " self.sizes = sizes\n", "\n", " # Input projection\n", " self.proj_in = nn.Linear(feature_dim, n_base * feature_dim)\n", "\n", " # Geometric levels\n", " self.levels = nn.ModuleList()\n", " for k in range(max_k):\n", " self.levels.append(GeometricSimplexLevel(\n", " k=k,\n", " n_in=sizes[k],\n", " n_out=sizes[k+1],\n", " feature_dim=feature_dim,\n", " embed_dim=embed_dim,\n", " ))\n", "\n", " # Geometric alignment loss\n", " self.align_loss = GeometricAlignmentLoss()\n", "\n", " # Level importance\n", " self.level_weights = nn.Parameter(torch.ones(max_k) / max_k)\n", "\n", " def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Dict]:\n", " \"\"\"x: [B, feature_dim]\"\"\"\n", " B = x.shape[0]\n", "\n", " # Initial vertices\n", " h = self.proj_in(x).view(B, self.n_base, self.feature_dim)\n", "\n", " all_outputs = []\n", " all_infos = []\n", "\n", " for level in self.levels:\n", " h, info = level(h)\n", " all_outputs.append(h)\n", " all_infos.append(info)\n", "\n", " # Weighted combination with validity-aware pooling\n", " w = F.softmax(self.level_weights, dim=0)\n", "\n", " pooled = []\n", " for i, (out, info) in enumerate(zip(all_outputs, all_infos)):\n", " attn = info['attenuation']\n", " # Validity-weighted mean\n", " weighted = (out * attn.unsqueeze(-1)).sum(dim=1) / (attn.sum(dim=1, keepdim=True) + 1e-8)\n", " pooled.append(w[i] * weighted)\n", "\n", " result = sum(pooled)\n", "\n", " # Compute alignment loss\n", " align_losses = self.align_loss(all_infos)\n", "\n", " return result, {\n", " 'sizes': self.sizes,\n", " 'level_infos': all_infos,\n", " 'level_weights': w.detach(),\n", " 'align_losses': align_losses,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# MODEL\n", "# ============================================================================\n", "\n", "class FashionGeometricSimplex(nn.Module):\n", " def __init__(self, n_base=4, feature_dim=32, max_k=5, growth=1.5,\n", " embed_dim=8, n_complex=2):\n", " super().__init__()\n", "\n", " self.stem = nn.Sequential(\n", " nn.Conv2d(1, 32, 3, padding=1),\n", " nn.BatchNorm2d(32),\n", " nn.GELU(),\n", " nn.Conv2d(32, 64, 3, stride=2, padding=1),\n", " nn.BatchNorm2d(64),\n", " nn.GELU(),\n", " nn.Conv2d(64, 128, 3, stride=2, padding=1),\n", " nn.BatchNorm2d(128),\n", " nn.GELU(),\n", " )\n", " self.pool = nn.AdaptiveAvgPool2d(1)\n", " self.proj = nn.Linear(128, feature_dim)\n", "\n", " self.complexes = nn.ModuleList()\n", " self.norms = nn.ModuleList()\n", " for i in range(n_complex):\n", " cpx = GeometricSimplexComplex(n_base, feature_dim, max_k, growth, embed_dim)\n", " self.complexes.append(cpx)\n", " self.norms.append(nn.LayerNorm(feature_dim))\n", " print(f\"Complex {i+1} sizes: {cpx.sizes}\")\n", "\n", " self.head = nn.Linear(feature_dim, 10)\n", "\n", " def forward(self, x, return_diag=False):\n", " h = self.stem(x)\n", " h = self.pool(h).flatten(1)\n", " h = self.proj(h)\n", "\n", " all_diag = []\n", " total_align_loss = 0\n", "\n", " for cpx, norm in zip(self.complexes, self.norms):\n", " out, diag = cpx(h)\n", " all_diag.append(diag)\n", " total_align_loss = total_align_loss + diag['align_losses']['total']\n", " h = norm(h + out)\n", "\n", " logits = self.head(h)\n", "\n", " if return_diag:\n", " return logits, all_diag, total_align_loss\n", " return logits, total_align_loss\n", "\n", "\n", "# ============================================================================\n", "# TRAINING\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_data = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_data = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", "\n", " train_loader = DataLoader(train_data, batch_size=128, shuffle=True, num_workers=2)\n", " test_loader = DataLoader(test_data, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = FashionGeometricSimplex(\n", " n_base=4,\n", " feature_dim=32,\n", " max_k=5,\n", " growth=1.5,\n", " embed_dim=8,\n", " n_complex=2,\n", " ).to(device)\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\"Parameters: {params:,}\")\n", "\n", " optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 90)\n", "\n", " for epoch in range(30):\n", " model.train()\n", " loss_sum, align_sum, correct, total = 0, 0, 0, 0\n", "\n", " for imgs, labels in train_loader:\n", " imgs, labels = imgs.to(device), labels.to(device)\n", "\n", " optimizer.zero_grad()\n", " logits, align_loss = model(imgs)\n", "\n", " ce_loss = F.cross_entropy(logits, labels)\n", " total_loss = ce_loss + align_loss\n", "\n", " total_loss.backward()\n", " optimizer.step()\n", "\n", " loss_sum += ce_loss.item() * imgs.size(0)\n", " align_sum += align_loss.item() * imgs.size(0)\n", " correct += (logits.argmax(1) == labels).sum().item()\n", " total += imgs.size(0)\n", "\n", " train_acc = correct / total\n", " train_loss = loss_sum / total\n", " train_align = align_sum / total\n", "\n", " # Eval\n", " model.eval()\n", " correct, total = 0, 0\n", " sample_diag = None\n", "\n", " with torch.no_grad():\n", " for imgs, labels in test_loader:\n", " imgs, labels = imgs.to(device), labels.to(device)\n", " logits, diag, _ = model(imgs, return_diag=True)\n", " correct += (logits.argmax(1) == labels).sum().item()\n", " total += imgs.size(0)\n", " if sample_diag is None:\n", " sample_diag = diag\n", "\n", " test_acc = correct / total\n", " scheduler.step()\n", "\n", " if test_acc > best:\n", " best = test_acc\n", "\n", " if epoch % 5 == 0 or epoch == 29:\n", " print(f\"Ep {epoch+1:2d} | CE {train_loss:.4f} | Align {train_align:.4f} | Train {train_acc:.2%} | Test {test_acc:.2%} | Best {best:.2%}\")\n", "\n", " # Geometry stats\n", " d = sample_diag[0]\n", " for k, info in enumerate(d['level_infos']):\n", " vol = info['vol_sq'].mean().item()\n", " valid_pct = info['valid'].float().mean().item()\n", " attn = info['attenuation'].mean().item()\n", " d_mean = info['d_sq'].mean().item()\n", " print(f\" k={k}: vol²={vol:8.4f} | valid={valid_pct:.1%} | attn={attn:.3f} | d²={d_mean:.4f}\")\n", "\n", " print(\"=\" * 90)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 477 }, "cellView": "form", "id": "llm5QaVACpL8", "outputId": "20adb10e-dd5c-4005-9407-1ec53a89804b" }, "execution_count": 1, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "Complex 1 sizes: [4, 5, 8, 12, 18, 25]\n", "Complex 2 sizes: [4, 5, 8, 12, 18, 25]\n", "Parameters: 1,848,756\n", "\n", "Training...\n", "==========================================================================================\n" ] }, { "output_type": "error", "ename": "AttributeError", "evalue": "'int' object has no attribute 'softmax'", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipython-input-251580671.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 541\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 542\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"__main__\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 543\u001b[0;31m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m/tmp/ipython-input-251580671.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 486\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 487\u001b[0m \u001b[0moptimizer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mzero_grad\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 488\u001b[0;31m \u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malign_loss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimgs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 489\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 490\u001b[0m \u001b[0mce_loss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcross_entropy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabels\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1773\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1775\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1776\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1777\u001b[0m \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1784\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1785\u001b[0m or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1786\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1787\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1788\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-251580671.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, x, return_diag)\u001b[0m\n\u001b[1;32m 430\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 431\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcpx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnorm\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcomplexes\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnorms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 432\u001b[0;31m \u001b[0mout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdiag\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcpx\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mh\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 433\u001b[0m \u001b[0mall_diag\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdiag\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 434\u001b[0m \u001b[0mtotal_align_loss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtotal_align_loss\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdiag\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'align_losses'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'total'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1773\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1775\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1776\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1777\u001b[0m \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1784\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1785\u001b[0m or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1786\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1787\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1788\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-251580671.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m 361\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 362\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mlevel\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlevels\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 363\u001b[0;31m \u001b[0mh\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlevel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mh\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 364\u001b[0m \u001b[0mall_outputs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mh\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 365\u001b[0m \u001b[0mall_infos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1773\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1775\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1776\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1777\u001b[0m \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1784\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1785\u001b[0m or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1786\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1787\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1788\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-251580671.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m 190\u001b[0m \u001b[0;31m# vertex_logits: [n_out, num_vertices, n_in]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 191\u001b[0m \u001b[0;31m# Softmax over inputs for each vertex position\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 192\u001b[0;31m \u001b[0mvertex_weights\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msoftmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvertex_logits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdim\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# [n_out, V, n_in]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 193\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 194\u001b[0m \u001b[0;31m# Gather weighted input features for each simplex's vertices\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mAttributeError\u001b[0m: 'int' object has no attribute 'softmax'" ] } ] }, { "cell_type": "code", "source": [ "#@title True CM KSimplex - Fixed k=0 Edge Case\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from typing import List, Dict, Tuple\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, simplex_order):\n", " super().__init__()\n", " self._order = simplex_order\n", " self._nv = simplex_order + 1\n", "\n", " # k=0 has no pairs (single point)\n", " if self._nv < 2:\n", " self._npairs = 0\n", " self.register_buffer('_pi', torch.tensor([], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([], dtype=torch.long))\n", " else:\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " if self._order > 0:\n", " sign = (-1.0) ** (self._order + 1)\n", " fact = math.factorial(self._order)\n", " self._prefactor = sign / ((2.0 ** self._order) * (fact ** 2))\n", " else:\n", " self._prefactor = 1.0\n", "\n", " def forward(self, verts):\n", " \"\"\"verts: [..., num_vertices, embed_dim]\"\"\"\n", "\n", " # k=0: single point, no geometry\n", " if self._order == 0:\n", " shape = verts.shape[:-2]\n", " vol2 = torch.ones(*shape, device=verts.device, dtype=verts.dtype)\n", " d2_pairs = torch.zeros(*shape, 0, device=verts.device, dtype=verts.dtype)\n", " valid = torch.ones(*shape, device=verts.device, dtype=torch.bool)\n", " return d2_pairs, vol2, valid\n", "\n", " # Gram matrix for distances\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat) # Numerical stability\n", "\n", " # Extract pairs\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " # CM determinant\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", " det = torch.linalg.det(cm)\n", "\n", " vol2 = self._prefactor * det\n", " valid = vol2 > 1e-8\n", "\n", " return d2_pairs, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC LEVEL\n", "# ============================================================================\n", "\n", "class GeoLevel(nn.Module):\n", " def __init__(self, order, nin, nout, fdim, edim):\n", " super().__init__()\n", " self._order = order\n", " self._nin = nin\n", " self._nout = nout\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._nv = order + 1\n", "\n", " # CM validator\n", " self._cm = CMValidator(order)\n", "\n", " # Vertex selection: [nout, nv, nin]\n", " _sel = torch.randn(nout, self._nv, nin) * 0.02\n", " self.register_parameter('_W_select', nn.Parameter(_sel))\n", "\n", " # Embedding projection\n", " self._to_embed = nn.Linear(fdim, edim)\n", "\n", " # Feature MLP\n", " self._mlp = nn.Sequential(\n", " nn.Linear(fdim, fdim * 2),\n", " nn.LayerNorm(fdim * 2),\n", " nn.GELU(),\n", " nn.Linear(fdim * 2, fdim),\n", " )\n", "\n", " # Geometry gate - handle k=0 case (no pairs)\n", " gate_in = max(1, self._cm._npairs + 1) # At least 1 input\n", " self._gate = nn.Sequential(\n", " nn.Linear(gate_in, fdim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Attenuation params\n", " self.register_parameter('_scale', nn.Parameter(torch.tensor(1.0)))\n", " self.register_parameter('_bias', nn.Parameter(torch.tensor(0.0)))\n", "\n", " # Residual projection\n", " if nin != nout:\n", " self._proj = nn.Linear(nin * fdim, nout * fdim)\n", " else:\n", " self._proj = None\n", "\n", " def forward(self, x):\n", " \"\"\"x: [B, nin, fdim]\"\"\"\n", " B = x.shape[0]\n", "\n", " # Soft vertex selection\n", " sel = F.softmax(self._W_select, dim=-1) # [nout, nv, nin]\n", "\n", " # Select vertices: [B, nout, nv, fdim]\n", " picked = torch.einsum('ovn,bnf->bovf', sel, x)\n", "\n", " # Embed for geometry\n", " emb = self._to_embed(picked) # [B, nout, nv, edim]\n", "\n", " # CM computation\n", " d2, vol2, valid = self._cm(emb)\n", "\n", " # Geometric gating\n", " if self._order == 0:\n", " # k=0: just use volume (which is 1)\n", " geo = vol2.unsqueeze(-1) # [B, nout, 1]\n", " else:\n", " geo = torch.cat([d2, vol2.unsqueeze(-1)], dim=-1)\n", " gate = self._gate(geo)\n", "\n", " # Feature transform\n", " agg = picked.mean(dim=2) # [B, nout, fdim]\n", " out = self._mlp(agg) * gate\n", "\n", " # Validity attenuation\n", " logv = torch.log(vol2.abs() + 1e-8)\n", " attn = torch.sigmoid(self._scale * logv + self._bias)\n", " out = out * attn.unsqueeze(-1)\n", "\n", " # Residual\n", " if self._proj is not None:\n", " res = self._proj(x.flatten(1)).view(B, self._nout, self._fdim)\n", " else:\n", " res = x\n", " out = out + 0.1 * res\n", "\n", " return out, {'d2': d2, 'vol2': vol2, 'valid': valid, 'attn': attn, 'order': self._order}\n", "\n", "\n", "# ============================================================================\n", "# COMPLEX\n", "# ============================================================================\n", "\n", "class GeoComplex(nn.Module):\n", " def __init__(self, nbase, fdim, depth, growth, edim):\n", " super().__init__()\n", " self._nbase = nbase\n", " self._fdim = fdim\n", " self._depth = depth\n", "\n", " # Growing sizes\n", " sizes = [nbase]\n", " for i in range(depth):\n", " sizes.append(sizes[-1] + max(1, int(growth * (i + 1))))\n", " self._sizes = sizes\n", "\n", " # Input projection\n", " self._proj_in = nn.Linear(fdim, nbase * fdim)\n", "\n", " # Build levels - start from k=1 (edges) since k=0 is trivial\n", " level_list = []\n", " for i in range(depth):\n", " lv = GeoLevel(\n", " order=i + 1, # Start at k=1, not k=0\n", " nin=sizes[i],\n", " nout=sizes[i+1],\n", " fdim=fdim,\n", " edim=edim\n", " )\n", " level_list.append(lv)\n", " self._levels = nn.ModuleList(level_list)\n", "\n", " # Level weights\n", " self.register_parameter('_lw', nn.Parameter(torch.ones(depth) / depth))\n", "\n", " def forward(self, x):\n", " \"\"\"x: [B, fdim]\"\"\"\n", " B = x.shape[0]\n", " h = self._proj_in(x).view(B, self._nbase, self._fdim)\n", "\n", " outs, infos = [], []\n", " for lv in self._levels:\n", " h, info = lv(h)\n", " outs.append(h)\n", " infos.append(info)\n", "\n", " # Weighted pool\n", " w = F.softmax(self._lw, dim=0)\n", " pooled = []\n", " for i, (o, inf) in enumerate(zip(outs, infos)):\n", " a = inf['attn']\n", " wm = (o * a.unsqueeze(-1)).sum(1) / (a.sum(1, keepdim=True) + 1e-8)\n", " pooled.append(w[i] * wm)\n", "\n", " return sum(pooled), {'sizes': self._sizes, 'infos': infos, 'weights': w.detach()}\n", "\n", "\n", "# ============================================================================\n", "# MODEL\n", "# ============================================================================\n", "\n", "class FashionGeoSimplex(nn.Module):\n", " def __init__(self, nbase=4, fdim=32, depth=5, growth=1.5, edim=8, ncpx=2):\n", " super().__init__()\n", "\n", " self._stem = nn.Sequential(\n", " nn.Conv2d(1, 32, 3, padding=1), nn.BatchNorm2d(32), nn.GELU(),\n", " nn.Conv2d(32, 64, 3, stride=2, padding=1), nn.BatchNorm2d(64), nn.GELU(),\n", " nn.Conv2d(64, 128, 3, stride=2, padding=1), nn.BatchNorm2d(128), nn.GELU(),\n", " )\n", " self._pool = nn.AdaptiveAvgPool2d(1)\n", " self._proj = nn.Linear(128, fdim)\n", "\n", " cpx_list = []\n", " for i in range(ncpx):\n", " c = GeoComplex(nbase, fdim, depth, growth, edim)\n", " cpx_list.append(c)\n", " print(f\"Complex {i+1} sizes: {c._sizes}\")\n", " self._cpx = nn.ModuleList(cpx_list)\n", " self._norms = nn.ModuleList([nn.LayerNorm(fdim) for _ in range(ncpx)])\n", "\n", " self._head = nn.Linear(fdim, 10)\n", "\n", " def forward(self, x, ret_diag=False):\n", " h = self._stem(x)\n", " h = self._pool(h).flatten(1)\n", " h = self._proj(h)\n", "\n", " diags = []\n", " align = 0\n", " for cpx, norm in zip(self._cpx, self._norms):\n", " out, diag = cpx(h)\n", " diags.append(diag)\n", " for inf in diag['infos']:\n", " align = align + F.relu(-inf['vol2']).mean()\n", " h = norm(h + out)\n", "\n", " logits = self._head(h)\n", "\n", " if ret_diag:\n", " return logits, diags, align\n", " return logits, align\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = FashionGeoSimplex(\n", " nbase=4, fdim=32, depth=5, growth=1.5, edim=8, ncpx=2\n", " ).to(device)\n", "\n", " print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 100)\n", "\n", " for ep in range(30):\n", " model.train()\n", " lsum, asum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, align = model(img)\n", " ce = F.cross_entropy(logits, lab)\n", " loss = ce + 0.1 * align\n", " loss.backward()\n", " opt.step()\n", "\n", " lsum += ce.item() * img.size(0)\n", " asum += align.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", " tr_loss = lsum / tot\n", " tr_align = asum / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " samp = None\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, diag, _ = model(img, ret_diag=True)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " if samp is None:\n", " samp = diag\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"Ep {ep+1:2d} | CE {tr_loss:.4f} | Align {tr_align:.4f} | Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%}\")\n", " for inf in samp[0]['infos']:\n", " k = inf['order']\n", " v = inf['vol2'].mean().item()\n", " vld = inf['valid'].float().mean().item()\n", " att = inf['attn'].mean().item()\n", " d = inf['d2'].mean().item() if inf['d2'].numel() > 0 else 0\n", " npairs = inf['d2'].shape[-1] if inf['d2'].numel() > 0 else 0\n", " print(f\" k={k}: pairs={npairs:2d} vol²={v:10.4f} valid={vld:5.1%} attn={att:.3f} d²={d:.4f}\")\n", "\n", " print(\"=\" * 100)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 790 }, "id": "AwfSL9fyGRz2", "outputId": "7c11affd-ea50-4706-819a-e564ec756246" }, "execution_count": 5, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "Complex 1 sizes: [4, 5, 8, 12, 18, 25]\n", "Complex 2 sizes: [4, 5, 8, 12, 18, 25]\n", "Params: 1,851,360\n", "\n", "Training...\n", "====================================================================================================\n", "Ep 1 | CE 0.6076 | Align 0.0000 | Tr 80.14% | Te 84.11% | Best 84.11%\n", " k=1: pairs= 1 vol²= 0.0001 valid=69.9% attn=0.000 d²=0.0001\n", " k=2: pairs= 3 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=3: pairs= 6 vol²= -0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=4: pairs=10 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=5: pairs=15 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", "Ep 6 | CE 0.2593 | Align 0.0000 | Tr 90.64% | Te 88.86% | Best 88.86%\n", " k=1: pairs= 1 vol²= 0.0002 valid=60.2% attn=0.000 d²=0.0002\n", " k=2: pairs= 3 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=3: pairs= 6 vol²= -0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=4: pairs=10 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=5: pairs=15 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", "Ep 11 | CE 0.1813 | Align 0.0000 | Tr 93.45% | Te 89.39% | Best 90.19%\n", " k=1: pairs= 1 vol²= 0.0002 valid=60.2% attn=0.000 d²=0.0002\n", " k=2: pairs= 3 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=3: pairs= 6 vol²= -0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=4: pairs=10 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n", " k=5: pairs=15 vol²= 0.0000 valid= 0.0% attn=0.000 d²=0.0000\n" ] }, { "output_type": "error", "ename": "KeyboardInterrupt", "evalue": "", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipython-input-2853210191.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 358\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 359\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"__main__\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 360\u001b[0;31m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m/tmp/ipython-input-2853210191.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[0mce\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcross_entropy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlab\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 313\u001b[0m \u001b[0mloss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mce\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m0.1\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0malign\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 314\u001b[0;31m \u001b[0mloss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbackward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 315\u001b[0m \u001b[0mopt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 316\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/_tensor.py\u001b[0m in \u001b[0;36mbackward\u001b[0;34m(self, gradient, retain_graph, create_graph, inputs)\u001b[0m\n\u001b[1;32m 623\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 624\u001b[0m )\n\u001b[0;32m--> 625\u001b[0;31m torch.autograd.backward(\n\u001b[0m\u001b[1;32m 626\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgradient\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mretain_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcreate_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 627\u001b[0m )\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/autograd/__init__.py\u001b[0m in \u001b[0;36mbackward\u001b[0;34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[0m\n\u001b[1;32m 352\u001b[0m \u001b[0;31m# some Python versions print out the first line of a multi-line function\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 353\u001b[0m \u001b[0;31m# calls in the traceback and some print out the last line\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 354\u001b[0;31m _engine_run_backward(\n\u001b[0m\u001b[1;32m 355\u001b[0m \u001b[0mtensors\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 356\u001b[0m \u001b[0mgrad_tensors_\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/autograd/graph.py\u001b[0m in \u001b[0;36m_engine_run_backward\u001b[0;34m(t_outputs, *args, **kwargs)\u001b[0m\n\u001b[1;32m 839\u001b[0m \u001b[0munregister_hooks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_register_logging_hooks_on_whole_graph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt_outputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 840\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 841\u001b[0;31m return Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass\n\u001b[0m\u001b[1;32m 842\u001b[0m \u001b[0mt_outputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 843\u001b[0m ) # Calls into the C++ engine to run the backward pass\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ] }, { "cell_type": "code", "source": [ "#@title GeoLevel with Uniform Contribution Scale\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, simplex_order):\n", " super().__init__()\n", " self._order = simplex_order\n", " self._nv = simplex_order + 1\n", "\n", " if self._nv < 2:\n", " self._npairs = 0\n", " self.register_buffer('_pi', torch.tensor([], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([], dtype=torch.long))\n", " else:\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " if self._order > 0:\n", " sign = (-1.0) ** (self._order + 1)\n", " fact = math.factorial(self._order)\n", " self._prefactor = sign / ((2.0 ** self._order) * (fact ** 2))\n", " else:\n", " self._prefactor = 1.0\n", "\n", " def forward(self, verts):\n", " if self._order == 0:\n", " shape = verts.shape[:-2]\n", " return (torch.zeros(*shape, 0, device=verts.device),\n", " torch.ones(*shape, device=verts.device),\n", " torch.ones(*shape, dtype=torch.bool, device=verts.device))\n", "\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", " det = torch.linalg.det(cm)\n", "\n", " vol2 = self._prefactor * det\n", " valid = vol2 > 1e-8\n", "\n", " return d2_pairs, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC LEVEL - UNIFORM CONTRIBUTION\n", "# ============================================================================\n", "\n", "class GeoLevel(nn.Module):\n", " # Class-level constant: deform scale safe for highest k\n", " BASE_DEFORM = 0.05 # Small enough for k=5 to stay valid\n", "\n", " def __init__(self, order, nin, nout, fdim, edim):\n", " super().__init__()\n", " self._order = order\n", " self._nin = nin\n", " self._nout = nout\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._nv = order + 1\n", "\n", " self._cm = CMValidator(order)\n", "\n", " # Regular simplex template from factory\n", " factory = SimplexFactory(k=order, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template)\n", "\n", " # Compute template's natural volume for normalization\n", " with torch.no_grad():\n", " _, template_vol, _ = self._cm(template.unsqueeze(0))\n", " self._template_vol = template_vol.item()\n", "\n", " # Vertex selection\n", " _sel = torch.randn(nout, self._nv, nin) * 0.1\n", " self.register_parameter('_W_select', nn.Parameter(_sel))\n", "\n", " # Deformation network\n", " self._deform = nn.Sequential(\n", " nn.Linear(fdim, fdim),\n", " nn.LayerNorm(fdim),\n", " nn.GELU(),\n", " nn.Linear(fdim, self._nv * edim),\n", " )\n", "\n", " # Feature MLP\n", " self._mlp = nn.Sequential(\n", " nn.Linear(fdim, fdim * 2),\n", " nn.LayerNorm(fdim * 2),\n", " nn.GELU(),\n", " nn.Linear(fdim * 2, fdim),\n", " )\n", "\n", " # Geometry gate - normalize by template volume for uniform contribution\n", " gate_in = max(1, self._cm._npairs + 1)\n", " self._gate = nn.Sequential(\n", " nn.Linear(gate_in, fdim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Volume normalization factor: scale vol² so all k contribute equally\n", " # vol² for regular k-simplex ∝ 1/((k+1)! * 2^k)\n", " # We want to amplify higher-k volumes\n", " self._vol_norm = math.factorial(order + 1) * (2 ** order)\n", "\n", " # Attenuation uses NORMALIZED volume\n", " self.register_parameter('_scale', nn.Parameter(torch.tensor(5.0))) # Higher sensitivity\n", " self.register_parameter('_bias', nn.Parameter(torch.tensor(0.0)))\n", "\n", " # Residual\n", " if nin != nout:\n", " self._proj = nn.Linear(nin * fdim, nout * fdim)\n", " else:\n", " self._proj = None\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", "\n", " sel = F.softmax(self._W_select, dim=-1)\n", " picked = torch.einsum('ovn,bnf->bovf', sel, x)\n", " agg = picked.mean(dim=2)\n", "\n", " # Deformation - UNIFORM small scale\n", " deform = self._deform(agg).view(B, self._nout, self._nv, self._edim)\n", "\n", " # Template expansion\n", " template_expanded = self._template.unsqueeze(0).unsqueeze(0).expand(B, self._nout, -1, -1)\n", "\n", " # Apply UNIFORM deformation scale\n", " verts = template_expanded + self.BASE_DEFORM * deform\n", "\n", " # CM computation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " # NORMALIZE volume for uniform contribution across k\n", " vol2_norm = vol2 * self._vol_norm\n", "\n", " # Geometric gating with normalized volume\n", " if self._cm._npairs == 0:\n", " geo = vol2_norm.unsqueeze(-1)\n", " else:\n", " # Also normalize distances? Keep raw for now\n", " geo = torch.cat([d2, vol2_norm.unsqueeze(-1)], dim=-1)\n", " gate = self._gate(geo)\n", "\n", " # Feature transform\n", " out = self._mlp(agg) * gate\n", "\n", " # Attenuation based on NORMALIZED volume\n", " logv = torch.log(vol2_norm.abs() + 1e-8)\n", " attn = torch.sigmoid(self._scale * logv + self._bias)\n", " out = out * attn.unsqueeze(-1)\n", "\n", " # Residual\n", " if self._proj is not None:\n", " res = self._proj(x.flatten(1)).view(B, self._nout, self._fdim)\n", " else:\n", " res = x\n", " out = out + 0.1 * res\n", "\n", " return out, {\n", " 'd2': d2,\n", " 'vol2': vol2,\n", " 'vol2_norm': vol2_norm,\n", " 'valid': valid,\n", " 'attn': attn,\n", " 'order': self._order,\n", " 'vol_norm_factor': self._vol_norm,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# COMPLEX\n", "# ============================================================================\n", "\n", "class GeoComplex(nn.Module):\n", " def __init__(self, nbase, fdim, depth, growth, edim):\n", " super().__init__()\n", " self._nbase = nbase\n", " self._fdim = fdim\n", " self._depth = depth\n", "\n", " sizes = [nbase]\n", " for i in range(depth):\n", " sizes.append(sizes[-1] + max(1, int(growth * (i + 1))))\n", " self._sizes = sizes\n", "\n", " self._proj_in = nn.Linear(fdim, nbase * fdim)\n", "\n", " level_list = []\n", " for i in range(depth):\n", " lv = GeoLevel(order=i + 1, nin=sizes[i], nout=sizes[i+1], fdim=fdim, edim=edim)\n", " level_list.append(lv)\n", " self._levels = nn.ModuleList(level_list)\n", "\n", " self.register_parameter('_lw', nn.Parameter(torch.ones(depth) / depth))\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", " h = self._proj_in(x).view(B, self._nbase, self._fdim)\n", "\n", " outs, infos = [], []\n", " for lv in self._levels:\n", " h, info = lv(h)\n", " outs.append(h)\n", " infos.append(info)\n", "\n", " w = F.softmax(self._lw, dim=0)\n", " pooled = []\n", " for i, (o, inf) in enumerate(zip(outs, infos)):\n", " a = inf['attn']\n", " wm = (o * a.unsqueeze(-1)).sum(1) / (a.sum(1, keepdim=True) + 1e-8)\n", " pooled.append(w[i] * wm)\n", "\n", " return sum(pooled), {'sizes': self._sizes, 'infos': infos, 'weights': w.detach()}\n", "\n", "\n", "# ============================================================================\n", "# MODEL\n", "# ============================================================================\n", "\n", "class FashionGeoSimplex(nn.Module):\n", " def __init__(self, nbase=4, fdim=32, depth=5, growth=1.5, edim=8, ncpx=2):\n", " super().__init__()\n", "\n", " self._stem = nn.Sequential(\n", " nn.Conv2d(1, 32, 3, padding=1), nn.BatchNorm2d(32), nn.GELU(),\n", " nn.Conv2d(32, 64, 3, stride=2, padding=1), nn.BatchNorm2d(64), nn.GELU(),\n", " nn.Conv2d(64, 128, 3, stride=2, padding=1), nn.BatchNorm2d(128), nn.GELU(),\n", " )\n", " self._pool = nn.AdaptiveAvgPool2d(1)\n", " self._proj = nn.Linear(128, fdim)\n", "\n", " cpx_list = []\n", " for i in range(ncpx):\n", " c = GeoComplex(nbase, fdim, depth, growth, edim)\n", " cpx_list.append(c)\n", " print(f\"Complex {i+1} sizes: {c._sizes}\")\n", " self._cpx = nn.ModuleList(cpx_list)\n", " self._norms = nn.ModuleList([nn.LayerNorm(fdim) for _ in range(ncpx)])\n", "\n", " self._head = nn.Linear(fdim, 10)\n", "\n", " def forward(self, x, ret_diag=False):\n", " h = self._stem(x)\n", " h = self._pool(h).flatten(1)\n", " h = self._proj(h)\n", "\n", " diags = []\n", " align = 0\n", " for cpx, norm in zip(self._cpx, self._norms):\n", " out, diag = cpx(h)\n", " diags.append(diag)\n", " for inf in diag['infos']:\n", " align = align + F.relu(-inf['vol2']).mean()\n", " h = norm(h + out)\n", "\n", " logits = self._head(h)\n", "\n", " if ret_diag:\n", " return logits, diags, align\n", " return logits, align\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = FashionGeoSimplex(\n", " nbase=4, fdim=32, depth=5, growth=1.5, edim=8, ncpx=2\n", " ).to(device)\n", "\n", " print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\")\n", " print(f\"Deform scale: {GeoLevel.BASE_DEFORM}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 115)\n", "\n", " for ep in range(30):\n", " model.train()\n", " lsum, asum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, align = model(img)\n", " ce = F.cross_entropy(logits, lab)\n", " loss = ce + 0.1 * align\n", " loss.backward()\n", " opt.step()\n", "\n", " lsum += ce.item() * img.size(0)\n", " asum += align.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", " tr_loss = lsum / tot\n", " tr_align = asum / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " samp = None\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, diag, _ = model(img, ret_diag=True)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " if samp is None:\n", " samp = diag\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"Ep {ep+1:2d} | CE {tr_loss:.4f} | Align {tr_align:.4f} | Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%}\")\n", " for inf in samp[0]['infos']:\n", " k = inf['order']\n", " v = inf['vol2'].mean().item()\n", " vn = inf['vol2_norm'].mean().item()\n", " vld = inf['valid'].float().mean().item()\n", " att = inf['attn'].mean().item()\n", " d = inf['d2'].mean().item() if inf['d2'].numel() > 0 else 0\n", " nf = inf['vol_norm_factor']\n", " print(f\" k={k}: vol²={v:.2e} norm_vol²={vn:8.4f} (×{nf:6.0f}) valid={vld:5.1%} attn={att:.3f} d²={d:.4f}\")\n", "\n", " print(\"=\" * 115)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "cellView": "form", "id": "0KgB_FEGI_wQ", "outputId": "b57726eb-f7cd-438d-9640-7279aa802eec" }, "execution_count": 2, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "Complex 1 sizes: [4, 5, 8, 12, 18, 25]\n", "Complex 2 sizes: [4, 5, 8, 12, 18, 25]\n", "Params: 1,870,480\n", "Deform scale: 0.05\n", "\n", "Training...\n", "===================================================================================================================\n", "Ep 1 | CE 0.6210 | Align 0.0000 | Tr 79.12% | Te 82.34% | Best 82.34%\n", " k=1: vol²=1.10e+00 norm_vol²= 4.3829 (× 4) valid=100.0% attn=0.999 d²=1.0957\n", " k=2: vol²=1.74e-01 norm_vol²= 4.1699 (× 24) valid=100.0% attn=0.999 d²=0.9628\n", " k=3: vol²=1.22e-02 norm_vol²= 2.3417 (× 192) valid=100.0% attn=0.957 d²=0.9536\n", " k=4: vol²=3.84e-04 norm_vol²= 0.7364 (× 1920) valid=100.0% attn=0.214 d²=0.9126\n", " k=5: vol²=9.74e-06 norm_vol²= 0.2244 (× 23040) valid=100.0% attn=0.002 d²=0.9434\n", "Ep 6 | CE 0.2658 | Align 0.0000 | Tr 90.39% | Te 88.01% | Best 88.69%\n", " k=1: vol²=1.10e+00 norm_vol²= 4.3852 (× 4) valid=100.0% attn=0.997 d²=1.0963\n", " k=2: vol²=1.71e-01 norm_vol²= 4.1001 (× 24) valid=100.0% attn=0.995 d²=0.9512\n", " k=3: vol²=6.77e-03 norm_vol²= 1.3001 (× 192) valid=100.0% attn=0.559 d²=0.7758\n", " k=4: vol²=2.16e-04 norm_vol²= 0.4138 (× 1920) valid=100.0% attn=0.039 d²=0.7908\n", " k=5: vol²=5.65e-06 norm_vol²= 0.1301 (× 23040) valid=100.0% attn=0.000 d²=0.8504\n", "Ep 11 | CE 0.1864 | Align 0.0000 | Tr 93.15% | Te 90.00% | Best 90.00%\n", " k=1: vol²=1.01e+00 norm_vol²= 4.0337 (× 4) valid=100.0% attn=0.988 d²=1.0084\n", " k=2: vol²=1.67e-01 norm_vol²= 4.0047 (× 24) valid=100.0% attn=0.988 d²=0.9339\n", " k=3: vol²=3.95e-03 norm_vol²= 0.7577 (× 192) valid=100.0% attn=0.203 d²=0.6477\n", " k=4: vol²=1.45e-04 norm_vol²= 0.2780 (× 1920) valid=100.0% attn=0.006 d²=0.7200\n", " k=5: vol²=3.54e-06 norm_vol²= 0.0815 (× 23040) valid=100.0% attn=0.000 d²=0.7747\n", "Ep 16 | CE 0.1209 | Align 0.0000 | Tr 95.69% | Te 90.17% | Best 90.23%\n", " k=1: vol²=9.67e-01 norm_vol²= 3.8681 (× 4) valid=100.0% attn=0.974 d²=0.9670\n", " k=2: vol²=1.83e-01 norm_vol²= 4.3922 (× 24) valid=100.0% attn=0.976 d²=0.9606\n", " k=3: vol²=4.44e-03 norm_vol²= 0.8527 (× 192) valid=100.0% attn=0.264 d²=0.6602\n", " k=4: vol²=1.37e-04 norm_vol²= 0.2625 (× 1920) valid=100.0% attn=0.014 d²=0.7010\n", " k=5: vol²=2.42e-06 norm_vol²= 0.0557 (× 23040) valid=100.0% attn=0.000 d²=0.7184\n", "Ep 21 | CE 0.0618 | Align 0.0000 | Tr 97.87% | Te 89.79% | Best 90.23%\n", " k=1: vol²=9.37e-01 norm_vol²= 3.7499 (× 4) valid=100.0% attn=0.972 d²=0.9375\n", " k=2: vol²=1.80e-01 norm_vol²= 4.3153 (× 24) valid=100.0% attn=0.969 d²=0.9480\n", " k=3: vol²=4.56e-03 norm_vol²= 0.8756 (× 192) valid=100.0% attn=0.280 d²=0.6662\n", " k=4: vol²=1.05e-04 norm_vol²= 0.2024 (× 1920) valid=100.0% attn=0.007 d²=0.6569\n", " k=5: vol²=1.81e-06 norm_vol²= 0.0417 (× 23040) valid=100.0% attn=0.000 d²=0.6783\n", "Ep 26 | CE 0.0219 | Align 0.0000 | Tr 99.39% | Te 90.16% | Best 90.39%\n", " k=1: vol²=9.27e-01 norm_vol²= 3.7065 (× 4) valid=100.0% attn=0.970 d²=0.9266\n", " k=2: vol²=1.86e-01 norm_vol²= 4.4618 (× 24) valid=100.0% attn=0.972 d²=0.9652\n", " k=3: vol²=4.70e-03 norm_vol²= 0.9024 (× 192) valid=100.0% attn=0.287 d²=0.6710\n", " k=4: vol²=1.79e-04 norm_vol²= 0.3436 (× 1920) valid=100.0% attn=0.087 d²=0.7025\n", " k=5: vol²=1.96e-06 norm_vol²= 0.0451 (× 23040) valid=100.0% attn=0.000 d²=0.6807\n", "Ep 30 | CE 0.0129 | Align 0.0000 | Tr 99.69% | Te 90.05% | Best 90.39%\n", " k=1: vol²=9.34e-01 norm_vol²= 3.7373 (× 4) valid=100.0% attn=0.971 d²=0.9343\n", " k=2: vol²=1.87e-01 norm_vol²= 4.4798 (× 24) valid=100.0% attn=0.973 d²=0.9686\n", " k=3: vol²=4.66e-03 norm_vol²= 0.8940 (× 192) valid=100.0% attn=0.289 d²=0.6706\n", " k=4: vol²=1.76e-04 norm_vol²= 0.3385 (× 1920) valid=100.0% attn=0.083 d²=0.7028\n", " k=5: vol²=1.92e-06 norm_vol²= 0.0443 (× 23040) valid=100.0% attn=0.000 d²=0.6794\n", "===================================================================================================================\n", "Best: 90.39%\n" ] } ] }, { "cell_type": "code", "source": [ "#@title Validity-Only Attenuation (Valid = Full Contribution)\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, simplex_order):\n", " super().__init__()\n", " self._order = simplex_order\n", " self._nv = simplex_order + 1\n", "\n", " if self._nv < 2:\n", " self._npairs = 0\n", " self.register_buffer('_pi', torch.tensor([], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([], dtype=torch.long))\n", " else:\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " if self._order > 0:\n", " sign = (-1.0) ** (self._order + 1)\n", " fact = math.factorial(self._order)\n", " self._prefactor = sign / ((2.0 ** self._order) * (fact ** 2))\n", " else:\n", " self._prefactor = 1.0\n", "\n", " def forward(self, verts):\n", " if self._order == 0:\n", " shape = verts.shape[:-2]\n", " return (torch.zeros(*shape, 0, device=verts.device),\n", " torch.ones(*shape, device=verts.device),\n", " torch.ones(*shape, dtype=torch.bool, device=verts.device))\n", "\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", " det = torch.linalg.det(cm)\n", "\n", " vol2 = self._prefactor * det\n", " valid = vol2 > 1e-8\n", "\n", " return d2_pairs, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC LEVEL - VALIDITY-ONLY ATTENUATION\n", "# ============================================================================\n", "\n", "class GeoLevel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, order, nin, nout, fdim, edim):\n", " super().__init__()\n", " self._order = order\n", " self._nin = nin\n", " self._nout = nout\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._nv = order + 1\n", "\n", " self._cm = CMValidator(order)\n", "\n", " # Regular simplex template\n", " factory = SimplexFactory(k=order, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template)\n", "\n", " # Vertex selection\n", " _sel = torch.randn(nout, self._nv, nin) * 0.1\n", " self.register_parameter('_W_select', nn.Parameter(_sel))\n", "\n", " # Deformation network\n", " self._deform = nn.Sequential(\n", " nn.Linear(fdim, fdim),\n", " nn.LayerNorm(fdim),\n", " nn.GELU(),\n", " nn.Linear(fdim, self._nv * edim),\n", " )\n", "\n", " # Feature MLP\n", " self._mlp = nn.Sequential(\n", " nn.Linear(fdim, fdim * 2),\n", " nn.LayerNorm(fdim * 2),\n", " nn.GELU(),\n", " nn.Linear(fdim * 2, fdim),\n", " )\n", "\n", " # Geometry-to-feature gate (NOT attenuation - this is for feature modulation)\n", " gate_in = max(1, self._cm._npairs + 1)\n", " self._gate = nn.Sequential(\n", " nn.Linear(gate_in, fdim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Residual\n", " if nin != nout:\n", " self._proj = nn.Linear(nin * fdim, nout * fdim)\n", " else:\n", " self._proj = None\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", "\n", " sel = F.softmax(self._W_select, dim=-1)\n", " picked = torch.einsum('ovn,bnf->bovf', sel, x)\n", " agg = picked.mean(dim=2)\n", "\n", " # Deformation\n", " deform = self._deform(agg).view(B, self._nout, self._nv, self._edim)\n", " template_expanded = self._template.unsqueeze(0).unsqueeze(0).expand(B, self._nout, -1, -1)\n", " verts = template_expanded + self.BASE_DEFORM * deform\n", "\n", " # CM computation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " # Geometric gating (feature modulation based on geometry)\n", " if self._cm._npairs == 0:\n", " geo = torch.ones(B, self._nout, 1, device=x.device)\n", " else:\n", " # Normalize d2 for gating (mean-center, scale)\n", " d2_norm = d2 / (d2.mean(dim=-1, keepdim=True) + 1e-8)\n", " geo = torch.cat([d2_norm, vol2.unsqueeze(-1) / (vol2.mean() + 1e-8)], dim=-1)\n", " gate = self._gate(geo)\n", "\n", " # Feature transform WITH geometry modulation\n", " out = self._mlp(agg) * gate\n", "\n", " # VALIDITY-ONLY ATTENUATION\n", " # Valid (vol² > 0) → attn = 1.0 (full contribution)\n", " # Invalid (vol² ≤ 0) → attn = 0.0 (no contribution)\n", " # Use soft version for gradient flow\n", " attn = torch.sigmoid(vol2 * 1e6) # Sharp sigmoid: valid→1, invalid→0\n", " out = out * attn.unsqueeze(-1)\n", "\n", " # Residual\n", " if self._proj is not None:\n", " res = self._proj(x.flatten(1)).view(B, self._nout, self._fdim)\n", " else:\n", " res = x\n", " out = out + 0.1 * res\n", "\n", " return out, {\n", " 'd2': d2,\n", " 'vol2': vol2,\n", " 'valid': valid,\n", " 'attn': attn,\n", " 'order': self._order,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# COMPLEX\n", "# ============================================================================\n", "\n", "class GeoComplex(nn.Module):\n", " def __init__(self, nbase, fdim, depth, growth, edim):\n", " super().__init__()\n", " self._nbase = nbase\n", " self._fdim = fdim\n", " self._depth = depth\n", "\n", " sizes = [nbase]\n", " for i in range(depth):\n", " sizes.append(sizes[-1] + max(1, int(growth * (i + 1))))\n", " self._sizes = sizes\n", "\n", " self._proj_in = nn.Linear(fdim, nbase * fdim)\n", "\n", " level_list = []\n", " for i in range(depth):\n", " lv = GeoLevel(order=i + 1, nin=sizes[i], nout=sizes[i+1], fdim=fdim, edim=edim)\n", " level_list.append(lv)\n", " self._levels = nn.ModuleList(level_list)\n", "\n", " # EQUAL weights - each k contributes equally\n", " self.register_parameter('_lw', nn.Parameter(torch.ones(depth) / depth))\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", " h = self._proj_in(x).view(B, self._nbase, self._fdim)\n", "\n", " outs, infos = [], []\n", " for lv in self._levels:\n", " h, info = lv(h)\n", " outs.append(h)\n", " infos.append(info)\n", "\n", " w = F.softmax(self._lw, dim=0)\n", " pooled = []\n", " for i, (o, inf) in enumerate(zip(outs, infos)):\n", " a = inf['attn']\n", " # Mean over simplices (attn-weighted for validity)\n", " wm = (o * a.unsqueeze(-1)).sum(1) / (a.sum(1, keepdim=True) + 1e-8)\n", " pooled.append(w[i] * wm)\n", "\n", " return sum(pooled), {'sizes': self._sizes, 'infos': infos, 'weights': w.detach()}\n", "\n", "\n", "# ============================================================================\n", "# MODEL\n", "# ============================================================================\n", "\n", "class FashionGeoSimplex(nn.Module):\n", " def __init__(self, nbase=4, fdim=32, depth=5, growth=1.5, edim=8, ncpx=1):\n", " super().__init__()\n", "\n", " self._stem = nn.Sequential(\n", " nn.Conv2d(1, 32, 3, padding=1), nn.BatchNorm2d(32), nn.GELU(),\n", " nn.Conv2d(32, 64, 3, stride=2, padding=1), nn.BatchNorm2d(64), nn.GELU(),\n", " nn.Conv2d(64, 128, 3, stride=2, padding=1), nn.BatchNorm2d(128), nn.GELU(),\n", " )\n", " self._pool = nn.AdaptiveAvgPool2d(1)\n", " self._proj = nn.Linear(128, fdim)\n", "\n", " cpx_list = []\n", " for i in range(ncpx):\n", " c = GeoComplex(nbase, fdim, depth, growth, edim)\n", " cpx_list.append(c)\n", " print(f\"Complex {i+1} sizes: {c._sizes}\")\n", " self._cpx = nn.ModuleList(cpx_list)\n", " self._norms = nn.ModuleList([nn.LayerNorm(fdim) for _ in range(ncpx)])\n", "\n", " self._head = nn.Linear(fdim, 10)\n", "\n", " def forward(self, x, ret_diag=False):\n", " h = self._stem(x)\n", " h = self._pool(h).flatten(1)\n", " h = self._proj(h)\n", "\n", " diags = []\n", " validity_loss = 0\n", " for cpx, norm in zip(self._cpx, self._norms):\n", " out, diag = cpx(h)\n", " diags.append(diag)\n", " # Penalize invalid simplices\n", " for inf in diag['infos']:\n", " validity_loss = validity_loss + F.relu(-inf['vol2']).mean()\n", " h = norm(h + out)\n", "\n", " logits = self._head(h)\n", "\n", " if ret_diag:\n", " return logits, diags, validity_loss\n", " return logits, validity_loss\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = FashionGeoSimplex(\n", " nbase=4, fdim=32, depth=5, growth=1.5, edim=8, ncpx=1\n", " ).to(device)\n", "\n", " print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 95)\n", "\n", " for ep in range(30):\n", " model.train()\n", " lsum, vsum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, vloss = model(img)\n", " ce = F.cross_entropy(logits, lab)\n", " loss = ce + 0.1 * vloss\n", " loss.backward()\n", " opt.step()\n", "\n", " lsum += ce.item() * img.size(0)\n", " vsum += vloss.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", " tr_loss = lsum / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " samp = None\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, diag, _ = model(img, ret_diag=True)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " if samp is None:\n", " samp = diag\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " # Get level weights\n", " lw = samp[0]['weights'].cpu().numpy()\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"Ep {ep+1:2d} | CE {tr_loss:.4f} | Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%}\")\n", " print(f\" | Level weights: {['%.3f' % w for w in lw]}\")\n", " for inf in samp[0]['infos']:\n", " k = inf['order']\n", " v = inf['vol2'].mean().item()\n", " vld = inf['valid'].float().mean().item()\n", " att = inf['attn'].mean().item()\n", " d = inf['d2'].mean().item() if inf['d2'].numel() > 0 else 0\n", " print(f\" k={k}: vol²={v:.2e} valid={vld:5.1%} attn={att:.3f} d²={d:.4f}\")\n", "\n", " print(\"=\" * 95)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "cellView": "form", "id": "gk0zo8LONzca", "outputId": "f136f1a9-7a2b-4e0b-be80-fce3644d58bd" }, "execution_count": 5, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "Complex 1 sizes: [4, 5, 8, 12, 18, 25]\n", "Params: 984,019\n", "\n", "Training...\n", "===============================================================================================\n", "Ep 1 | CE 0.6183 | Tr 79.44% | Te 82.62% | Best 82.62%\n", " | Level weights: ['0.207', '0.203', '0.199', '0.198', '0.194']\n", " k=1: vol²=8.62e-01 valid=100.0% attn=1.000 d²=0.8620\n", " k=2: vol²=1.86e-01 valid=100.0% attn=1.000 d²=0.9950\n", " k=3: vol²=1.51e-02 valid=100.0% attn=1.000 d²=1.0361\n", " k=4: vol²=6.00e-04 valid=100.0% attn=1.000 d²=1.0339\n", " k=5: vol²=1.14e-05 valid=100.0% attn=1.000 d²=0.9892\n", "Ep 6 | CE 0.2578 | Tr 90.67% | Te 87.74% | Best 87.74%\n", " | Level weights: ['0.210', '0.196', '0.191', '0.207', '0.195']\n", " k=1: vol²=4.75e-01 valid=100.0% attn=1.000 d²=0.4749\n", " k=2: vol²=1.98e-01 valid=100.0% attn=1.000 d²=0.8901\n", " k=3: vol²=1.98e-02 valid=100.0% attn=1.000 d²=1.0984\n", " k=4: vol²=4.95e-04 valid=100.0% attn=1.000 d²=1.0151\n", " k=5: vol²=5.98e-06 valid=100.0% attn=0.997 d²=0.9892\n", "Ep 11 | CE 0.1790 | Tr 93.56% | Te 88.70% | Best 89.66%\n", " | Level weights: ['0.207', '0.184', '0.183', '0.216', '0.210']\n", " k=1: vol²=2.71e-01 valid=100.0% attn=1.000 d²=0.2715\n", " k=2: vol²=1.66e-01 valid=100.0% attn=1.000 d²=0.8062\n", " k=3: vol²=2.53e-02 valid=100.0% attn=1.000 d²=1.0979\n", " k=4: vol²=3.09e-04 valid=100.0% attn=1.000 d²=0.9295\n", " k=5: vol²=5.28e-08 valid=100.0% attn=0.513 d²=0.7772\n", "Ep 16 | CE 0.1146 | Tr 95.83% | Te 89.59% | Best 89.99%\n", " | Level weights: ['0.205', '0.179', '0.177', '0.222', '0.216']\n", " k=1: vol²=2.80e-01 valid=100.0% attn=1.000 d²=0.2799\n", " k=2: vol²=2.98e-01 valid=100.0% attn=1.000 d²=1.0136\n", " k=3: vol²=3.29e-02 valid=100.0% attn=1.000 d²=1.1553\n", " k=4: vol²=1.47e-04 valid=100.0% attn=1.000 d²=0.8277\n", " k=5: vol²=2.97e-08 valid=100.0% attn=0.507 d²=0.8029\n", "Ep 21 | CE 0.0568 | Tr 97.98% | Te 89.74% | Best 89.99%\n", " | Level weights: ['0.210', '0.182', '0.176', '0.220', '0.212']\n", " k=1: vol²=1.83e-01 valid=100.0% attn=1.000 d²=0.1833\n", " k=2: vol²=2.07e-01 valid=100.0% attn=1.000 d²=0.8469\n", " k=3: vol²=2.62e-02 valid=100.0% attn=1.000 d²=1.0473\n", " k=4: vol²=1.55e-04 valid=100.0% attn=1.000 d²=0.7964\n", " k=5: vol²=2.45e-08 valid=100.0% attn=0.506 d²=0.8140\n", "Ep 26 | CE 0.0201 | Tr 99.45% | Te 89.71% | Best 90.05%\n", " | Level weights: ['0.214', '0.185', '0.177', '0.217', '0.207']\n", " k=1: vol²=2.19e-01 valid=100.0% attn=1.000 d²=0.2194\n", " k=2: vol²=2.29e-01 valid=100.0% attn=1.000 d²=0.8758\n", " k=3: vol²=2.19e-02 valid=100.0% attn=1.000 d²=0.9457\n", " k=4: vol²=1.41e-04 valid=100.0% attn=1.000 d²=0.7400\n", " k=5: vol²=2.15e-08 valid=64.1% attn=0.505 d²=0.8143\n", "Ep 30 | CE 0.0115 | Tr 99.77% | Te 89.72% | Best 90.05%\n", " | Level weights: ['0.215', '0.185', '0.177', '0.216', '0.206']\n", " k=1: vol²=2.16e-01 valid=100.0% attn=1.000 d²=0.2163\n", " k=2: vol²=2.25e-01 valid=100.0% attn=1.000 d²=0.8611\n", " k=3: vol²=2.11e-02 valid=100.0% attn=1.000 d²=0.9233\n", " k=4: vol²=1.42e-04 valid=100.0% attn=1.000 d²=0.7251\n", " k=5: vol²=1.99e-08 valid=55.5% attn=0.505 d²=0.8124\n", "===============================================================================================\n", "Best: 90.05%\n" ] } ] }, { "cell_type": "code", "source": [ "#@title True CM KSimplex - Exponential Growth (Proper Structure)\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, simplex_order):\n", " super().__init__()\n", " self._order = simplex_order\n", " self._nv = simplex_order + 1\n", "\n", " if self._nv < 2:\n", " self._npairs = 0\n", " self.register_buffer('_pi', torch.tensor([], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([], dtype=torch.long))\n", " else:\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " if self._order > 0:\n", " sign = (-1.0) ** (self._order + 1)\n", " fact = math.factorial(self._order)\n", " self._prefactor = sign / ((2.0 ** self._order) * (fact ** 2))\n", " else:\n", " self._prefactor = 1.0\n", "\n", " def forward(self, verts):\n", " if self._order == 0:\n", " shape = verts.shape[:-2]\n", " return (torch.zeros(*shape, 0, device=verts.device),\n", " torch.ones(*shape, device=verts.device),\n", " torch.ones(*shape, dtype=torch.bool, device=verts.device))\n", "\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", " det = torch.linalg.det(cm)\n", "\n", " vol2 = self._prefactor * det\n", " valid = vol2 > 1e-8\n", "\n", " return d2_pairs, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC LEVEL\n", "# ============================================================================\n", "\n", "class GeoLevel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, order, nin, nout, fdim, edim):\n", " super().__init__()\n", " self._order = order\n", " self._nin = nin\n", " self._nout = nout\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._nv = order + 1\n", "\n", " # Potential simplices from nin inputs\n", " self._potential = math.comb(nin, self._nv)\n", "\n", " self._cm = CMValidator(order)\n", "\n", " # Regular simplex template\n", " factory = SimplexFactory(k=order, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template)\n", "\n", " # Vertex selection: [nout, nv, nin]\n", " # Each output simplex soft-selects nv vertices from nin inputs\n", " _sel = torch.randn(nout, self._nv, nin) * 0.1\n", " self.register_parameter('_W_select', nn.Parameter(_sel))\n", "\n", " # Deformation network\n", " self._deform = nn.Sequential(\n", " nn.Linear(fdim, fdim),\n", " nn.LayerNorm(fdim),\n", " nn.GELU(),\n", " nn.Linear(fdim, self._nv * edim),\n", " )\n", "\n", " # Feature MLP\n", " self._mlp = nn.Sequential(\n", " nn.Linear(fdim, fdim * 2),\n", " nn.LayerNorm(fdim * 2),\n", " nn.GELU(),\n", " nn.Linear(fdim * 2, fdim),\n", " )\n", "\n", " # Geometry gate\n", " gate_in = max(1, self._cm._npairs + 1)\n", " self._gate = nn.Sequential(\n", " nn.Linear(gate_in, fdim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Residual\n", " if nin != nout:\n", " self._proj = nn.Linear(nin * fdim, nout * fdim)\n", " else:\n", " self._proj = None\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", "\n", " sel = F.softmax(self._W_select, dim=-1)\n", " picked = torch.einsum('ovn,bnf->bovf', sel, x)\n", " agg = picked.mean(dim=2)\n", "\n", " # Deformation\n", " deform = self._deform(agg).view(B, self._nout, self._nv, self._edim)\n", " template_expanded = self._template.unsqueeze(0).unsqueeze(0).expand(B, self._nout, -1, -1)\n", " verts = template_expanded + self.BASE_DEFORM * deform\n", "\n", " # CM computation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " # Geometric gating\n", " if self._cm._npairs == 0:\n", " geo = torch.ones(B, self._nout, 1, device=x.device)\n", " else:\n", " d2_norm = d2 / (d2.mean(dim=-1, keepdim=True) + 1e-8)\n", " geo = torch.cat([d2_norm, vol2.unsqueeze(-1) / (vol2.mean() + 1e-8)], dim=-1)\n", " gate = self._gate(geo)\n", "\n", " out = self._mlp(agg) * gate\n", "\n", " # Validity-only attenuation\n", " attn = torch.sigmoid(vol2 * 1e6)\n", " out = out * attn.unsqueeze(-1)\n", "\n", " # Residual\n", " if self._proj is not None:\n", " res = self._proj(x.flatten(1)).view(B, self._nout, self._fdim)\n", " else:\n", " res = x\n", " out = out + 0.1 * res\n", "\n", " return out, {\n", " 'd2': d2,\n", " 'vol2': vol2,\n", " 'valid': valid,\n", " 'attn': attn,\n", " 'order': self._order,\n", " 'potential': self._potential,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# COMPLEX - EXPONENTIAL GROWTH\n", "# ============================================================================\n", "\n", "class GeoComplex(nn.Module):\n", " def __init__(self, nbase, fdim, depth, edim):\n", " super().__init__()\n", " self._nbase = nbase\n", " self._fdim = fdim\n", " self._depth = depth\n", "\n", " # EXPONENTIAL GROWTH: each level doubles\n", " # Outputs become vertices for next level\n", " sizes = [nbase]\n", " for _ in range(depth):\n", " sizes.append(sizes[-1] * 2)\n", " self._sizes = sizes\n", "\n", " # Compute potential simplices at each level\n", " potentials = []\n", " for i in range(depth):\n", " k = i + 1 # simplex order\n", " nv = k + 1 # vertices needed\n", " nin = sizes[i]\n", " pot = math.comb(nin, nv) if nin >= nv else 0\n", " potentials.append(pot)\n", " self._potentials = potentials\n", "\n", " # Input projection: fdim -> nbase * fdim\n", " self._proj_in = nn.Linear(fdim, nbase * fdim)\n", "\n", " # Build levels\n", " level_list = []\n", " for i in range(depth):\n", " lv = GeoLevel(\n", " order=i + 1,\n", " nin=sizes[i],\n", " nout=sizes[i + 1],\n", " fdim=fdim,\n", " edim=edim\n", " )\n", " level_list.append(lv)\n", " self._levels = nn.ModuleList(level_list)\n", "\n", " # Level weights\n", " self.register_parameter('_lw', nn.Parameter(torch.ones(depth) / depth))\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", " h = self._proj_in(x).view(B, self._nbase, self._fdim)\n", "\n", " outs, infos = [], []\n", " for lv in self._levels:\n", " h, info = lv(h)\n", " outs.append(h)\n", " infos.append(info)\n", "\n", " w = F.softmax(self._lw, dim=0)\n", " pooled = []\n", " for i, (o, inf) in enumerate(zip(outs, infos)):\n", " a = inf['attn']\n", " wm = (o * a.unsqueeze(-1)).sum(1) / (a.sum(1, keepdim=True) + 1e-8)\n", " pooled.append(w[i] * wm)\n", "\n", " return sum(pooled), {'sizes': self._sizes, 'potentials': self._potentials, 'infos': infos, 'weights': w.detach()}\n", "\n", "\n", "# ============================================================================\n", "# MODEL\n", "# ============================================================================\n", "\n", "class FashionGeoSimplex(nn.Module):\n", " def __init__(self, nbase=4, fdim=32, depth=5, edim=8, ncpx=2):\n", " super().__init__()\n", "\n", " self._stem = nn.Sequential(\n", " nn.Conv2d(1, 32, 3, padding=1), nn.BatchNorm2d(32), nn.GELU(),\n", " nn.Conv2d(32, 64, 3, stride=2, padding=1), nn.BatchNorm2d(64), nn.GELU(),\n", " nn.Conv2d(64, 128, 3, stride=2, padding=1), nn.BatchNorm2d(128), nn.GELU(),\n", " )\n", " self._pool = nn.AdaptiveAvgPool2d(1)\n", " self._proj = nn.Linear(128, fdim)\n", "\n", " cpx_list = []\n", " for i in range(ncpx):\n", " c = GeoComplex(nbase, fdim, depth, edim)\n", " cpx_list.append(c)\n", " print(f\"Complex {i+1}:\")\n", " print(f\" Sizes: {c._sizes}\")\n", " print(f\" Potentials per level:\")\n", " for k, pot in enumerate(c._potentials):\n", " print(f\" k={k+1}: C({c._sizes[k]},{k+2}) = {pot:,} potential {k+1}-simplices\")\n", " self._cpx = nn.ModuleList(cpx_list)\n", " self._norms = nn.ModuleList([nn.LayerNorm(fdim) for _ in range(ncpx)])\n", "\n", " self._head = nn.Linear(fdim, 10)\n", "\n", " def forward(self, x, ret_diag=False):\n", " h = self._stem(x)\n", " h = self._pool(h).flatten(1)\n", " h = self._proj(h)\n", "\n", " diags = []\n", " validity_loss = 0\n", " for cpx, norm in zip(self._cpx, self._norms):\n", " out, diag = cpx(h)\n", " diags.append(diag)\n", " for inf in diag['infos']:\n", " validity_loss = validity_loss + F.relu(-inf['vol2']).mean()\n", " h = norm(h + out)\n", "\n", " logits = self._head(h)\n", "\n", " if ret_diag:\n", " return logits, diags, validity_loss\n", " return logits, validity_loss\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = FashionGeoSimplex(\n", " nbase=4,\n", " fdim=32,\n", " depth=5,\n", " edim=8,\n", " ncpx=2\n", " ).to(device)\n", "\n", " print(f\"\\nTotal params: {sum(p.numel() for p in model.parameters()):,}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 100)\n", "\n", " for ep in range(30):\n", " model.train()\n", " lsum, vsum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, vloss = model(img)\n", " ce = F.cross_entropy(logits, lab)\n", " loss = ce + 0.1 * vloss\n", " loss.backward()\n", " opt.step()\n", "\n", " lsum += ce.item() * img.size(0)\n", " vsum += vloss.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", " tr_loss = lsum / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " samp = None\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, diag, _ = model(img, ret_diag=True)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " if samp is None:\n", " samp = diag\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " lw = samp[0]['weights'].cpu().numpy()\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"Ep {ep+1:2d} | CE {tr_loss:.4f} | Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%}\")\n", " print(f\" | Weights: {['%.3f' % w for w in lw]}\")\n", " for inf in samp[0]['infos']:\n", " k = inf['order']\n", " v = inf['vol2'].mean().item()\n", " vld = inf['valid'].float().mean().item()\n", " att = inf['attn'].mean().item()\n", " d = inf['d2'].mean().item() if inf['d2'].numel() > 0 else 0\n", " pot = inf['potential']\n", " print(f\" k={k}: potential={pot:>10,} vol²={v:.2e} valid={vld:5.1%} attn={att:.3f} d²={d:.4f}\")\n", "\n", " print(\"=\" * 100)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "cellView": "form", "id": "NwBIvJu0VDlc", "outputId": "87067b96-dc52-4aa5-8115-4a217467cf31" }, "execution_count": 6, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "Complex 1:\n", " Sizes: [4, 8, 16, 32, 64, 128]\n", " Potentials per level:\n", " k=1: C(4,2) = 6 potential 1-simplices\n", " k=2: C(8,3) = 56 potential 2-simplices\n", " k=3: C(16,4) = 1,820 potential 3-simplices\n", " k=4: C(32,5) = 201,376 potential 4-simplices\n", " k=5: C(64,6) = 74,974,368 potential 5-simplices\n", "Complex 2:\n", " Sizes: [4, 8, 16, 32, 64, 128]\n", " Potentials per level:\n", " k=1: C(4,2) = 6 potential 1-simplices\n", " k=2: C(8,3) = 56 potential 2-simplices\n", " k=3: C(16,4) = 1,820 potential 3-simplices\n", " k=4: C(32,5) = 201,376 potential 4-simplices\n", " k=5: C(64,6) = 74,974,368 potential 5-simplices\n", "\n", "Total params: 22,661,428\n", "\n", "Training...\n", "====================================================================================================\n", "Ep 1 | CE 0.6211 | Tr 79.52% | Te 83.99% | Best 83.99%\n", " | Weights: ['0.213', '0.201', '0.201', '0.200', '0.184']\n", " k=1: potential= 6 vol²=8.85e-01 valid=100.0% attn=1.000 d²=0.8848\n", " k=2: potential= 56 vol²=1.95e-01 valid=100.0% attn=1.000 d²=1.0221\n", " k=3: potential= 1,820 vol²=1.29e-02 valid=100.0% attn=1.000 d²=0.9890\n", " k=4: potential= 201,376 vol²=5.25e-04 valid=100.0% attn=1.000 d²=1.0006\n", " k=5: potential=74,974,368 vol²=1.22e-05 valid=100.0% attn=1.000 d²=1.0023\n", "Ep 6 | CE 0.2642 | Tr 90.43% | Te 86.53% | Best 88.26%\n", " | Weights: ['0.209', '0.193', '0.183', '0.238', '0.178']\n", " k=1: potential= 6 vol²=2.19e-01 valid=100.0% attn=1.000 d²=0.2186\n", " k=2: potential= 56 vol²=1.85e-01 valid=100.0% attn=1.000 d²=1.0016\n", " k=3: potential= 1,820 vol²=1.27e-02 valid=100.0% attn=1.000 d²=0.9950\n", " k=4: potential= 201,376 vol²=2.90e-04 valid=100.0% attn=1.000 d²=0.9341\n", " k=5: potential=74,974,368 vol²=2.38e-06 valid=100.0% attn=0.915 d²=0.9012\n", "Ep 11 | CE 0.1861 | Tr 93.23% | Te 89.94% | Best 89.94%\n", " | Weights: ['0.202', '0.180', '0.165', '0.268', '0.185']\n", " k=1: potential= 6 vol²=9.29e-02 valid=100.0% attn=1.000 d²=0.0929\n", " k=2: potential= 56 vol²=1.98e-01 valid=100.0% attn=1.000 d²=1.0434\n", " k=3: potential= 1,820 vol²=1.31e-02 valid=100.0% attn=1.000 d²=1.0180\n", " k=4: potential= 201,376 vol²=1.26e-04 valid=100.0% attn=1.000 d²=1.1049\n", " k=5: potential=74,974,368 vol²=3.15e-08 valid=100.0% attn=0.508 d²=0.6983\n", "Ep 16 | CE 0.1142 | Tr 95.81% | Te 89.96% | Best 90.12%\n", " | Weights: ['0.203', '0.175', '0.157', '0.279', '0.186']\n", " k=1: potential= 6 vol²=8.39e-02 valid=100.0% attn=1.000 d²=0.0839\n", " k=2: potential= 56 vol²=2.04e-01 valid=100.0% attn=1.000 d²=1.0218\n", " k=3: potential= 1,820 vol²=1.30e-02 valid=100.0% attn=1.000 d²=1.0185\n", " k=4: potential= 201,376 vol²=1.72e-05 valid=100.0% attn=1.000 d²=1.1952\n", " k=5: potential=74,974,368 vol²=3.01e-08 valid=100.0% attn=0.508 d²=0.7236\n", "Ep 21 | CE 0.0577 | Tr 98.02% | Te 89.94% | Best 90.20%\n", " | Weights: ['0.205', '0.177', '0.154', '0.279', '0.185']\n", " k=1: potential= 6 vol²=8.29e-02 valid=100.0% attn=1.000 d²=0.0829\n", " k=2: potential= 56 vol²=2.08e-01 valid=100.0% attn=1.000 d²=1.0266\n", " k=3: potential= 1,820 vol²=1.30e-02 valid=100.0% attn=1.000 d²=1.0203\n", " k=4: potential= 201,376 vol²=8.19e-06 valid=100.0% attn=0.979 d²=1.2106\n", " k=5: potential=74,974,368 vol²=3.26e-08 valid=100.0% attn=0.508 d²=0.7480\n", "Ep 26 | CE 0.0210 | Tr 99.36% | Te 90.23% | Best 90.23%\n", " | Weights: ['0.207', '0.182', '0.154', '0.275', '0.182']\n", " k=1: potential= 6 vol²=6.87e-02 valid=100.0% attn=1.000 d²=0.0687\n", " k=2: potential= 56 vol²=1.84e-01 valid=100.0% attn=1.000 d²=0.9339\n", " k=3: potential= 1,820 vol²=1.36e-02 valid=100.0% attn=1.000 d²=1.0209\n", " k=4: potential= 201,376 vol²=1.38e-06 valid=100.0% attn=0.689 d²=1.1003\n", " k=5: potential=74,974,368 vol²=3.84e-08 valid=100.0% attn=0.510 d²=0.7503\n", "Ep 30 | CE 0.0113 | Tr 99.71% | Te 90.19% | Best 90.23%\n", " | Weights: ['0.207', '0.182', '0.154', '0.274', '0.182']\n", " k=1: potential= 6 vol²=6.02e-02 valid=100.0% attn=1.000 d²=0.0602\n", " k=2: potential= 56 vol²=1.92e-01 valid=100.0% attn=1.000 d²=0.9515\n", " k=3: potential= 1,820 vol²=1.37e-02 valid=100.0% attn=1.000 d²=1.0220\n", " k=4: potential= 201,376 vol²=1.98e-06 valid=100.0% attn=0.754 d²=1.0963\n", " k=5: potential=74,974,368 vol²=4.16e-08 valid=100.0% attn=0.510 d²=0.7500\n", "====================================================================================================\n", "Best: 90.23%\n" ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork ViT - Full K-Simplex Hierarchy\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", " det = torch.linalg.det(cm)\n", "\n", " vol2 = self._prefactor * det\n", " valid = vol2 > 1e-8\n", "\n", " return d2_pairs, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC LEVEL (k-simplex layer with factory template)\n", "# ============================================================================\n", "\n", "class GeoLevel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, order, nin, nout, fdim, edim):\n", " super().__init__()\n", " self._order = order\n", " self._nin = nin\n", " self._nout = nout\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._nv = order + 1\n", "\n", " self._potential = math.comb(nin, self._nv) if nin >= self._nv else 0\n", "\n", " self._cm = CMValidator(order)\n", "\n", " # Factory template - guaranteed valid\n", " factory = SimplexFactory(k=order, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template)\n", "\n", " # Vertex selection: soft-select nv vertices from nin inputs\n", " _sel = torch.randn(nout, self._nv, nin) * 0.1\n", " self.register_parameter('_W_select', nn.Parameter(_sel))\n", "\n", " # Deformation network\n", " self._deform = nn.Sequential(\n", " nn.Linear(fdim, fdim),\n", " nn.LayerNorm(fdim),\n", " nn.GELU(),\n", " nn.Linear(fdim, self._nv * edim),\n", " )\n", "\n", " # Feature MLP\n", " self._mlp = nn.Sequential(\n", " nn.Linear(fdim, fdim * 2),\n", " nn.LayerNorm(fdim * 2),\n", " nn.GELU(),\n", " nn.Linear(fdim * 2, fdim),\n", " )\n", "\n", " # Geometry gate\n", " gate_in = self._cm._npairs + 1\n", " self._gate = nn.Sequential(\n", " nn.Linear(gate_in, fdim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Residual projection\n", " if nin != nout:\n", " self._proj = nn.Linear(nin * fdim, nout * fdim)\n", " else:\n", " self._proj = None\n", "\n", " def forward(self, x):\n", " \"\"\"x: [B, nin, fdim]\"\"\"\n", " B = x.shape[0]\n", "\n", " sel = F.softmax(self._W_select, dim=-1)\n", " picked = torch.einsum('ovn,bnf->bovf', sel, x)\n", " agg = picked.mean(dim=2)\n", "\n", " # Deformation from template\n", " deform = self._deform(agg).view(B, self._nout, self._nv, self._edim)\n", " template = self._template.unsqueeze(0).unsqueeze(0).expand(B, self._nout, -1, -1)\n", " verts = template + self.BASE_DEFORM * deform\n", "\n", " # CM validation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " # Geometry gating\n", " d2_norm = d2 / (d2.mean(dim=-1, keepdim=True) + 1e-8)\n", " geo = torch.cat([d2_norm, vol2.unsqueeze(-1) / (vol2.mean() + 1e-8)], dim=-1)\n", " gate = self._gate(geo)\n", "\n", " out = self._mlp(agg) * gate\n", "\n", " # Validity-only attenuation\n", " attn = torch.sigmoid(vol2 * 1e6)\n", " out = out * attn.unsqueeze(-1)\n", "\n", " # Residual\n", " if self._proj is not None:\n", " res = self._proj(x.flatten(1)).view(B, self._nout, self._fdim)\n", " else:\n", " res = x\n", " out = out + 0.1 * res\n", "\n", " return out, {\n", " 'd2': d2, 'vol2': vol2, 'valid': valid, 'attn': attn,\n", " 'order': self._order, 'potential': self._potential,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC COMPLEX (k=1→2→3→... with exponential growth)\n", "# ============================================================================\n", "\n", "class GeoComplex(nn.Module):\n", " def __init__(self, nbase, fdim, depth, edim):\n", " super().__init__()\n", " self._nbase = nbase\n", " self._fdim = fdim\n", " self._depth = depth\n", "\n", " # EXPONENTIAL GROWTH\n", " sizes = [nbase]\n", " for _ in range(depth):\n", " sizes.append(sizes[-1] * 2)\n", " self._sizes = sizes\n", "\n", " # Potentials per level\n", " potentials = []\n", " for i in range(depth):\n", " k = i + 1\n", " nv = k + 1\n", " nin = sizes[i]\n", " pot = math.comb(nin, nv) if nin >= nv else 0\n", " potentials.append(pot)\n", " self._potentials = potentials\n", "\n", " # Input projection\n", " self._proj_in = nn.Linear(fdim, nbase * fdim)\n", "\n", " # Build levels k=1, 2, 3, ...\n", " level_list = []\n", " for i in range(depth):\n", " lv = GeoLevel(\n", " order=i + 1,\n", " nin=sizes[i],\n", " nout=sizes[i + 1],\n", " fdim=fdim,\n", " edim=edim\n", " )\n", " level_list.append(lv)\n", " self._levels = nn.ModuleList(level_list)\n", "\n", " # Level weights\n", " self.register_parameter('_lw', nn.Parameter(torch.ones(depth) / depth))\n", "\n", " def forward(self, x):\n", " \"\"\"x: [B, fdim]\"\"\"\n", " B = x.shape[0]\n", " h = self._proj_in(x).view(B, self._nbase, self._fdim)\n", "\n", " outs, infos = [], []\n", " for lv in self._levels:\n", " h, info = lv(h)\n", " outs.append(h)\n", " infos.append(info)\n", "\n", " # Weighted pool across levels\n", " w = F.softmax(self._lw, dim=0)\n", " pooled = []\n", " for i, (o, inf) in enumerate(zip(outs, infos)):\n", " a = inf['attn']\n", " wm = (o * a.unsqueeze(-1)).sum(1) / (a.sum(1, keepdim=True) + 1e-8)\n", " pooled.append(w[i] * wm)\n", "\n", " return sum(pooled), {\n", " 'sizes': self._sizes,\n", " 'potentials': self._potentials,\n", " 'infos': infos,\n", " 'weights': w.detach(),\n", " }\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC PATCH EMBEDDING (each patch → k-simplex hierarchy)\n", "# ============================================================================\n", "\n", "class GeoPatchEmbed(nn.Module):\n", " \"\"\"\n", " Each patch processed through full k-simplex hierarchy.\n", " \"\"\"\n", " def __init__(self, img_size=28, patch_size=7, in_chans=1, embed_dim=64,\n", " nbase=4, depth=4, edim=8):\n", " super().__init__()\n", " self.img_size = img_size\n", " self.patch_size = patch_size\n", " self.num_patches = (img_size // patch_size) ** 2\n", " self.patch_dim = patch_size * patch_size * in_chans\n", " self.embed_dim = embed_dim\n", "\n", " # Patch to feature dim\n", " self._patch_proj = nn.Sequential(\n", " nn.Linear(self.patch_dim, embed_dim),\n", " nn.LayerNorm(embed_dim),\n", " nn.GELU(),\n", " )\n", "\n", " # Geometric complex per patch\n", " self._complex = GeoComplex(nbase, embed_dim, depth, edim)\n", "\n", " # CLS token\n", " self.cls_token = nn.Parameter(torch.randn(1, 1, embed_dim) * 0.02)\n", "\n", " # Position embedding\n", " self.pos_embed = nn.Parameter(torch.randn(1, self.num_patches + 1, embed_dim) * 0.02)\n", "\n", " def forward(self, x):\n", " \"\"\"x: [B, C, H, W]\"\"\"\n", " B, C, H, W = x.shape\n", "\n", " # Extract patches\n", " patches = x.unfold(2, self.patch_size, self.patch_size).unfold(3, self.patch_size, self.patch_size)\n", " patches = patches.contiguous().view(B, C, -1, self.patch_size * self.patch_size)\n", " patches = patches.permute(0, 2, 1, 3).contiguous().view(B, self.num_patches, -1)\n", "\n", " # Project patches\n", " patch_feats = self._patch_proj(patches) # [B, P, embed_dim]\n", "\n", " # Process each patch through geometric complex\n", " # Reshape for batch processing: [B*P, embed_dim]\n", " flat = patch_feats.view(B * self.num_patches, self.embed_dim)\n", " geo_out, geo_info = self._complex(flat)\n", " geo_out = geo_out.view(B, self.num_patches, self.embed_dim)\n", "\n", " # Add CLS token\n", " cls = self.cls_token.expand(B, -1, -1)\n", " tokens = torch.cat([cls, geo_out], dim=1)\n", "\n", " # Add position embedding\n", " tokens = tokens + self.pos_embed\n", "\n", " return tokens, geo_info\n", "\n", "\n", "# ============================================================================\n", "# 4-SIMPLEX ATTENTION (sequence stream)\n", "# ============================================================================\n", "\n", "class Simplex4Attention(nn.Module):\n", " \"\"\"\n", " 4-simplex attention: each head soft-selects 5 tokens to form a 4-simplex.\n", " CM-validated geometry determines attention.\n", " \"\"\"\n", " def __init__(self, embed_dim, num_heads=4, edim=8):\n", " super().__init__()\n", " self.embed_dim = embed_dim\n", " self.num_heads = num_heads\n", " self.head_dim = embed_dim // num_heads\n", " self.edim = edim\n", "\n", " self._k = 4\n", " self._nv = 5\n", "\n", " self._cm = CMValidator(self._k)\n", "\n", " # Factory template\n", " factory = SimplexFactory(k=self._k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template)\n", "\n", " # Token to vertex selection scores (per head)\n", " self._to_vertex_scores = nn.Linear(embed_dim, num_heads * self._nv)\n", "\n", " # Token to coordinate for deformation\n", " self._to_coord = nn.Linear(embed_dim, edim)\n", "\n", " # Value projection\n", " self._to_v = nn.Linear(embed_dim, embed_dim)\n", "\n", " # Geometry to output gate\n", " geom_dim = self._cm._npairs + 1 # 10 + 1 = 11\n", " self._geo_gate = nn.Sequential(\n", " nn.Linear(geom_dim, num_heads),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Output projection\n", " self._out = nn.Linear(embed_dim, embed_dim)\n", "\n", " self._deform_scale = 0.05\n", "\n", " def forward(self, x):\n", " \"\"\"x: [B, N, D]\"\"\"\n", " B, N, D = x.shape\n", " H = self.num_heads\n", "\n", " # Vertex selection: [B, N, H*5] -> [B, H, 5, N]\n", " vs = self._to_vertex_scores(x).view(B, N, H, self._nv).permute(0, 2, 3, 1)\n", " vw = F.softmax(vs, dim=-1) # [B, H, 5, N]\n", "\n", " # Coordinates\n", " coords = self._to_coord(x) # [B, N, edim]\n", "\n", " # Select coordinates: [B, H, 5, edim]\n", " sel_coords = torch.einsum('bhvn,bne->bhve', vw, coords)\n", "\n", " # Deform template\n", " template = self._template.unsqueeze(0).unsqueeze(0)\n", " verts = template + self._deform_scale * sel_coords\n", "\n", " # CM validation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " # Validity per head\n", " head_valid = torch.sigmoid(vol2 * 1e6) # [B, H]\n", "\n", " # Geometry gate\n", " geo = torch.cat([d2, vol2.unsqueeze(-1)], dim=-1)\n", " geo_gate = self._geo_gate(geo).mean(dim=-1) # [B, H]\n", " head_weight = geo_gate * head_valid\n", " head_weight = head_weight / (head_weight.sum(dim=-1, keepdim=True) + 1e-8)\n", "\n", " # Values\n", " v = self._to_v(x).view(B, N, H, self.head_dim).permute(0, 2, 1, 3) # [B, H, N, hd]\n", "\n", " # Attention from vertex weights (average over 5 positions)\n", " attn = vw.mean(dim=2) # [B, H, N]\n", "\n", " # Aggregate\n", " out = torch.einsum('bhn,bhnd->bhd', attn, v) # [B, H, hd]\n", " out = out * head_weight.unsqueeze(-1)\n", " out = out.reshape(B, D)\n", "\n", " # Project and broadcast\n", " out = self._out(out).unsqueeze(1).expand(-1, N, -1)\n", "\n", " return out, {\n", " 'd2': d2, 'vol2': vol2, 'valid': valid,\n", " 'head_weight': head_weight, 'vertex_weights': vw,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# TRANSFORMER BLOCK\n", "# ============================================================================\n", "\n", "class GeoTransformerBlock(nn.Module):\n", " def __init__(self, embed_dim, num_heads, edim, mlp_ratio=4.0):\n", " super().__init__()\n", " self.norm1 = nn.LayerNorm(embed_dim)\n", " self.attn = Simplex4Attention(embed_dim, num_heads, edim)\n", " self.norm2 = nn.LayerNorm(embed_dim)\n", " self.mlp = nn.Sequential(\n", " nn.Linear(embed_dim, int(embed_dim * mlp_ratio)),\n", " nn.GELU(),\n", " nn.Linear(int(embed_dim * mlp_ratio), embed_dim),\n", " )\n", "\n", " def forward(self, x):\n", " attn_out, attn_info = self.attn(self.norm1(x))\n", " x = x + attn_out\n", " x = x + self.mlp(self.norm2(x))\n", " return x, attn_info\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " embed_dim=64,\n", " nbase=4,\n", " geo_depth=4, # k=1,2,3,4 hierarchy\n", " attn_depth=4, # transformer blocks\n", " num_heads=4,\n", " edim=8,\n", " ):\n", " super().__init__()\n", "\n", " # Geometric patch embedding with k-simplex hierarchy\n", " self.patch_embed = GeoPatchEmbed(\n", " img_size=img_size,\n", " patch_size=patch_size,\n", " in_chans=in_chans,\n", " embed_dim=embed_dim,\n", " nbase=nbase,\n", " depth=geo_depth,\n", " edim=edim,\n", " )\n", "\n", " # Transformer blocks with 4-simplex attention\n", " self.blocks = nn.ModuleList([\n", " GeoTransformerBlock(embed_dim, num_heads, edim)\n", " for _ in range(attn_depth)\n", " ])\n", "\n", " self.norm = nn.LayerNorm(embed_dim)\n", " self.head = nn.Linear(embed_dim, num_classes)\n", "\n", " # Print architecture\n", " print(f\"\\nGeometricPatchworkViT:\")\n", " print(f\" Patches: {self.patch_embed.num_patches} × {patch_size}×{patch_size}\")\n", " print(f\" K-simplex hierarchy per patch:\")\n", " print(f\" Sizes: {self.patch_embed._complex._sizes}\")\n", " print(f\" Potentials: {self.patch_embed._complex._potentials}\")\n", " print(f\" 4-simplex attention: {attn_depth} blocks × {num_heads} heads\")\n", "\n", " def forward(self, x, ret_diag=False):\n", " # Patch embedding through k-simplex hierarchy\n", " tokens, patch_geo_info = self.patch_embed(x)\n", "\n", " # Transformer with 4-simplex attention\n", " block_infos = []\n", " validity_loss = 0\n", "\n", " for block in self.blocks:\n", " tokens, attn_info = block(tokens)\n", " block_infos.append(attn_info)\n", " validity_loss = validity_loss + F.relu(-attn_info['vol2']).mean()\n", "\n", " # Add patch hierarchy validity loss\n", " for inf in patch_geo_info['infos']:\n", " validity_loss = validity_loss + F.relu(-inf['vol2']).mean()\n", "\n", " # CLS for classification\n", " tokens = self.norm(tokens)\n", " cls = tokens[:, 0]\n", " logits = self.head(cls)\n", "\n", " if ret_diag:\n", " return logits, {'patch': patch_geo_info, 'blocks': block_infos}, validity_loss\n", " return logits, validity_loss\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"Building model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=28,\n", " patch_size=2,\n", " in_chans=1,\n", " num_classes=10,\n", " embed_dim=8,\n", " nbase=4,\n", " geo_depth=4, # k=1→2→3→4 hierarchy\n", " attn_depth=4, # 4 transformer blocks\n", " num_heads=4,\n", " edim=8,\n", " ).to(device)\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\"Total params: {params:,}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 115)\n", "\n", " for ep in range(30):\n", " model.train()\n", " lsum, vsum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, vloss = model(img)\n", " ce = F.cross_entropy(logits, lab)\n", " loss = ce + 0.1 * vloss\n", " loss.backward()\n", " opt.step()\n", "\n", " lsum += ce.item() * img.size(0)\n", " vsum += vloss.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", " tr_loss = lsum / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " samp = None\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, diag, _ = model(img, ret_diag=True)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " if samp is None:\n", " samp = diag\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"\\nEp {ep+1:2d} | CE {tr_loss:.4f} | Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%}\")\n", "\n", " # Patch hierarchy stats\n", " pw = samp['patch']['weights'].cpu().numpy()\n", " print(f\" Patch k-hierarchy weights: {['%.3f' % w for w in pw]}\")\n", " for inf in samp['patch']['infos']:\n", " k = inf['order']\n", " v = inf['vol2'].mean().item()\n", " vld = inf['valid'].float().mean().item()\n", " att = inf['attn'].mean().item()\n", " d = inf['d2'].mean().item()\n", " pot = inf['potential']\n", " print(f\" k={k}: pot={pot:>8,} vol²={v:.2e} valid={vld:5.1%} attn={att:.3f} d²={d:.4f}\")\n", "\n", " # Attention block stats\n", " for i, bi in enumerate(samp['blocks']):\n", " v = bi['vol2'].mean().item()\n", " vld = bi['valid'].float().mean().item()\n", " hw = bi['head_weight'].mean(dim=0).cpu().numpy()\n", " print(f\" Block {i+1} 4-simplex: vol²={v:.2e} valid={vld:5.1%} heads={['%.2f' % h for h in hw]}\")\n", "\n", " print(\"=\" * 115)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "cellView": "form", "id": "GUzM109PYxIE", "outputId": "a57d14dc-3503-46a5-b01a-c9f70ba0a1c0" }, "execution_count": 8, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "Building model...\n", "\n", "GeometricPatchworkViT:\n", " Patches: 16 × 7×7\n", " K-simplex hierarchy per patch:\n", " Sizes: [4, 8, 16, 32, 64]\n", " Potentials: [6, 56, 1820, 201376]\n", " 4-simplex attention: 4 blocks × 4 heads\n", "Total params: 11,451,118\n", "\n", "Training...\n", "===================================================================================================================\n", "\n", "Ep 1 | CE 0.8111 | Tr 70.79% | Te 80.51% | Best 80.51%\n", " Patch k-hierarchy weights: ['0.287', '0.260', '0.238', '0.215']\n", " k=1: pot= 6 vol²=8.99e-01 valid=100.0% attn=1.000 d²=0.8991\n", " k=2: pot= 56 vol²=1.84e-01 valid=100.0% attn=1.000 d²=0.9894\n", " k=3: pot= 1,820 vol²=1.21e-02 valid=100.0% attn=1.000 d²=0.9959\n", " k=4: pot= 201,376 vol²=5.20e-04 valid=100.0% attn=1.000 d²=1.0052\n", " Block 1 4-simplex: vol²=5.32e-04 valid=100.0% heads=['0.25', '0.25', '0.25', '0.25']\n", " Block 2 4-simplex: vol²=5.84e-04 valid=100.0% heads=['0.25', '0.25', '0.24', '0.26']\n", " Block 3 4-simplex: vol²=5.32e-04 valid=100.0% heads=['0.25', '0.25', '0.25', '0.25']\n", " Block 4 4-simplex: vol²=5.68e-04 valid=100.0% heads=['0.25', '0.26', '0.24', '0.25']\n", "\n", "Ep 6 | CE 0.3254 | Tr 88.00% | Te 86.47% | Best 86.47%\n", " Patch k-hierarchy weights: ['0.341', '0.247', '0.237', '0.174']\n", " k=1: pot= 6 vol²=7.11e-01 valid=100.0% attn=1.000 d²=0.7110\n", " k=2: pot= 56 vol²=1.39e-01 valid=100.0% attn=1.000 d²=0.9200\n", " k=3: pot= 1,820 vol²=8.78e-03 valid=100.0% attn=1.000 d²=0.9587\n", " k=4: pot= 201,376 vol²=3.98e-04 valid=100.0% attn=1.000 d²=0.9743\n", " Block 1 4-simplex: vol²=5.63e-04 valid=100.0% heads=['0.26', '0.24', '0.24', '0.26']\n", " Block 2 4-simplex: vol²=8.67e-04 valid=100.0% heads=['0.23', '0.23', '0.18', '0.36']\n", " Block 3 4-simplex: vol²=5.51e-04 valid=100.0% heads=['0.26', '0.25', '0.24', '0.25']\n", " Block 4 4-simplex: vol²=8.45e-04 valid=100.0% heads=['0.24', '0.34', '0.23', '0.19']\n", "\n", "Ep 11 | CE 0.2659 | Tr 90.07% | Te 88.09% | Best 88.09%\n", " Patch k-hierarchy weights: ['0.387', '0.240', '0.228', '0.146']\n", " k=1: pot= 6 vol²=4.17e-01 valid=100.0% attn=1.000 d²=0.4174\n", " k=2: pot= 56 vol²=9.97e-02 valid=100.0% attn=1.000 d²=0.7037\n", " k=3: pot= 1,820 vol²=8.29e-03 valid=100.0% attn=1.000 d²=0.8660\n", " k=4: pot= 201,376 vol²=4.20e-04 valid=100.0% attn=1.000 d²=0.9698\n", " Block 1 4-simplex: vol²=5.85e-04 valid=100.0% heads=['0.26', '0.23', '0.25', '0.25']\n", " Block 2 4-simplex: vol²=9.18e-04 valid=100.0% heads=['0.22', '0.25', '0.16', '0.37']\n", " Block 3 4-simplex: vol²=6.01e-04 valid=100.0% heads=['0.27', '0.24', '0.22', '0.26']\n", " Block 4 4-simplex: vol²=1.26e-03 valid=100.0% heads=['0.16', '0.44', '0.20', '0.20']\n", "\n", "Ep 16 | CE 0.2147 | Tr 91.95% | Te 88.97% | Best 88.97%\n", " Patch k-hierarchy weights: ['0.417', '0.237', '0.219', '0.127']\n", " k=1: pot= 6 vol²=2.79e-01 valid=100.0% attn=1.000 d²=0.2792\n", " k=2: pot= 56 vol²=5.88e-02 valid=100.0% attn=1.000 d²=0.6106\n", " k=3: pot= 1,820 vol²=7.82e-03 valid=100.0% attn=1.000 d²=0.7911\n", " k=4: pot= 201,376 vol²=2.37e-04 valid=100.0% attn=1.000 d²=0.8056\n", " Block 1 4-simplex: vol²=5.91e-04 valid=100.0% heads=['0.26', '0.23', '0.25', '0.25']\n", " Block 2 4-simplex: vol²=9.90e-04 valid=100.0% heads=['0.21', '0.29', '0.14', '0.37']\n", " Block 3 4-simplex: vol²=6.33e-04 valid=100.0% heads=['0.25', '0.25', '0.25', '0.25']\n", " Block 4 4-simplex: vol²=1.48e-03 valid=100.0% heads=['0.14', '0.46', '0.19', '0.21']\n", "\n", "Ep 21 | CE 0.1570 | Tr 94.20% | Te 90.06% | Best 90.06%\n", " Patch k-hierarchy weights: ['0.421', '0.237', '0.217', '0.125']\n", " k=1: pot= 6 vol²=2.33e-01 valid=100.0% attn=1.000 d²=0.2326\n", " k=2: pot= 56 vol²=4.50e-02 valid=100.0% attn=1.000 d²=0.5084\n", " k=3: pot= 1,820 vol²=5.98e-03 valid=100.0% attn=1.000 d²=0.6563\n", " k=4: pot= 201,376 vol²=2.94e-04 valid=100.0% attn=1.000 d²=0.7931\n", " Block 1 4-simplex: vol²=6.10e-04 valid=100.0% heads=['0.26', '0.23', '0.26', '0.25']\n", " Block 2 4-simplex: vol²=9.67e-04 valid=100.0% heads=['0.21', '0.29', '0.14', '0.36']\n", " Block 3 4-simplex: vol²=6.64e-04 valid=100.0% heads=['0.26', '0.24', '0.24', '0.25']\n", " Block 4 4-simplex: vol²=1.51e-03 valid=100.0% heads=['0.13', '0.46', '0.20', '0.20']\n", "\n", "Ep 26 | CE 0.1012 | Tr 96.44% | Te 89.76% | Best 90.06%\n", " Patch k-hierarchy weights: ['0.413', '0.238', '0.219', '0.130']\n", " k=1: pot= 6 vol²=2.01e-01 valid=100.0% attn=1.000 d²=0.2010\n", " k=2: pot= 56 vol²=3.48e-02 valid=100.0% attn=1.000 d²=0.4850\n", " k=3: pot= 1,820 vol²=5.92e-03 valid=100.0% attn=1.000 d²=0.5973\n", " k=4: pot= 201,376 vol²=2.71e-04 valid=100.0% attn=1.000 d²=0.7089\n", " Block 1 4-simplex: vol²=5.99e-04 valid=100.0% heads=['0.26', '0.23', '0.26', '0.25']\n", " Block 2 4-simplex: vol²=9.32e-04 valid=100.0% heads=['0.22', '0.30', '0.13', '0.35']\n", " Block 3 4-simplex: vol²=7.11e-04 valid=100.0% heads=['0.25', '0.25', '0.26', '0.25']\n", " Block 4 4-simplex: vol²=1.49e-03 valid=100.0% heads=['0.14', '0.43', '0.20', '0.24']\n", "\n", "Ep 30 | CE 0.0801 | Tr 97.38% | Te 89.91% | Best 90.06%\n", " Patch k-hierarchy weights: ['0.411', '0.238', '0.220', '0.131']\n", " k=1: pot= 6 vol²=1.93e-01 valid=100.0% attn=1.000 d²=0.1933\n", " k=2: pot= 56 vol²=3.37e-02 valid=100.0% attn=1.000 d²=0.4832\n", " k=3: pot= 1,820 vol²=5.55e-03 valid=100.0% attn=1.000 d²=0.5836\n", " k=4: pot= 201,376 vol²=2.61e-04 valid=100.0% attn=0.999 d²=0.6983\n", " Block 1 4-simplex: vol²=6.01e-04 valid=100.0% heads=['0.26', '0.23', '0.26', '0.25']\n", " Block 2 4-simplex: vol²=9.30e-04 valid=100.0% heads=['0.22', '0.30', '0.13', '0.35']\n", " Block 3 4-simplex: vol²=7.22e-04 valid=100.0% heads=['0.25', '0.25', '0.25', '0.25']\n", " Block 4 4-simplex: vol²=1.46e-03 valid=100.0% heads=['0.14', '0.42', '0.20', '0.24']\n", "===================================================================================================================\n", "Best: 90.06%\n" ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork ViT - Proper Simplex Patch Structure\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", " det = torch.linalg.det(cm)\n", "\n", " vol2 = self._prefactor * det\n", " valid = vol2 > 1e-8\n", "\n", " return d2_pairs, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# 3-SIMPLEX PATCH: Each patch IS a tetrahedron\n", "# ============================================================================\n", "\n", "class SimplexPatch(nn.Module):\n", " \"\"\"\n", " Each image patch is represented as a valid 3-simplex (tetrahedron).\n", "\n", " Structure per patch:\n", " - 4 vertices in R^edim (the tetrahedron)\n", " - 6 edge distances (d²)\n", " - 1 volume (vol²)\n", " - 4 vertex features in R^fdim\n", "\n", " The tetrahedron IS the patch representation, not a flattened summary.\n", " \"\"\"\n", " def __init__(self, patch_dim, fdim, edim):\n", " super().__init__()\n", " self._k = 3 # tetrahedron\n", " self._nv = 4\n", " self._fdim = fdim\n", " self._edim = edim\n", "\n", " self._cm = CMValidator(self._k)\n", "\n", " # Factory template\n", " factory = SimplexFactory(k=self._k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template) # [4, edim]\n", "\n", " # Patch pixels → 4 vertex features\n", " self._to_vert_feats = nn.Sequential(\n", " nn.Linear(patch_dim, fdim * 2),\n", " nn.LayerNorm(fdim * 2),\n", " nn.GELU(),\n", " nn.Linear(fdim * 2, self._nv * fdim),\n", " )\n", "\n", " # Vertex features → deformation of template\n", " self._to_deform = nn.Linear(fdim, edim)\n", "\n", " self._deform_scale = 0.05\n", "\n", " def forward(self, patches):\n", " \"\"\"\n", " patches: [B, P, patch_dim]\n", "\n", " Returns:\n", " verts: [B, P, 4, edim] - tetrahedron vertices per patch\n", " feats: [B, P, 4, fdim] - features at each vertex\n", " d2: [B, P, 6] - squared edge distances\n", " vol2: [B, P] - squared volume\n", " valid: [B, P] - validity mask\n", " \"\"\"\n", " B, P, _ = patches.shape\n", "\n", " # Patch → 4 vertex features\n", " vert_feats = self._to_vert_feats(patches) # [B, P, 4*fdim]\n", " vert_feats = vert_feats.view(B, P, self._nv, self._fdim) # [B, P, 4, fdim]\n", "\n", " # Vertex features → deformation\n", " deform = self._to_deform(vert_feats) # [B, P, 4, edim]\n", "\n", " # Apply to template\n", " template = self._template.unsqueeze(0).unsqueeze(0) # [1, 1, 4, edim]\n", " verts = template + self._deform_scale * deform # [B, P, 4, edim]\n", "\n", " # CM validation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " return verts, vert_feats, d2, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# 4-SIMPLEX ATTENTION: Operates on tetrahedra as vertices\n", "# ============================================================================\n", "\n", "class Simplex4Attention(nn.Module):\n", " \"\"\"\n", " 4-simplex attention over patch tetrahedra.\n", "\n", " Each patch is a tetrahedron. We select 5 patches to form a 4-simplex.\n", " The centroid of each tetrahedron becomes a vertex of the 4-simplex.\n", "\n", " This preserves the geometric structure:\n", " - 16 tetrahedra (patches) as input\n", " - Select 5 tetrahedra per head\n", " - Their centroids form a 4-simplex\n", " - CM validates the 4-simplex\n", " - Attention weights from geometry\n", " \"\"\"\n", " def __init__(self, fdim, edim, num_heads=4):\n", " super().__init__()\n", " self.fdim = fdim\n", " self.edim = edim\n", " self.num_heads = num_heads\n", " self.head_dim = fdim // num_heads\n", "\n", " self._k = 4 # 4-simplex\n", " self._nv = 5 # select 5 patches\n", "\n", " self._cm = CMValidator(self._k)\n", "\n", " # Factory template for 4-simplex\n", " factory = SimplexFactory(k=self._k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template) # [5, edim]\n", "\n", " # Selection scores: which 5 patches to select per head\n", " # Input: patch features (aggregated from vertices)\n", " self._to_select = nn.Linear(fdim, num_heads * self._nv)\n", "\n", " # Value projection (from vertex features)\n", " self._to_v = nn.Linear(fdim, fdim)\n", "\n", " # Geometry modulates output\n", " geom_dim = self._cm._npairs + 1 # 10 + 1\n", " self._geo_gate = nn.Sequential(\n", " nn.Linear(geom_dim, fdim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Output projection\n", " self._out = nn.Linear(fdim, fdim)\n", "\n", " self._deform_scale = 0.05\n", "\n", " def forward(self, patch_verts, patch_feats, patch_d2, patch_vol2, patch_valid):\n", " \"\"\"\n", " patch_verts: [B, P, 4, edim] - tetrahedron vertices per patch\n", " patch_feats: [B, P, 4, fdim] - vertex features per patch\n", " patch_d2: [B, P, 6] - edge distances per patch\n", " patch_vol2: [B, P] - volumes per patch\n", " patch_valid: [B, P] - validity per patch\n", "\n", " Returns:\n", " out: [B, P, fdim] - updated patch features\n", " info: dict\n", " \"\"\"\n", " B, P, _, _ = patch_verts.shape\n", " H = self.num_heads\n", "\n", " # Aggregate patch features (mean over 4 vertices)\n", " patch_agg = patch_feats.mean(dim=2) # [B, P, fdim]\n", "\n", " # Centroid of each tetrahedron\n", " centroids = patch_verts.mean(dim=2) # [B, P, edim]\n", "\n", " # Selection scores: [B, P, H * 5]\n", " sel_scores = self._to_select(patch_agg)\n", " sel_scores = sel_scores.view(B, P, H, self._nv).permute(0, 2, 3, 1) # [B, H, 5, P]\n", "\n", " # Soft-select 5 patches per head\n", " sel_weights = F.softmax(sel_scores, dim=-1) # [B, H, 5, P]\n", "\n", " # Gather centroids for selected patches: [B, H, 5, edim]\n", " sel_centroids = torch.einsum('bhvp,bpe->bhve', sel_weights, centroids)\n", "\n", " # Deform 4-simplex template based on selected centroids\n", " template = self._template.unsqueeze(0).unsqueeze(0) # [1, 1, 5, edim]\n", " simplex4_verts = template + self._deform_scale * sel_centroids # [B, H, 5, edim]\n", "\n", " # CM validation of 4-simplices\n", " d2_4, vol2_4, valid_4 = self._cm(simplex4_verts) # d2: [B,H,10], vol2: [B,H]\n", "\n", " # Validity attenuation per head\n", " head_valid = torch.sigmoid(vol2_4 * 1e6) # [B, H]\n", "\n", " # Geometry gate\n", " geo = torch.cat([d2_4, vol2_4.unsqueeze(-1)], dim=-1) # [B, H, 11]\n", "\n", " # Values from vertex features\n", " v = self._to_v(patch_feats) # [B, P, 4, fdim]\n", " v = v.mean(dim=2) # [B, P, fdim] - aggregate per patch\n", " v = v.view(B, P, H, self.head_dim).permute(0, 2, 1, 3) # [B, H, P, head_dim]\n", "\n", " # Attention weights from selection (average over 5 positions)\n", " attn = sel_weights.mean(dim=2) # [B, H, P]\n", " attn = attn * patch_valid.unsqueeze(1) # mask invalid patches\n", " attn = attn / (attn.sum(dim=-1, keepdim=True) + 1e-8)\n", "\n", " # Weighted sum of values\n", " out = torch.einsum('bhp,bhpd->bhd', attn, v) # [B, H, head_dim]\n", "\n", " # Apply head validity\n", " out = out * head_valid.unsqueeze(-1) # [B, H, head_dim]\n", " out = out.reshape(B, self.fdim) # [B, fdim]\n", "\n", " # Geometry gate per-patch (broadcast from 4-simplex geometry)\n", " geo_expanded = geo.mean(dim=1) # [B, 11] - average across heads\n", " gate = self._geo_gate(geo_expanded) # [B, fdim]\n", "\n", " out = self._out(out) * gate # [B, fdim]\n", "\n", " # Broadcast back to all patches (residual style)\n", " out = out.unsqueeze(1).expand(-1, P, -1) # [B, P, fdim]\n", "\n", " return out, {\n", " 'd2_3': patch_d2, # tetrahedron edges\n", " 'vol2_3': patch_vol2, # tetrahedron volumes\n", " 'valid_3': patch_valid, # tetrahedron validity\n", " 'd2_4': d2_4, # 4-simplex edges\n", " 'vol2_4': vol2_4, # 4-simplex volumes\n", " 'valid_4': valid_4, # 4-simplex validity\n", " 'sel_weights': sel_weights, # which patches selected\n", " 'head_valid': head_valid,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# TRANSFORMER BLOCK\n", "# ============================================================================\n", "\n", "class GeoTransformerBlock(nn.Module):\n", " def __init__(self, fdim, edim, num_heads, mlp_ratio=4.0):\n", " super().__init__()\n", " self.norm1_feats = nn.LayerNorm(fdim)\n", " self.attn = Simplex4Attention(fdim, edim, num_heads)\n", " self.norm2 = nn.LayerNorm(fdim)\n", " self.mlp = nn.Sequential(\n", " nn.Linear(fdim, int(fdim * mlp_ratio)),\n", " nn.GELU(),\n", " nn.Linear(int(fdim * mlp_ratio), fdim),\n", " )\n", "\n", " def forward(self, patch_verts, patch_feats, patch_d2, patch_vol2, patch_valid):\n", " \"\"\"\n", " Maintains full geometric structure throughout.\n", " \"\"\"\n", " B, P, nv, fdim = patch_feats.shape\n", "\n", " # Normalize features\n", " feats_norm = self.norm1_feats(patch_feats)\n", "\n", " # 4-simplex attention\n", " attn_out, attn_info = self.attn(\n", " patch_verts, feats_norm, patch_d2, patch_vol2, patch_valid\n", " )\n", "\n", " # Residual: add to all vertices of all patches\n", " patch_feats = patch_feats + attn_out.unsqueeze(2) # [B, P, 4, fdim]\n", "\n", " # MLP\n", " feats_flat = patch_feats.view(B * P * nv, fdim)\n", " feats_flat = self.norm2(feats_flat)\n", " feats_flat = feats_flat + self.mlp(feats_flat)\n", " patch_feats = feats_flat.view(B, P, nv, fdim)\n", "\n", " return patch_verts, patch_feats, patch_d2, patch_vol2, patch_valid, attn_info\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " fdim=64,\n", " edim=8,\n", " num_heads=4,\n", " depth=4,\n", " ):\n", " super().__init__()\n", "\n", " self.img_size = img_size\n", " self.patch_size = patch_size\n", " self.num_patches = (img_size // patch_size) ** 2\n", " self.patch_dim = patch_size * patch_size * in_chans\n", " self.fdim = fdim\n", " self.edim = edim\n", "\n", " # 3-simplex patch embedding\n", " self.patch_embed = SimplexPatch(self.patch_dim, fdim, edim)\n", "\n", " # Position embedding for tetrahedron centroids\n", " self.pos_embed = nn.Parameter(torch.randn(1, self.num_patches, edim) * 0.02)\n", "\n", " # Transformer blocks maintaining full geometry\n", " self.blocks = nn.ModuleList([\n", " GeoTransformerBlock(fdim, edim, num_heads)\n", " for _ in range(depth)\n", " ])\n", "\n", " # Classification from aggregated tetrahedra\n", " # Use both geometry and features\n", " self.norm = nn.LayerNorm(fdim)\n", " geom_feat_dim = fdim + 6 + 1 # features + d² + vol²\n", " self.head = nn.Sequential(\n", " nn.Linear(geom_feat_dim, fdim),\n", " nn.GELU(),\n", " nn.Linear(fdim, num_classes),\n", " )\n", "\n", " print(f\"\\nGeometricPatchworkViT:\")\n", " print(f\" {self.num_patches} patches × {patch_size}×{patch_size}\")\n", " print(f\" Each patch = 3-simplex (tetrahedron): 4 verts in R^{edim}\")\n", " print(f\" Attention = 4-simplex: selects 5 tetrahedra per head\")\n", " print(f\" {depth} blocks × {num_heads} heads\")\n", "\n", " def forward(self, x, ret_diag=False):\n", " B = x.shape[0]\n", "\n", " # Extract patches\n", " patches = x.unfold(2, self.patch_size, self.patch_size).unfold(3, self.patch_size, self.patch_size)\n", " patches = patches.contiguous().view(B, -1, self.patch_size * self.patch_size) # [B, P, patch_dim]\n", "\n", " # Embed as tetrahedra\n", " patch_verts, patch_feats, patch_d2, patch_vol2, patch_valid = self.patch_embed(patches)\n", " # verts: [B, P, 4, edim], feats: [B, P, 4, fdim]\n", "\n", " # Add position embedding to centroids (affects geometry)\n", " centroids = patch_verts.mean(dim=2) # [B, P, edim]\n", " pos_offset = self.pos_embed # [1, P, edim]\n", " patch_verts = patch_verts + pos_offset.unsqueeze(2) # Add to all vertices\n", "\n", " # Recompute CM after position embedding\n", " patch_d2, patch_vol2, patch_valid = self.patch_embed._cm(patch_verts)\n", "\n", " # Transformer blocks\n", " block_infos = []\n", " validity_loss = F.relu(-patch_vol2).mean() # Initial patch validity\n", "\n", " for block in self.blocks:\n", " patch_verts, patch_feats, patch_d2, patch_vol2, patch_valid, info = block(\n", " patch_verts, patch_feats, patch_d2, patch_vol2, patch_valid\n", " )\n", " block_infos.append(info)\n", " validity_loss = validity_loss + F.relu(-info['vol2_4']).mean()\n", "\n", " # Aggregate for classification\n", " # Validity-weighted mean over patches\n", " patch_valid_f = patch_valid.float() # [B, P]\n", "\n", " # Mean over vertices, then weighted mean over patches\n", " feats_agg = patch_feats.mean(dim=2) # [B, P, fdim]\n", " feats_agg = self.norm(feats_agg)\n", " feats_agg = (feats_agg * patch_valid_f.unsqueeze(-1)).sum(dim=1) / (patch_valid_f.sum(dim=1, keepdim=True) + 1e-8) # [B, fdim]\n", "\n", " # Mean geometry\n", " d2_agg = (patch_d2 * patch_valid_f.unsqueeze(-1)).sum(dim=1) / (patch_valid_f.sum(dim=1, keepdim=True) + 1e-8) # [B, 6]\n", " vol2_agg = (patch_vol2 * patch_valid_f).sum(dim=1) / (patch_valid_f.sum(dim=1) + 1e-8) # [B]\n", "\n", " # Combine features + geometry\n", " combined = torch.cat([feats_agg, d2_agg, vol2_agg.unsqueeze(-1)], dim=-1) # [B, fdim+7]\n", "\n", " logits = self.head(combined)\n", "\n", " if ret_diag:\n", " return logits, {\n", " 'patch_d2': patch_d2,\n", " 'patch_vol2': patch_vol2,\n", " 'patch_valid': patch_valid,\n", " 'blocks': block_infos,\n", " }, validity_loss\n", "\n", " return logits, validity_loss\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"Building model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " fdim=64,\n", " edim=8,\n", " num_heads=4,\n", " depth=4,\n", " ).to(device)\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\"Total params: {params:,}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 115)\n", "\n", " for ep in range(30):\n", " model.train()\n", " lsum, vsum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, vloss = model(img)\n", " ce = F.cross_entropy(logits, lab)\n", " loss = ce + 0.1 * vloss\n", " loss.backward()\n", " opt.step()\n", "\n", " lsum += ce.item() * img.size(0)\n", " vsum += vloss.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", " tr_loss = lsum / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " samp = None\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, diag, _ = model(img, ret_diag=True)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " if samp is None:\n", " samp = diag\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " # Patch 3-simplex stats\n", " p_vol = samp['patch_vol2'].mean().item()\n", " p_valid = samp['patch_valid'].float().mean().item()\n", " p_d2 = samp['patch_d2'].mean().item()\n", "\n", " print(f\"\\nEp {ep+1:2d} | CE {tr_loss:.4f} | Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%}\")\n", " print(f\" 3-simplex patches: vol²={p_vol:.4f} valid={p_valid:.1%} d²={p_d2:.4f}\")\n", "\n", " # Per-block 4-simplex stats\n", " for i, bi in enumerate(samp['blocks']):\n", " v4 = bi['vol2_4'].mean().item()\n", " valid4 = bi['valid_4'].float().mean().item()\n", " d4 = bi['d2_4'].mean().item()\n", " hw = bi['head_valid'].mean(dim=0).cpu().numpy()\n", " print(f\" Block {i+1} 4-simplex: vol²={v4:.4f} valid={valid4:.1%} d²={d4:.4f} heads={['%.2f' % h for h in hw]}\")\n", "\n", " print(\"=\" * 115)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "cellView": "form", "id": "jcvID8JBh5la", "outputId": "640ef88c-a3cb-4533-a7d2-f251ed0990f2" }, "execution_count": 9, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "Building model...\n", "\n", "GeometricPatchworkViT:\n", " 16 patches × 7×7\n", " Each patch = 3-simplex (tetrahedron): 4 verts in R^8\n", " Attention = 4-simplex: selects 5 tetrahedra per head\n", " 4 blocks × 4 heads\n", "Total params: 220,642\n", "\n", "Training...\n", "===================================================================================================================\n", "\n", "Ep 1 | CE 0.8372 | Tr 70.18% | Te 77.86% | Best 77.86%\n", " 3-simplex patches: vol²=0.0160 valid=100.0% d²=1.0490\n", " Block 1 4-simplex: vol²=0.0005 valid=100.0% d²=1.0008 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 2 4-simplex: vol²=0.0005 valid=100.0% d²=1.0004 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 3 4-simplex: vol²=0.0005 valid=100.0% d²=0.9998 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 4 4-simplex: vol²=0.0005 valid=100.0% d²=0.9997 heads=['1.00', '1.00', '1.00', '1.00']\n", "\n", "Ep 6 | CE 0.3629 | Tr 86.72% | Te 85.57% | Best 85.57%\n", " 3-simplex patches: vol²=0.0158 valid=100.0% d²=1.0448\n", " Block 1 4-simplex: vol²=0.0005 valid=100.0% d²=1.0019 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 2 4-simplex: vol²=0.0005 valid=100.0% d²=1.0013 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 3 4-simplex: vol²=0.0005 valid=100.0% d²=0.9988 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 4 4-simplex: vol²=0.0005 valid=100.0% d²=0.9993 heads=['1.00', '1.00', '1.00', '1.00']\n", "\n", "Ep 11 | CE 0.2780 | Tr 89.74% | Te 87.42% | Best 87.42%\n", " 3-simplex patches: vol²=0.0151 valid=100.0% d²=1.0293\n", " Block 1 4-simplex: vol²=0.0005 valid=100.0% d²=1.0026 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 2 4-simplex: vol²=0.0005 valid=100.0% d²=1.0013 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 3 4-simplex: vol²=0.0005 valid=100.0% d²=0.9990 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 4 4-simplex: vol²=0.0005 valid=100.0% d²=0.9996 heads=['1.00', '1.00', '1.00', '1.00']\n", "\n", "Ep 16 | CE 0.2135 | Tr 92.12% | Te 88.07% | Best 88.07%\n", " 3-simplex patches: vol²=0.0148 valid=100.0% d²=1.0227\n", " Block 1 4-simplex: vol²=0.0005 valid=100.0% d²=1.0027 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 2 4-simplex: vol²=0.0005 valid=100.0% d²=1.0012 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 3 4-simplex: vol²=0.0005 valid=100.0% d²=0.9991 heads=['1.00', '1.00', '1.00', '1.00']\n", " Block 4 4-simplex: vol²=0.0005 valid=100.0% d²=0.9992 heads=['1.00', '1.00', '1.00', '1.00']\n" ] }, { "output_type": "error", "ename": "KeyboardInterrupt", "evalue": "", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipython-input-4143309422.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 516\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 517\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"__main__\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 518\u001b[0;31m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m/tmp/ipython-input-4143309422.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 465\u001b[0m \u001b[0mce\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcross_entropy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlab\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 466\u001b[0m \u001b[0mloss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mce\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m0.1\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mvloss\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 467\u001b[0;31m \u001b[0mloss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbackward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 468\u001b[0m \u001b[0mopt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 469\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/_tensor.py\u001b[0m in \u001b[0;36mbackward\u001b[0;34m(self, gradient, retain_graph, create_graph, inputs)\u001b[0m\n\u001b[1;32m 623\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 624\u001b[0m )\n\u001b[0;32m--> 625\u001b[0;31m torch.autograd.backward(\n\u001b[0m\u001b[1;32m 626\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgradient\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mretain_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcreate_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 627\u001b[0m )\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/autograd/__init__.py\u001b[0m in \u001b[0;36mbackward\u001b[0;34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[0m\n\u001b[1;32m 352\u001b[0m \u001b[0;31m# some Python versions print out the first line of a multi-line function\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 353\u001b[0m \u001b[0;31m# calls in the traceback and some print out the last line\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 354\u001b[0;31m _engine_run_backward(\n\u001b[0m\u001b[1;32m 355\u001b[0m \u001b[0mtensors\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 356\u001b[0m \u001b[0mgrad_tensors_\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/autograd/graph.py\u001b[0m in \u001b[0;36m_engine_run_backward\u001b[0;34m(t_outputs, *args, **kwargs)\u001b[0m\n\u001b[1;32m 839\u001b[0m \u001b[0munregister_hooks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_register_logging_hooks_on_whole_graph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt_outputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 840\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 841\u001b[0;31m return Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass\n\u001b[0m\u001b[1;32m 842\u001b[0m \u001b[0mt_outputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 843\u001b[0m ) # Calls into the C++ engine to run the backward pass\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork ViT - Using Full GeoComplex\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", " det = torch.linalg.det(cm)\n", "\n", " vol2 = self._prefactor * det\n", " valid = vol2 > 1e-8\n", "\n", " return d2_pairs, vol2, valid\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC LEVEL (from our validated design)\n", "# ============================================================================\n", "\n", "class GeoLevel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, order, nin, nout, fdim, edim):\n", " super().__init__()\n", " self._order = order\n", " self._nin = nin\n", " self._nout = nout\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._nv = order + 1\n", "\n", " self._potential = math.comb(nin, self._nv) if nin >= self._nv else 0\n", "\n", " self._cm = CMValidator(order)\n", "\n", " # Factory template - guaranteed valid\n", " factory = SimplexFactory(k=order, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template)\n", "\n", " # Vertex selection\n", " _sel = torch.randn(nout, self._nv, nin) * 0.1\n", " self.register_parameter('_W_select', nn.Parameter(_sel))\n", "\n", " # Deformation network\n", " self._deform = nn.Sequential(\n", " nn.Linear(fdim, fdim),\n", " nn.LayerNorm(fdim),\n", " nn.GELU(),\n", " nn.Linear(fdim, self._nv * edim),\n", " )\n", "\n", " # Feature MLP\n", " self._mlp = nn.Sequential(\n", " nn.Linear(fdim, fdim * 2),\n", " nn.LayerNorm(fdim * 2),\n", " nn.GELU(),\n", " nn.Linear(fdim * 2, fdim),\n", " )\n", "\n", " # Geometry gate\n", " gate_in = self._cm._npairs + 1\n", " self._gate = nn.Sequential(\n", " nn.Linear(gate_in, fdim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Residual\n", " if nin != nout:\n", " self._proj = nn.Linear(nin * fdim, nout * fdim)\n", " else:\n", " self._proj = None\n", "\n", " def forward(self, x):\n", " \"\"\"x: [B, nin, fdim] -> [B, nout, fdim]\"\"\"\n", " B = x.shape[0]\n", "\n", " sel = F.softmax(self._W_select, dim=-1)\n", " picked = torch.einsum('ovn,bnf->bovf', sel, x)\n", " agg = picked.mean(dim=2)\n", "\n", " # Deform template\n", " deform = self._deform(agg).view(B, self._nout, self._nv, self._edim)\n", " template = self._template.unsqueeze(0).unsqueeze(0).expand(B, self._nout, -1, -1)\n", " verts = template + self.BASE_DEFORM * deform\n", "\n", " # CM validation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " # Geometry gating\n", " d2_norm = d2 / (d2.mean(dim=-1, keepdim=True) + 1e-8)\n", " geo = torch.cat([d2_norm, vol2.unsqueeze(-1) / (vol2.mean() + 1e-8)], dim=-1)\n", " gate = self._gate(geo)\n", "\n", " out = self._mlp(agg) * gate\n", "\n", " # Validity attenuation\n", " attn = torch.sigmoid(vol2 * 1e6)\n", " out = out * attn.unsqueeze(-1)\n", "\n", " # Residual\n", " if self._proj is not None:\n", " res = self._proj(x.flatten(1)).view(B, self._nout, self._fdim)\n", " else:\n", " res = x\n", " out = out + 0.1 * res\n", "\n", " return out, verts, {\n", " 'd2': d2, 'vol2': vol2, 'valid': valid, 'attn': attn,\n", " 'order': self._order, 'potential': self._potential,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC COMPLEX (exponential growth k-simplex hierarchy)\n", "# ============================================================================\n", "\n", "class GeoComplex(nn.Module):\n", " def __init__(self, nbase, fdim, depth, edim):\n", " super().__init__()\n", " self._nbase = nbase\n", " self._fdim = fdim\n", " self._depth = depth\n", " self._edim = edim\n", "\n", " # EXPONENTIAL GROWTH\n", " sizes = [nbase]\n", " for _ in range(depth):\n", " sizes.append(sizes[-1] * 2)\n", " self._sizes = sizes\n", "\n", " # Potentials\n", " potentials = []\n", " for i in range(depth):\n", " k = i + 1\n", " nv = k + 1\n", " nin = sizes[i]\n", " pot = math.comb(nin, nv) if nin >= nv else 0\n", " potentials.append(pot)\n", " self._potentials = potentials\n", "\n", " # Input projection\n", " self._proj_in = nn.Linear(fdim, nbase * fdim)\n", "\n", " # Levels\n", " level_list = []\n", " for i in range(depth):\n", " lv = GeoLevel(\n", " order=i + 1,\n", " nin=sizes[i],\n", " nout=sizes[i + 1],\n", " fdim=fdim,\n", " edim=edim\n", " )\n", " level_list.append(lv)\n", " self._levels = nn.ModuleList(level_list)\n", "\n", " # Level weights\n", " self.register_parameter('_lw', nn.Parameter(torch.ones(depth) / depth))\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [B, fdim]\n", " Returns:\n", " pooled: [B, fdim] - weighted pooled output\n", " all_verts: list of [B, nout, nv, edim] per level\n", " all_feats: list of [B, nout, fdim] per level\n", " infos: list of info dicts per level\n", " \"\"\"\n", " B = x.shape[0]\n", " h = self._proj_in(x).view(B, self._nbase, self._fdim)\n", "\n", " all_feats, all_verts, infos = [], [], []\n", " for lv in self._levels:\n", " h, verts, info = lv(h)\n", " all_feats.append(h)\n", " all_verts.append(verts)\n", " infos.append(info)\n", "\n", " # Weighted pool\n", " w = F.softmax(self._lw, dim=0)\n", " pooled = []\n", " for i, (feat, info) in enumerate(zip(all_feats, infos)):\n", " a = info['attn']\n", " wm = (feat * a.unsqueeze(-1)).sum(1) / (a.sum(1, keepdim=True) + 1e-8)\n", " pooled.append(w[i] * wm)\n", "\n", " return sum(pooled), all_verts, all_feats, {\n", " 'sizes': self._sizes,\n", " 'potentials': self._potentials,\n", " 'infos': infos,\n", " 'weights': w.detach(),\n", " }\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC PATCH: Each patch through full k-simplex hierarchy\n", "# ============================================================================\n", "\n", "class GeoPatch(nn.Module):\n", " \"\"\"\n", " Each image patch is processed through a full GeoComplex.\n", " Maintains the complete k-simplex hierarchy per patch.\n", " \"\"\"\n", " def __init__(self, patch_dim, fdim, nbase, depth, edim):\n", " super().__init__()\n", " self._fdim = fdim\n", " self._depth = depth\n", "\n", " # Patch to initial features\n", " self._patch_proj = nn.Sequential(\n", " nn.Linear(patch_dim, fdim),\n", " nn.LayerNorm(fdim),\n", " nn.GELU(),\n", " )\n", "\n", " # Full geometric complex\n", " self._complex = GeoComplex(nbase, fdim, depth, edim)\n", "\n", " def forward(self, patches):\n", " \"\"\"\n", " patches: [B, P, patch_dim]\n", "\n", " Returns per patch, per level:\n", " - vertices (the actual k-simplex geometry)\n", " - features\n", " - CM info\n", " \"\"\"\n", " B, P, _ = patches.shape\n", "\n", " # Project patches\n", " patch_feats = self._patch_proj(patches) # [B, P, fdim]\n", "\n", " # Process each patch through complex\n", " # Reshape: [B*P, fdim]\n", " flat = patch_feats.view(B * P, self._fdim)\n", "\n", " pooled, all_verts, all_feats, info = self._complex(flat)\n", "\n", " # Reshape back: each has shape [B*P, ...] -> [B, P, ...]\n", " pooled = pooled.view(B, P, self._fdim)\n", "\n", " # Reshape vertices and features per level\n", " all_verts_reshaped = []\n", " all_feats_reshaped = []\n", " for verts, feats in zip(all_verts, all_feats):\n", " # verts: [B*P, nout, nv, edim] -> [B, P, nout, nv, edim]\n", " nout, nv, edim = verts.shape[1], verts.shape[2], verts.shape[3]\n", " all_verts_reshaped.append(verts.view(B, P, nout, nv, edim))\n", "\n", " # feats: [B*P, nout, fdim] -> [B, P, nout, fdim]\n", " nout, fdim = feats.shape[1], feats.shape[2]\n", " all_feats_reshaped.append(feats.view(B, P, nout, fdim))\n", "\n", " # Reshape info\n", " for inf in info['infos']:\n", " for key in ['d2', 'vol2', 'valid', 'attn']:\n", " if key in inf:\n", " shape = inf[key].shape\n", " if len(shape) == 2: # [B*P, X]\n", " inf[key] = inf[key].view(B, P, shape[-1])\n", " elif len(shape) == 1: # [B*P]\n", " inf[key] = inf[key].view(B, P)\n", "\n", " return pooled, all_verts_reshaped, all_feats_reshaped, info\n", "\n", "\n", "# ============================================================================\n", "# 4-SIMPLEX SEQUENCE ATTENTION\n", "# ============================================================================\n", "\n", "class Simplex4SequenceAttention(nn.Module):\n", " \"\"\"\n", " 4-simplex attention over patches.\n", "\n", " Each patch has full k-simplex hierarchy.\n", " We use the highest level (k=depth) simplices as the \"tokens\".\n", " Select 5 patches to form a 4-simplex over patch-level geometry.\n", " \"\"\"\n", " def __init__(self, fdim, edim, num_patches, num_heads=4):\n", " super().__init__()\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._num_patches = num_patches\n", " self._num_heads = num_heads\n", " self._head_dim = fdim // num_heads\n", "\n", " self._k = 4\n", " self._nv = 5\n", "\n", " self._cm = CMValidator(self._k)\n", "\n", " # Factory template\n", " factory = SimplexFactory(k=self._k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " template = factory.build_torch(dtype=torch.float32)\n", " self.register_buffer('_template', template)\n", "\n", " # Selection: which 5 patches per head\n", " self._to_select = nn.Linear(fdim, num_heads * self._nv)\n", "\n", " # Patch features to coordinate\n", " self._to_coord = nn.Linear(fdim, edim)\n", "\n", " # Value projection\n", " self._to_v = nn.Linear(fdim, fdim)\n", "\n", " # Geometry gate\n", " geom_dim = self._cm._npairs + 1\n", " self._geo_gate = nn.Sequential(\n", " nn.Linear(geom_dim, num_heads),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Output\n", " self._out = nn.Linear(fdim, fdim)\n", "\n", " self._deform_scale = 0.05\n", "\n", " def forward(self, patch_pooled, patch_verts_highest, patch_feats_highest, patch_info):\n", " \"\"\"\n", " patch_pooled: [B, P, fdim] - pooled features per patch\n", " patch_verts_highest: [B, P, nout, nv, edim] - highest k-level vertices\n", " patch_feats_highest: [B, P, nout, fdim] - highest k-level features\n", " patch_info: info from GeoComplex\n", "\n", " Returns:\n", " out: [B, P, fdim]\n", " attn_info: dict\n", " \"\"\"\n", " B, P, _ = patch_pooled.shape\n", " H = self._num_heads\n", "\n", " # Selection scores: [B, P, H*5] -> [B, H, 5, P]\n", " sel = self._to_select(patch_pooled)\n", " sel = sel.view(B, P, H, self._nv).permute(0, 2, 3, 1)\n", " sel_weights = F.softmax(sel, dim=-1) # [B, H, 5, P]\n", "\n", " # Patch coordinates (from pooled features)\n", " coords = self._to_coord(patch_pooled) # [B, P, edim]\n", "\n", " # Select 5 patches -> their coordinates become 4-simplex vertices\n", " sel_coords = torch.einsum('bhvp,bpe->bhve', sel_weights, coords) # [B, H, 5, edim]\n", "\n", " # Deform template\n", " template = self._template.unsqueeze(0).unsqueeze(0)\n", " verts_4 = template + self._deform_scale * sel_coords\n", "\n", " # CM validation\n", " d2_4, vol2_4, valid_4 = self._cm(verts_4)\n", "\n", " # Head validity\n", " head_valid = torch.sigmoid(vol2_4 * 1e6) # [B, H]\n", "\n", " # Geometry gate\n", " geo = torch.cat([d2_4, vol2_4.unsqueeze(-1)], dim=-1)\n", " geo_weight = self._geo_gate(geo).mean(dim=-1) # [B, H]\n", " head_weight = geo_weight * head_valid\n", " head_weight = head_weight / (head_weight.sum(dim=-1, keepdim=True) + 1e-8)\n", "\n", " # Values\n", " v = self._to_v(patch_pooled) # [B, P, fdim]\n", " v = v.view(B, P, H, self._head_dim).permute(0, 2, 1, 3) # [B, H, P, hd]\n", "\n", " # Attention from selection\n", " attn = sel_weights.mean(dim=2) # [B, H, P]\n", " attn = attn / (attn.sum(dim=-1, keepdim=True) + 1e-8)\n", "\n", " # Aggregate\n", " out = torch.einsum('bhp,bhpd->bhd', attn, v) # [B, H, hd]\n", " out = out * head_weight.unsqueeze(-1)\n", " out = out.reshape(B, self._fdim)\n", " out = self._out(out)\n", "\n", " # Broadcast to patches\n", " out = out.unsqueeze(1).expand(-1, P, -1)\n", "\n", " return out, {\n", " 'd2_4': d2_4,\n", " 'vol2_4': vol2_4,\n", " 'valid_4': valid_4,\n", " 'head_weight': head_weight,\n", " 'sel_weights': sel_weights,\n", " }\n", "\n", "\n", "# ============================================================================\n", "# TRANSFORMER BLOCK\n", "# ============================================================================\n", "\n", "class GeoTransformerBlock(nn.Module):\n", " def __init__(self, fdim, edim, num_patches, num_heads, mlp_ratio=4.0):\n", " super().__init__()\n", " self.norm1 = nn.LayerNorm(fdim)\n", " self.attn = Simplex4SequenceAttention(fdim, edim, num_patches, num_heads)\n", " self.norm2 = nn.LayerNorm(fdim)\n", " self.mlp = nn.Sequential(\n", " nn.Linear(fdim, int(fdim * mlp_ratio)),\n", " nn.GELU(),\n", " nn.Linear(int(fdim * mlp_ratio), fdim),\n", " )\n", "\n", " def forward(self, patch_pooled, patch_verts, patch_feats, patch_info):\n", " # Attention\n", " attn_out, attn_info = self.attn(\n", " self.norm1(patch_pooled),\n", " patch_verts[-1], # Highest k-level\n", " patch_feats[-1],\n", " patch_info\n", " )\n", " patch_pooled = patch_pooled + attn_out\n", "\n", " # MLP\n", " patch_pooled = patch_pooled + self.mlp(self.norm2(patch_pooled))\n", "\n", " return patch_pooled, attn_info\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " fdim=64,\n", " edim=8,\n", " nbase=4,\n", " geo_depth=4,\n", " attn_depth=4,\n", " num_heads=4,\n", " ):\n", " super().__init__()\n", "\n", " self.img_size = img_size\n", " self.patch_size = patch_size\n", " self.num_patches = (img_size // patch_size) ** 2\n", " self.patch_dim = patch_size * patch_size * in_chans\n", "\n", " # Geometric patch embedding with full k-hierarchy\n", " self.patch_embed = GeoPatch(\n", " patch_dim=self.patch_dim,\n", " fdim=fdim,\n", " nbase=nbase,\n", " depth=geo_depth,\n", " edim=edim,\n", " )\n", "\n", " # Position embedding\n", " self.pos_embed = nn.Parameter(torch.randn(1, self.num_patches, fdim) * 0.02)\n", "\n", " # Transformer blocks with 4-simplex attention\n", " self.blocks = nn.ModuleList([\n", " GeoTransformerBlock(fdim, edim, self.num_patches, num_heads)\n", " for _ in range(attn_depth)\n", " ])\n", "\n", " # Classification head uses geometry\n", " self.norm = nn.LayerNorm(fdim)\n", " self.head = nn.Linear(fdim, num_classes)\n", "\n", " # Print architecture\n", " print(f\"\\nGeometricPatchworkViT:\")\n", " print(f\" Patches: {self.num_patches} × {patch_size}×{patch_size}\")\n", " print(f\" K-simplex hierarchy per patch:\")\n", " print(f\" Sizes: {self.patch_embed._complex._sizes}\")\n", " print(f\" Potentials: {self.patch_embed._complex._potentials}\")\n", " for i, pot in enumerate(self.patch_embed._complex._potentials):\n", " k = i + 1\n", " print(f\" k={k}: {pot:,} potential {k}-simplices\")\n", " print(f\" 4-simplex attention: {attn_depth} blocks × {num_heads} heads\")\n", " print(f\" Selects 5 of {self.num_patches} patches per head\")\n", " print(f\" C({self.num_patches},5) = {math.comb(self.num_patches, 5):,} potential 4-simplices\")\n", "\n", " def forward(self, x, ret_diag=False):\n", " B = x.shape[0]\n", "\n", " # Extract patches\n", " patches = x.unfold(2, self.patch_size, self.patch_size).unfold(3, self.patch_size, self.patch_size)\n", " patches = patches.contiguous().view(B, -1, self.patch_size * self.patch_size)\n", "\n", " # Geometric patch embedding - full k-hierarchy\n", " patch_pooled, all_verts, all_feats, patch_info = self.patch_embed(patches)\n", "\n", " # Add position embedding\n", " patch_pooled = patch_pooled + self.pos_embed\n", "\n", " # Transformer blocks\n", " block_infos = []\n", " validity_loss = 0\n", "\n", " # Patch hierarchy validity\n", " for inf in patch_info['infos']:\n", " validity_loss = validity_loss + F.relu(-inf['vol2']).mean()\n", "\n", " for block in self.blocks:\n", " patch_pooled, attn_info = block(patch_pooled, all_verts, all_feats, patch_info)\n", " block_infos.append(attn_info)\n", " validity_loss = validity_loss + F.relu(-attn_info['vol2_4']).mean()\n", "\n", " # Aggregate for classification\n", " patch_pooled = self.norm(patch_pooled)\n", " cls = patch_pooled.mean(dim=1) # [B, fdim]\n", " logits = self.head(cls)\n", "\n", " if ret_diag:\n", " return logits, {'patch': patch_info, 'blocks': block_infos}, validity_loss\n", "\n", " return logits, validity_loss\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"Building model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " fdim=64,\n", " edim=8,\n", " nbase=4,\n", " geo_depth=4,\n", " attn_depth=4,\n", " num_heads=4,\n", " ).to(device)\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\"Total params: {params:,}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 115)\n", "\n", " for ep in range(30):\n", " model.train()\n", " lsum, vsum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, vloss = model(img)\n", " ce = F.cross_entropy(logits, lab)\n", " loss = ce + 0.1 * vloss\n", " loss.backward()\n", " opt.step()\n", "\n", " lsum += ce.item() * img.size(0)\n", " vsum += vloss.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", " tr_loss = lsum / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " samp = None\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, diag, _ = model(img, ret_diag=True)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " if samp is None:\n", " samp = diag\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"\\nEp {ep+1:2d} | CE {tr_loss:.4f} | Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%}\")\n", "\n", " # K-hierarchy stats\n", " pw = samp['patch']['weights'].cpu().numpy()\n", " print(f\" Patch k-hierarchy weights: {['%.3f' % w for w in pw]}\")\n", " for inf in samp['patch']['infos']:\n", " k = inf['order']\n", " v = inf['vol2'].mean().item()\n", " vld = inf['valid'].float().mean().item()\n", " att = inf['attn'].mean().item()\n", " d = inf['d2'].mean().item()\n", " pot = inf['potential']\n", " print(f\" k={k}: pot={pot:>8,} vol²={v:.2e} valid={vld:5.1%} attn={att:.3f} d²={d:.4f}\")\n", "\n", " # 4-simplex attention stats\n", " for i, bi in enumerate(samp['blocks']):\n", " v4 = bi['vol2_4'].mean().item()\n", " valid4 = bi['valid_4'].float().mean().item()\n", " d4 = bi['d2_4'].mean().item()\n", " hw = bi['head_weight'].mean(dim=0).cpu().numpy()\n", " print(f\" Block {i+1} 4-simplex: vol²={v4:.2e} valid={valid4:5.1%} d²={d4:.4f} heads={['%.2f' % h for h in hw]}\")\n", "\n", " print(\"=\" * 115)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "id": "FugBnGC-i2tF", "outputId": "dd681fc1-2609-4751-d77c-4ae8c4dc3305" }, "execution_count": 2, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n" ] }, { "output_type": "stream", "name": "stderr", "text": [ "100%|██████████| 26.4M/26.4M [00:00<00:00, 109MB/s]\n", "100%|██████████| 29.5k/29.5k [00:00<00:00, 4.02MB/s]\n", "100%|██████████| 4.42M/4.42M [00:00<00:00, 63.3MB/s]\n", "100%|██████████| 5.15k/5.15k [00:00<00:00, 20.7MB/s]\n" ] }, { "output_type": "stream", "name": "stdout", "text": [ "Building model...\n", "\n", "GeometricPatchworkViT:\n", " Patches: 16 × 7×7\n", " K-simplex hierarchy per patch:\n", " Sizes: [4, 8, 16, 32, 64]\n", " Potentials: [6, 56, 1820, 201376]\n", " k=1: 6 potential 1-simplices\n", " k=2: 56 potential 2-simplices\n", " k=3: 1,820 potential 3-simplices\n", " k=4: 201,376 potential 4-simplices\n", " 4-simplex attention: 4 blocks × 4 heads\n", " Selects 5 of 16 patches per head\n", " C(16,5) = 4,368 potential 4-simplices\n", "Total params: 11,450,990\n", "\n", "Training...\n", "===================================================================================================================\n", "\n", "Ep 1 | CE 0.8320 | Tr 69.77% | Te 79.11% | Best 79.11%\n", " Patch k-hierarchy weights: ['0.285', '0.256', '0.240', '0.219']\n", " k=1: pot= 6 vol²=1.08e+00 valid=100.0% attn=1.000 d²=1.0760\n", " k=2: pot= 56 vol²=1.67e-01 valid=100.0% attn=1.000 d²=0.9428\n", " k=3: pot= 1,820 vol²=1.46e-02 valid=100.0% attn=1.000 d²=1.0262\n", " k=4: pot= 201,376 vol²=5.85e-04 valid=100.0% attn=1.000 d²=1.0350\n", " Block 1 4-simplex: vol²=5.34e-04 valid=100.0% d²=0.9994 heads=['0.25', '0.25', '0.25', '0.25']\n", " Block 2 4-simplex: vol²=6.01e-04 valid=100.0% d²=1.0270 heads=['0.26', '0.26', '0.25', '0.23']\n", " Block 3 4-simplex: vol²=5.39e-04 valid=100.0% d²=0.9985 heads=['0.25', '0.25', '0.25', '0.25']\n", " Block 4 4-simplex: vol²=5.71e-04 valid=100.0% d²=1.0132 heads=['0.25', '0.25', '0.25', '0.25']\n", "\n", "Ep 6 | CE 0.3281 | Tr 87.88% | Te 87.19% | Best 87.19%\n", " Patch k-hierarchy weights: ['0.344', '0.245', '0.241', '0.170']\n", " k=1: pot= 6 vol²=6.21e-01 valid=100.0% attn=1.000 d²=0.6207\n", " k=2: pot= 56 vol²=1.31e-01 valid=100.0% attn=1.000 d²=0.8210\n", " k=3: pot= 1,820 vol²=1.41e-02 valid=100.0% attn=1.000 d²=1.0366\n", " k=4: pot= 201,376 vol²=5.17e-04 valid=100.0% attn=1.000 d²=1.1102\n", " Block 1 4-simplex: vol²=5.94e-04 valid=100.0% d²=1.0406 heads=['0.26', '0.25', '0.25', '0.25']\n", " Block 2 4-simplex: vol²=8.14e-04 valid=100.0% d²=1.1329 heads=['0.31', '0.28', '0.17', '0.24']\n", " Block 3 4-simplex: vol²=5.48e-04 valid=100.0% d²=1.0050 heads=['0.27', '0.23', '0.25', '0.25']\n", " Block 4 4-simplex: vol²=8.22e-04 valid=100.0% d²=1.1456 heads=['0.28', '0.24', '0.21', '0.27']\n", "\n", "Ep 11 | CE 0.2621 | Tr 90.27% | Te 88.15% | Best 88.28%\n", " Patch k-hierarchy weights: ['0.385', '0.241', '0.232', '0.143']\n", " k=1: pot= 6 vol²=3.67e-01 valid=100.0% attn=1.000 d²=0.3675\n", " k=2: pot= 56 vol²=8.56e-02 valid=100.0% attn=1.000 d²=0.6449\n", " k=3: pot= 1,820 vol²=1.29e-02 valid=100.0% attn=1.000 d²=0.9979\n", " k=4: pot= 201,376 vol²=3.25e-04 valid=100.0% attn=1.000 d²=0.9680\n", " Block 1 4-simplex: vol²=5.81e-04 valid=100.0% d²=1.0404 heads=['0.27', '0.25', '0.24', '0.24']\n", " Block 2 4-simplex: vol²=8.57e-04 valid=100.0% d²=1.1579 heads=['0.32', '0.31', '0.14', '0.22']\n", " Block 3 4-simplex: vol²=6.19e-04 valid=100.0% d²=1.0418 heads=['0.28', '0.20', '0.26', '0.26']\n", " Block 4 4-simplex: vol²=7.56e-04 valid=100.0% d²=1.1167 heads=['0.29', '0.23', '0.22', '0.26']\n", "\n", "Ep 16 | CE 0.2119 | Tr 92.09% | Te 89.15% | Best 89.15%\n", " Patch k-hierarchy weights: ['0.404', '0.242', '0.227', '0.128']\n", " k=1: pot= 6 vol²=3.70e-01 valid=100.0% attn=1.000 d²=0.3697\n", " k=2: pot= 56 vol²=1.13e-01 valid=100.0% attn=1.000 d²=0.7210\n", " k=3: pot= 1,820 vol²=1.06e-02 valid=100.0% attn=1.000 d²=0.9167\n", " k=4: pot= 201,376 vol²=3.76e-04 valid=100.0% attn=1.000 d²=0.9582\n", " Block 1 4-simplex: vol²=5.87e-04 valid=100.0% d²=1.0405 heads=['0.28', '0.25', '0.25', '0.23']\n", " Block 2 4-simplex: vol²=8.92e-04 valid=100.0% d²=1.1717 heads=['0.32', '0.32', '0.15', '0.20']\n", " Block 3 4-simplex: vol²=6.41e-04 valid=100.0% d²=1.0514 heads=['0.31', '0.20', '0.23', '0.26']\n", " Block 4 4-simplex: vol²=7.89e-04 valid=100.0% d²=1.1320 heads=['0.27', '0.23', '0.26', '0.24']\n", "\n", "Ep 21 | CE 0.1558 | Tr 94.25% | Te 89.91% | Best 89.91%\n", " Patch k-hierarchy weights: ['0.405', '0.244', '0.226', '0.125']\n", " k=1: pot= 6 vol²=2.68e-01 valid=100.0% attn=1.000 d²=0.2681\n", " k=2: pot= 56 vol²=5.76e-02 valid=100.0% attn=1.000 d²=0.5542\n", " k=3: pot= 1,820 vol²=7.03e-03 valid=100.0% attn=1.000 d²=0.7703\n", " k=4: pot= 201,376 vol²=4.61e-04 valid=100.0% attn=1.000 d²=0.8753\n", " Block 1 4-simplex: vol²=5.91e-04 valid=100.0% d²=1.0420 heads=['0.27', '0.25', '0.25', '0.23']\n", " Block 2 4-simplex: vol²=8.80e-04 valid=100.0% d²=1.1653 heads=['0.31', '0.32', '0.16', '0.22']\n", " Block 3 4-simplex: vol²=6.73e-04 valid=100.0% d²=1.0667 heads=['0.30', '0.20', '0.23', '0.27']\n", " Block 4 4-simplex: vol²=8.03e-04 valid=100.0% d²=1.1387 heads=['0.25', '0.30', '0.23', '0.22']\n" ] }, { "output_type": "error", "ename": "KeyboardInterrupt", "evalue": "", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipython-input-3727292590.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 660\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 661\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"__main__\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 662\u001b[0;31m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m/tmp/ipython-input-3727292590.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 600\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 601\u001b[0m \u001b[0mopt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mzero_grad\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 602\u001b[0;31m \u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvloss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 603\u001b[0m \u001b[0mce\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcross_entropy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlab\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 604\u001b[0m \u001b[0mloss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mce\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m0.1\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mvloss\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1773\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1775\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1776\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1777\u001b[0m \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1784\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1785\u001b[0m or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1786\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1787\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1788\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-3727292590.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, x, ret_diag)\u001b[0m\n\u001b[1;32m 538\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 539\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mblock\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mblocks\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 540\u001b[0;31m \u001b[0mpatch_pooled\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattn_info\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mblock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpatch_pooled\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mall_verts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mall_feats\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpatch_info\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 541\u001b[0m \u001b[0mblock_infos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mattn_info\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 542\u001b[0m \u001b[0mvalidity_loss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mvalidity_loss\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrelu\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0mattn_info\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'vol2_4'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1773\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1775\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1776\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1777\u001b[0m \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1784\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1785\u001b[0m or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1786\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1787\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1788\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-3727292590.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, patch_pooled, patch_verts, patch_feats, patch_info)\u001b[0m\n\u001b[1;32m 442\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mforward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpatch_pooled\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpatch_verts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpatch_feats\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpatch_info\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 443\u001b[0m \u001b[0;31m# Attention\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 444\u001b[0;31m attn_out, attn_info = self.attn(\n\u001b[0m\u001b[1;32m 445\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnorm1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpatch_pooled\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 446\u001b[0m \u001b[0mpatch_verts\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;31m# Highest k-level\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1773\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1775\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1776\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1777\u001b[0m \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1784\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1785\u001b[0m or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1786\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1787\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1788\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-3727292590.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, patch_pooled, patch_verts_highest, patch_feats_highest, patch_info)\u001b[0m\n\u001b[1;32m 387\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 388\u001b[0m \u001b[0;31m# CM validation\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 389\u001b[0;31m \u001b[0md2_4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvol2_4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalid_4\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_cm\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mverts_4\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 390\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 391\u001b[0m \u001b[0;31m# Head validity\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1773\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1775\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1776\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1777\u001b[0m \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1784\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1785\u001b[0m or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1786\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1787\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1788\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/tmp/ipython-input-3727292590.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, verts)\u001b[0m\n\u001b[1;32m 36\u001b[0m \u001b[0mnorms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdiagonal\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgram\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdim1\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdim2\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 37\u001b[0m \u001b[0md2_mat\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnorms\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munsqueeze\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mnorms\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munsqueeze\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mgram\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 38\u001b[0;31m \u001b[0md2_mat\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrelu\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md2_mat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 39\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0md2_pairs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0md2_mat\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m...\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/nn/functional.py\u001b[0m in \u001b[0;36mrelu\u001b[0;34m(input, inplace)\u001b[0m\n\u001b[1;32m 1684\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1685\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1686\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mrelu\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minput\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minplace\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mbool\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# noqa: D400,D402\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1687\u001b[0m r\"\"\"relu(input, inplace=False) -> Tensor\n\u001b[1;32m 1688\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork ViT - NO FLATTEN\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " \"\"\"verts: [..., nv, edim] - works on any leading dims\"\"\"\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", "\n", " vol2 = self._prefactor * torch.linalg.det(cm)\n", "\n", " return d2_pairs, vol2, (vol2 > 1e-8)\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC LEVEL - OPERATES ON [..., nin, fdim]\n", "# ============================================================================\n", "\n", "class GeoLevel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, order, nin, nout, fdim, edim):\n", " super().__init__()\n", " self._order = order\n", " self._nin = nin\n", " self._nout = nout\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._nv = order + 1\n", "\n", " self._cm = CMValidator(order)\n", "\n", " factory = SimplexFactory(k=order, embed_dim=edim, method=\"regular\", scale=1.0)\n", " self.register_buffer('_template', factory.build_torch(dtype=torch.float32))\n", "\n", " self.register_parameter('_W_sel', nn.Parameter(torch.randn(nout, self._nv, nin) * 0.1))\n", " self._deform = nn.Linear(fdim, self._nv * edim)\n", " self._transform = nn.Linear(fdim, fdim)\n", " self._gate = nn.Linear(self._cm._npairs + 1, fdim)\n", " self._norm = nn.LayerNorm(fdim)\n", "\n", " def forward(self, feats, coords):\n", " \"\"\"\n", " feats: [..., nin, fdim] - arbitrary leading dims preserved\n", " coords: [..., nin, edim]\n", "\n", " Returns:\n", " out_feats: [..., nout, fdim]\n", " out_coords: [..., nout, edim]\n", " vol2: [..., nout]\n", " \"\"\"\n", " # Soft selection: [nout, nv, nin]\n", " sel = F.softmax(self._W_sel, dim=-1)\n", "\n", " # Select: [..., nin, fdim] @ [nout, nv, nin] -> [..., nout, nv, fdim]\n", " picked_feats = torch.einsum('...nf,ovn->...ovf', feats, sel)\n", " picked_coords = torch.einsum('...ne,ovn->...ove', coords, sel)\n", "\n", " # Aggregate per simplex: [..., nout, fdim]\n", " agg = picked_feats.mean(dim=-2)\n", "\n", " # Deform template: [..., nout, nv, edim]\n", " deform = self._deform(agg).unflatten(-1, (self._nv, self._edim))\n", " verts = self._template + self.BASE_DEFORM * deform + 0.1 * picked_coords\n", "\n", " # CM validation\n", " d2, vol2, valid = self._cm(verts)\n", "\n", " # Geometry gate\n", " geo = torch.cat([\n", " d2 / (d2.mean(dim=-1, keepdim=True) + 1e-8),\n", " vol2.unsqueeze(-1) / (vol2.abs().mean() + 1e-8)\n", " ], dim=-1)\n", " gate = torch.sigmoid(self._gate(geo))\n", "\n", " # Transform with validity attenuation\n", " out_feats = self._norm(self._transform(agg)) * gate * torch.sigmoid(vol2 * 1e6).unsqueeze(-1)\n", " out_coords = verts.mean(dim=-2)\n", "\n", " return out_feats, out_coords, vol2\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC COMPLEX - MAINTAINS [...] STRUCTURE\n", "# ============================================================================\n", "\n", "class GeoComplex(nn.Module):\n", " def __init__(self, nbase, fdim, depth, edim):\n", " super().__init__()\n", " self._nbase = nbase\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._depth = depth\n", "\n", " self._sizes = [nbase * (2 ** i) for i in range(depth + 1)]\n", "\n", " self._proj_f = nn.Linear(fdim, nbase * fdim)\n", " self._proj_c = nn.Linear(fdim, nbase * edim)\n", "\n", " self._levels = nn.ModuleList([\n", " GeoLevel(order=i+1, nin=self._sizes[i], nout=self._sizes[i+1], fdim=fdim, edim=edim)\n", " for i in range(depth)\n", " ])\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [..., fdim] - arbitrary leading dims\n", "\n", " Returns:\n", " feats: [..., final_size, fdim]\n", " coords: [..., final_size, edim]\n", " vol2_stack: [..., depth, max_size]\n", " \"\"\"\n", " # Project: [..., fdim] -> [..., nbase, fdim]\n", " feats = self._proj_f(x).unflatten(-1, (self._nbase, self._fdim))\n", " coords = self._proj_c(x).unflatten(-1, (self._nbase, self._edim))\n", "\n", " max_size = self._sizes[-1]\n", " vol2_list = []\n", "\n", " for lv in self._levels:\n", " feats, coords, vol2 = lv(feats, coords)\n", " # Pad to max_size: [..., nout] -> [..., max_size]\n", " pad_size = max_size - vol2.shape[-1]\n", " vol2_padded = F.pad(vol2, (0, pad_size), value=1.0)\n", " vol2_list.append(vol2_padded)\n", "\n", " # Stack: list of [..., max_size] -> [..., depth, max_size]\n", " vol2_stack = torch.stack(vol2_list, dim=-2)\n", "\n", " return feats, coords, vol2_stack\n", "\n", "\n", "# ============================================================================\n", "# GEOMETRIC PATCH EMBEDDING - NO FLATTEN\n", "# ============================================================================\n", "\n", "class GeoPatchEmbed(nn.Module):\n", " def __init__(self, patch_dim, fdim, nbase, depth, edim):\n", " super().__init__()\n", " self._fdim = fdim\n", " self._edim = edim\n", "\n", " self._proj = nn.Sequential(\n", " nn.Linear(patch_dim, fdim),\n", " nn.LayerNorm(fdim),\n", " nn.GELU(),\n", " )\n", " self._complex = GeoComplex(nbase, fdim, depth, edim)\n", " self._final_size = self._complex._sizes[-1]\n", "\n", " def forward(self, patches):\n", " \"\"\"\n", " patches: [B, P, patch_dim]\n", "\n", " Returns:\n", " feats: [B, P, S, fdim] - S = final_size\n", " coords: [B, P, S, edim]\n", " vol2: [B, P, depth, max_size]\n", " \"\"\"\n", " # [B, P, patch_dim] -> [B, P, fdim]\n", " x = self._proj(patches)\n", "\n", " # [B, P, fdim] -> [B, P, S, fdim], [B, P, S, edim], [B, P, depth, max_size]\n", " # GeoComplex operates on last dim, preserves [B, P, ...]\n", " feats, coords, vol2 = self._complex(x)\n", "\n", " return feats, coords, vol2\n", "\n", "\n", "# ============================================================================\n", "# 4-SIMPLEX ATTENTION - OPERATES ON [B, P, S, fdim]\n", "# ============================================================================\n", "\n", "class Simplex4Attn(nn.Module):\n", " def __init__(self, fdim, edim, num_heads=4):\n", " super().__init__()\n", " self._fdim = fdim\n", " self._edim = edim\n", " self._num_heads = num_heads\n", " self._head_dim = fdim // num_heads\n", " self._nv = 5\n", "\n", " self._cm = CMValidator(4)\n", "\n", " factory = SimplexFactory(k=4, embed_dim=edim, method=\"regular\", scale=1.0)\n", " self.register_buffer('_template', factory.build_torch(dtype=torch.float32))\n", "\n", " self._to_sel = nn.Linear(fdim, num_heads * self._nv)\n", " self._to_coord = nn.Linear(fdim, edim)\n", " self._to_v = nn.Linear(fdim, fdim)\n", " self._geo_gate = nn.Linear(self._cm._npairs + 1, fdim)\n", " self._out = nn.Linear(fdim, fdim)\n", "\n", " def forward(self, feats, coords):\n", " \"\"\"\n", " feats: [B, P, S, fdim]\n", " coords: [B, P, S, edim]\n", "\n", " Returns:\n", " out: [B, P, S, fdim]\n", " vol2: [B, H]\n", " \"\"\"\n", " B, P, S, _ = feats.shape\n", " H = self._num_heads\n", "\n", " # Aggregate per patch: [B, P, fdim]\n", " patch_feats = feats.mean(dim=2)\n", " patch_coords = self._to_coord(patch_feats) # [B, P, edim]\n", "\n", " # Selection: [B, P, H*5] -> [B, H, 5, P]\n", " sel = self._to_sel(patch_feats).view(B, P, H, self._nv).permute(0, 2, 3, 1)\n", " sel_w = F.softmax(sel, dim=-1)\n", "\n", " # Select coords: [B, H, 5, P] @ [B, P, edim] -> [B, H, 5, edim]\n", " sel_coords = torch.einsum('bhvp,bpe->bhve', sel_w, patch_coords)\n", "\n", " # Form 4-simplex\n", " verts = self._template + 0.05 * sel_coords\n", " d2, vol2, valid = self._cm(verts) # vol2: [B, H]\n", "\n", " # Gate\n", " geo = torch.cat([d2, vol2.unsqueeze(-1)], dim=-1)\n", " gate = torch.sigmoid(self._geo_gate(geo)).mean(dim=1) # [B, fdim]\n", "\n", " # Values: [B, P, S, fdim] -> [B, P, S, H, hd]\n", " v = self._to_v(feats).view(B, P, S, H, self._head_dim)\n", "\n", " # Attention: [B, H, P] from selection\n", " attn = sel_w.mean(dim=2)\n", " attn = attn / (attn.sum(dim=-1, keepdim=True) + 1e-8)\n", "\n", " # Aggregate: [B, H, P] @ [B, P, S, H, hd] -> [B, H, hd]\n", " v_perm = v.permute(0, 3, 1, 2, 4) # [B, H, P, S, hd]\n", " v_agg = torch.einsum('bhp,bhpd->bhd', attn, v_perm.mean(dim=3))\n", "\n", " # Validity\n", " head_valid = torch.sigmoid(vol2 * 1e6)\n", " v_agg = v_agg * head_valid.unsqueeze(-1)\n", "\n", " # Output\n", " out = self._out(v_agg.reshape(B, self._fdim)) * gate\n", " out = out[:, None, None, :].expand(-1, P, S, -1)\n", "\n", " return out, vol2\n", "\n", "\n", "# ============================================================================\n", "# TRANSFORMER BLOCK\n", "# ============================================================================\n", "\n", "class GeoBlock(nn.Module):\n", " def __init__(self, fdim, edim, num_heads, mlp_ratio=4.0):\n", " super().__init__()\n", " self._norm1 = nn.LayerNorm(fdim)\n", " self._attn = Simplex4Attn(fdim, edim, num_heads)\n", " self._norm2 = nn.LayerNorm(fdim)\n", " self._mlp = nn.Sequential(\n", " nn.Linear(fdim, int(fdim * mlp_ratio)),\n", " nn.GELU(),\n", " nn.Linear(int(fdim * mlp_ratio), fdim),\n", " )\n", "\n", " def forward(self, feats, coords):\n", " \"\"\"\n", " feats: [B, P, S, fdim]\n", " coords: [B, P, S, edim]\n", " \"\"\"\n", " attn_out, vol2 = self._attn(self._norm1(feats), coords)\n", " feats = feats + attn_out\n", " feats = feats + self._mlp(self._norm2(feats))\n", " return feats, vol2\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " fdim=64,\n", " edim=8,\n", " nbase=4,\n", " geo_depth=4,\n", " attn_depth=4,\n", " num_heads=4,\n", " ):\n", " super().__init__()\n", "\n", " self.patch_size = patch_size\n", " self.num_patches = (img_size // patch_size) ** 2\n", " patch_dim = patch_size * patch_size * in_chans\n", "\n", " self._embed = GeoPatchEmbed(patch_dim, fdim, nbase, geo_depth, edim)\n", " self._pos = nn.Parameter(torch.randn(1, self.num_patches, 1, edim) * 0.02)\n", "\n", " self._blocks = nn.ModuleList([\n", " GeoBlock(fdim, edim, num_heads) for _ in range(attn_depth)\n", " ])\n", "\n", " self._norm = nn.LayerNorm(fdim)\n", " self._head = nn.Linear(fdim, num_classes)\n", "\n", " print(f\"\\nGeometricPatchworkViT (No Flatten):\")\n", " print(f\" Patches: {self.num_patches}\")\n", " print(f\" K-hierarchy sizes: {self._embed._complex._sizes}\")\n", " print(f\" Structure: [B, {self.num_patches}, {self._embed._final_size}, {fdim}]\")\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [B, C, H, W]\n", "\n", " Returns:\n", " logits: [B, num_classes]\n", " vol2_patch: [B, P, depth, max_size]\n", " vol2_attn: [B, attn_depth, H]\n", " \"\"\"\n", " B = x.shape[0]\n", "\n", " # Extract patches: [B, P, patch_dim]\n", " patches = x.unfold(2, self.patch_size, self.patch_size).unfold(3, self.patch_size, self.patch_size)\n", " patches = patches.contiguous().view(B, self.num_patches, -1)\n", "\n", " # Embed: [B, P, S, fdim], [B, P, S, edim], [B, P, depth, max_size]\n", " feats, coords, vol2_patch = self._embed(patches)\n", " coords = coords + self._pos\n", "\n", " # Blocks\n", " vol2_attn = []\n", " for blk in self._blocks:\n", " feats, vol2 = blk(feats, coords)\n", " vol2_attn.append(vol2)\n", "\n", " vol2_attn = torch.stack(vol2_attn, dim=1) # [B, attn_depth, H]\n", "\n", " # Classify\n", " logits = self._head(self._norm(feats.mean(dim=[1, 2])))\n", "\n", " return logits, vol2_patch, vol2_attn\n", "\n", "\n", "# ============================================================================\n", "# LOSS FUNCTION\n", "# ============================================================================\n", "\n", "def geometric_loss(logits, labels, vol2_patch, vol2_attn, ce_weight=1.0, validity_weight=0.1):\n", " ce = F.cross_entropy(logits, labels)\n", " validity = F.relu(-vol2_patch).mean() + F.relu(-vol2_attn).mean()\n", "\n", " total = ce_weight * ce + validity_weight * validity\n", "\n", " with torch.no_grad():\n", " info = {\n", " 'ce': ce.item(),\n", " 'validity': validity.item(),\n", " 'patch_valid': (vol2_patch > 0).float().mean().item(),\n", " 'attn_valid': (vol2_attn > 0).float().mean().item(),\n", " }\n", "\n", " return total, info\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=128, shuffle=False, num_workers=2)\n", "\n", " print(\"Building model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " fdim=64,\n", " edim=8,\n", " nbase=4,\n", " geo_depth=4,\n", " attn_depth=4,\n", " num_heads=4,\n", " ).to(device)\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\"Total params: {params:,}\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 90)\n", "\n", " for ep in range(30):\n", " model.train()\n", " ce_sum, val_sum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, vol2_p, vol2_a = model(img)\n", " loss, info = geometric_loss(logits, lab, vol2_p, vol2_a)\n", " loss.backward()\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n", " opt.step()\n", "\n", " ce_sum += info['ce'] * img.size(0)\n", " val_sum += info['validity'] * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " p_valid, a_valid = 0, 0\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, vol2_p, vol2_a = model(img)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " p_valid += (vol2_p > 0).float().mean().item() * img.size(0)\n", " a_valid += (vol2_a > 0).float().mean().item() * img.size(0)\n", "\n", " te_acc = cor / tot\n", " p_valid /= tot\n", " a_valid /= tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"Ep {ep+1:2d} | CE {ce_sum/tot:.4f} | Val {val_sum/tot:.4f} | \"\n", " f\"Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%} | \"\n", " f\"Patch {p_valid:.1%} | Attn {a_valid:.1%}\")\n", "\n", " print(\"=\" * 90)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gUA5MxPslWZV", "outputId": "92b842ab-ed95-40de-dfba-0ed2e0331337" }, "execution_count": 3, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "Building model...\n", "\n", "GeometricPatchworkViT (No Flatten):\n", " Patches: 16\n", " K-hierarchy sizes: [4, 8, 16, 32, 64]\n", " Structure: [B, 16, 64, 64]\n", "Total params: 238,922\n", "\n", "Training...\n", "==========================================================================================\n", "Ep 1 | CE 4.9395 | Val 0.0000 | Tr 70.26% | Te 76.86% | Best 76.86% | Patch 100.0% | Attn 100.0%\n", "Ep 6 | CE 2.5194 | Val 0.0000 | Tr 84.74% | Te 83.96% | Best 83.96% | Patch 100.0% | Attn 100.0%\n", "Ep 11 | CE 2.0789 | Val 0.0000 | Tr 87.31% | Te 85.96% | Best 85.96% | Patch 100.0% | Attn 100.0%\n", "Ep 16 | CE 1.7462 | Val 0.0000 | Tr 89.17% | Te 86.77% | Best 86.82% | Patch 100.0% | Attn 100.0%\n", "Ep 21 | CE 1.4106 | Val 0.0000 | Tr 91.19% | Te 87.88% | Best 88.07% | Patch 100.0% | Attn 100.0%\n", "Ep 26 | CE 1.0994 | Val 0.0000 | Tr 93.19% | Te 88.31% | Best 88.34% | Patch 100.0% | Attn 100.0%\n", "Ep 30 | CE 0.9765 | Val 0.0000 | Tr 94.16% | Te 88.27% | Best 88.37% | Patch 100.0% | Attn 100.0%\n", "==========================================================================================\n", "Best: 88.37%\n" ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork - K-Simplex as Channels\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " \"\"\"verts: [..., nv, edim]\"\"\"\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", "\n", " vol2 = self._prefactor * torch.linalg.det(cm)\n", "\n", " return d2_pairs, vol2\n", "\n", "\n", "# ============================================================================\n", "# K-SIMPLEX CHANNEL ENCODER\n", "# ============================================================================\n", "\n", "class KSimplexChannel(nn.Module):\n", " \"\"\"\n", " Encodes input into a single k-simplex representation.\n", " Output is the geometric features: [d², vol²] flattened.\n", " \"\"\"\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, k, in_dim, edim):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", " self._edim = edim\n", "\n", " self._cm = CMValidator(k)\n", " self._out_dim = self._cm._npairs + 1 # distances + volume\n", "\n", " # Factory template\n", " factory = SimplexFactory(k=k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " self.register_buffer('_template', factory.build_torch(dtype=torch.float32))\n", "\n", " # Input → vertex deformations\n", " self._to_deform = nn.Linear(in_dim, self._nv * edim)\n", "\n", " @property\n", " def out_dim(self):\n", " return self._out_dim\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [..., in_dim]\n", "\n", " Returns:\n", " geo: [..., npairs + 1] (d² pairs + vol²)\n", " vol2: [...] (for validity loss)\n", " \"\"\"\n", " # Deform template\n", " deform = self._to_deform(x).unflatten(-1, (self._nv, self._edim))\n", " verts = self._template + self.BASE_DEFORM * deform\n", "\n", " # CM validation\n", " d2, vol2 = self._cm(verts)\n", "\n", " # Geometric features\n", " geo = torch.cat([d2, vol2.unsqueeze(-1)], dim=-1)\n", "\n", " return geo, vol2\n", "\n", "\n", "# ============================================================================\n", "# PATCH TO K-SIMPLEX CHANNELS\n", "# ============================================================================\n", "\n", "class PatchToKChannels(nn.Module):\n", " \"\"\"\n", " Converts a patch into k-simplex channel representation.\n", "\n", " Input: [B, P_h, P_w, C*H*W]\n", " Output: [B, P_h, P_w, K, F] where K=depth k-levels, F=features per level\n", " \"\"\"\n", " def __init__(self, patch_dim, depth, edim):\n", " super().__init__()\n", " self._depth = depth\n", " self._edim = edim\n", "\n", " # Project patch to intermediate\n", " hidden = max(patch_dim, 64)\n", " self._proj = nn.Sequential(\n", " nn.Linear(patch_dim, hidden),\n", " nn.LayerNorm(hidden),\n", " nn.GELU(),\n", " )\n", "\n", " # K-simplex encoders: k=1, 2, 3, 4, ...\n", " self._k_encoders = nn.ModuleList([\n", " KSimplexChannel(k=k+1, in_dim=hidden, edim=edim)\n", " for k in range(depth)\n", " ])\n", "\n", " # Feature dim per level (varies by k: npairs + 1)\n", " self._k_dims = [enc.out_dim for enc in self._k_encoders]\n", " # k=1: 1+1=2, k=2: 3+1=4, k=3: 6+1=7, k=4: 10+1=11\n", "\n", " # Pad to uniform size for stacking\n", " self._max_dim = max(self._k_dims)\n", "\n", " def forward(self, patches):\n", " \"\"\"\n", " patches: [B, P_h, P_w, patch_dim]\n", "\n", " Returns:\n", " k_channels: [B, P_h, P_w, K, F] K=depth, F=max_dim\n", " vol2: [B, P_h, P_w, K] for loss\n", " \"\"\"\n", " # Project\n", " h = self._proj(patches) # [B, P_h, P_w, hidden]\n", "\n", " # Encode each k-level\n", " geo_list = []\n", " vol2_list = []\n", "\n", " for enc in self._k_encoders:\n", " geo, vol2 = enc(h) # geo: [..., k_dim], vol2: [...]\n", "\n", " # Pad to max_dim\n", " pad_size = self._max_dim - geo.shape[-1]\n", " if pad_size > 0:\n", " geo = F.pad(geo, (0, pad_size))\n", "\n", " geo_list.append(geo)\n", " vol2_list.append(vol2)\n", "\n", " # Stack: [B, P_h, P_w, K, F]\n", " k_channels = torch.stack(geo_list, dim=-2)\n", " vol2 = torch.stack(vol2_list, dim=-1) # [B, P_h, P_w, K]\n", "\n", " return k_channels, vol2\n", "\n", "\n", "# ============================================================================\n", "# K-SIMPLEX CHANNEL ATTENTION\n", "# ============================================================================\n", "\n", "class KChannelAttention(nn.Module):\n", " \"\"\"\n", " Attention over patches using their k-simplex channel structure.\n", "\n", " Input: [B, P_h, P_w, K, F]\n", " Output: [B, P_h, P_w, K, F]\n", " \"\"\"\n", " def __init__(self, depth, feat_dim, num_heads=4):\n", " super().__init__()\n", " self._depth = depth\n", " self._feat_dim = feat_dim\n", " self._num_heads = num_heads\n", "\n", " # Total features across k-channels\n", " total_dim = depth * feat_dim\n", "\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._to_qkv = nn.Linear(total_dim, 3 * total_dim)\n", " self._out = nn.Linear(total_dim, total_dim)\n", "\n", " self._scale = (total_dim // num_heads) ** -0.5\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [B, P_h, P_w, K, F]\n", " \"\"\"\n", " B, Ph, Pw, K, F = x.shape\n", "\n", " # Flatten k-channels: [B, P_h, P_w, K*F]\n", " x_flat = x.flatten(-2)\n", "\n", " # Reshape to sequence: [B, P_h*P_w, K*F]\n", " x_seq = x_flat.view(B, Ph * Pw, K * F)\n", "\n", " # Attention\n", " x_norm = self._norm(x_seq)\n", " qkv = self._to_qkv(x_norm).chunk(3, dim=-1)\n", " q, k, v = [t.view(B, Ph * Pw, self._num_heads, -1).transpose(1, 2) for t in qkv]\n", "\n", " attn = (q @ k.transpose(-2, -1)) * self._scale\n", " attn = attn.softmax(dim=-1)\n", "\n", " out = (attn @ v).transpose(1, 2).reshape(B, Ph * Pw, K * F)\n", " out = self._out(out)\n", "\n", " # Reshape back: [B, P_h, P_w, K, F]\n", " out = out.view(B, Ph, Pw, K, F)\n", "\n", " return x + out\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=28,\n", " patch_size=7,\n", " in_chans=1,\n", " num_classes=10,\n", " depth=4, # k=1,2,3,4\n", " edim=8,\n", " num_heads=4,\n", " num_blocks=4,\n", " ):\n", " super().__init__()\n", "\n", " self._patch_size = patch_size\n", " self._ph = img_size // patch_size\n", " self._pw = img_size // patch_size\n", " self._patch_dim = patch_size * patch_size * in_chans\n", "\n", " # Patch to k-channels\n", " self._patch_enc = PatchToKChannels(self._patch_dim, depth, edim)\n", " self._max_dim = self._patch_enc._max_dim\n", "\n", " # Position embedding: [1, P_h, P_w, K, F]\n", " self._pos = nn.Parameter(torch.randn(1, self._ph, self._pw, depth, self._max_dim) * 0.02)\n", "\n", " # Attention blocks\n", " self._blocks = nn.ModuleList([\n", " KChannelAttention(depth, self._max_dim, num_heads)\n", " for _ in range(num_blocks)\n", " ])\n", "\n", " # Classification\n", " self._norm = nn.LayerNorm(depth * self._max_dim)\n", " self._head = nn.Linear(depth * self._max_dim, num_classes)\n", "\n", " print(f\"\\nGeometricPatchworkViT:\")\n", " print(f\" Image: {img_size}×{img_size}, Patch: {patch_size}×{patch_size}\")\n", " print(f\" Grid: {self._ph}×{self._pw} = {self._ph * self._pw} patches\")\n", " print(f\" K-simplex channels: k=1..{depth}\")\n", " print(f\" Feature dims per k: {self._patch_enc._k_dims}\")\n", " print(f\" Structure: [B, {self._ph}, {self._pw}, {depth}, {self._max_dim}]\")\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [B, C, H, W]\n", "\n", " Returns:\n", " logits: [B, num_classes]\n", " vol2: [B, P_h, P_w, K]\n", " \"\"\"\n", " B = x.shape[0]\n", "\n", " # Extract patches: [B, P_h, P_w, patch_dim]\n", " patches = x.unfold(2, self._patch_size, self._patch_size) \\\n", " .unfold(3, self._patch_size, self._patch_size)\n", " patches = patches.permute(0, 2, 3, 1, 4, 5).contiguous()\n", " patches = patches.view(B, self._ph, self._pw, -1)\n", "\n", " # Encode to k-channels: [B, P_h, P_w, K, F]\n", " k_channels, vol2 = self._patch_enc(patches)\n", "\n", " # Add position\n", " k_channels = k_channels + self._pos\n", "\n", " # Attention blocks\n", " for blk in self._blocks:\n", " k_channels = blk(k_channels)\n", "\n", " # Classify: mean over spatial, flatten k-channels\n", " pooled = k_channels.mean(dim=[1, 2]) # [B, K, F]\n", " pooled = pooled.flatten(1) # [B, K*F]\n", " logits = self._head(self._norm(pooled))\n", "\n", " return logits, vol2\n", "\n", "\n", "# ============================================================================\n", "# LOSS\n", "# ============================================================================\n", "\n", "def geometric_loss(logits, labels, vol2, ce_weight=1.0, validity_weight=0.1):\n", " ce = F.cross_entropy(logits, labels)\n", " validity = F.relu(-vol2).mean()\n", "\n", " total = ce_weight * ce + validity_weight * validity\n", "\n", " with torch.no_grad():\n", " info = {\n", " 'ce': ce.item(),\n", " 'validity': validity.item(),\n", " 'valid_rate': (vol2 > 0).float().mean().item(),\n", " }\n", "\n", " return total, info\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.2860,), (0.3530,))\n", " ])\n", "\n", " train_ds = datasets.FashionMNIST('./data', train=True, download=True, transform=transform)\n", " test_ds = datasets.FashionMNIST('./data', train=False, download=True, transform=transform)\n", " train_dl = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=2)\n", " test_dl = DataLoader(test_ds, batch_size=256, shuffle=False, num_workers=2)\n", "\n", " print(\"Building model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=28,\n", " patch_size=2,\n", " in_chans=1,\n", " num_classes=10,\n", " depth=4,\n", " edim=8,\n", " num_heads=4,\n", " num_blocks=4,\n", " ).to(device)\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\"Params: {params:,}\")\n", "\n", " model = torch.compile(model, mode='reduce-overhead')\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 85)\n", "\n", " for ep in range(30):\n", " model.train()\n", " ce_sum, val_sum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device), lab.to(device)\n", "\n", " opt.zero_grad()\n", " logits, vol2 = model(img)\n", " loss, info = geometric_loss(logits, lab, vol2)\n", " loss.backward()\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n", " opt.step()\n", "\n", " ce_sum += info['ce'] * img.size(0)\n", " val_sum += info['validity'] * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", "\n", " model.eval()\n", " cor, tot, valid_sum = 0, 0, 0\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device), lab.to(device)\n", " logits, vol2 = model(img)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " valid_sum += (vol2 > 0).float().mean().item() * img.size(0)\n", "\n", " te_acc = cor / tot\n", " valid_rate = valid_sum / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " if ep % 5 == 0 or ep == 29:\n", " print(f\"Ep {ep+1:2d} | CE {ce_sum/tot:.4f} | Val {val_sum/tot:.4f} | \"\n", " f\"Tr {tr_acc:.2%} | Te {te_acc:.2%} | Best {best:.2%} | Valid {valid_rate:.1%}\")\n", "\n", " print(\"=\" * 85)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "kSPu4uK7zHZq", "outputId": "0ae2ffc4-415b-4dd2-ad48-e36d63a7ae7e" }, "execution_count": 5, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "Building model...\n", "\n", "GeometricPatchworkViT:\n", " Image: 28×28, Patch: 2×2\n", " Grid: 14×14 = 196 patches\n", " K-simplex channels: k=1..4\n", " Feature dims per k: [2, 4, 7, 11]\n", " Structure: [B, 14, 14, 4, 11]\n", "Params: 48,922\n", "\n", "Training...\n", "=====================================================================================\n" ] }, { "output_type": "stream", "name": "stderr", "text": [ "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n", "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n", "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n" ] }, { "output_type": "stream", "name": "stdout", "text": [ "Ep 1 | CE 7.3460 | Val 0.0000 | Tr 53.06% | Te 77.26% | Best 77.26% | Valid 100.0%\n", "Ep 6 | CE 2.2888 | Val 0.0000 | Tr 85.96% | Te 85.46% | Best 85.46% | Valid 100.0%\n", "Ep 11 | CE 1.8840 | Val 0.0000 | Tr 88.44% | Te 87.16% | Best 87.77% | Valid 100.0%\n", "Ep 16 | CE 1.6329 | Val 0.0000 | Tr 89.96% | Te 87.67% | Best 88.12% | Valid 100.0%\n", "Ep 21 | CE 1.4449 | Val 0.0000 | Tr 91.02% | Te 88.76% | Best 88.82% | Valid 100.0%\n", "Ep 26 | CE 1.2977 | Val 0.0000 | Tr 92.05% | Te 88.88% | Best 89.14% | Valid 100.0%\n", "Ep 30 | CE 1.2468 | Val 0.0000 | Tr 92.40% | Te 89.05% | Best 89.14% | Valid 100.0%\n", "=====================================================================================\n", "Best: 89.14%\n" ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork - CIFAR-10, Cross-Attention, Compiled\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "import time\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", "\n", " vol2 = self._prefactor * torch.linalg.det(cm)\n", "\n", " return d2_pairs, vol2\n", "\n", "\n", "# ============================================================================\n", "# K-SIMPLEX CHANNEL ENCODER\n", "# ============================================================================\n", "\n", "class KSimplexChannel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, k, in_dim, edim):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", " self._edim = edim\n", "\n", " self._cm = CMValidator(k)\n", " self._out_dim = self._cm._npairs + 1\n", "\n", " factory = SimplexFactory(k=k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " self.register_buffer('_template', factory.build_torch(dtype=torch.float32))\n", "\n", " self._to_deform = nn.Linear(in_dim, self._nv * edim)\n", "\n", " @property\n", " def out_dim(self):\n", " return self._out_dim\n", "\n", " def forward(self, x):\n", " deform = self._to_deform(x).unflatten(-1, (self._nv, self._edim))\n", " verts = self._template + self.BASE_DEFORM * deform\n", " d2, vol2 = self._cm(verts)\n", " geo = torch.cat([d2, vol2.unsqueeze(-1)], dim=-1)\n", " return geo, vol2\n", "\n", "\n", "# ============================================================================\n", "# PATCH TO K-SIMPLEX CHANNELS\n", "# ============================================================================\n", "\n", "class PatchToKChannels(nn.Module):\n", " def __init__(self, patch_dim, depth, edim, hidden=128):\n", " super().__init__()\n", " self._depth = depth\n", " self._edim = edim\n", "\n", " self._proj = nn.Sequential(\n", " nn.Linear(patch_dim, hidden),\n", " nn.LayerNorm(hidden),\n", " nn.GELU(),\n", " )\n", "\n", " self._k_encoders = nn.ModuleList([\n", " KSimplexChannel(k=k+1, in_dim=hidden, edim=edim)\n", " for k in range(depth)\n", " ])\n", "\n", " self._k_dims = [enc.out_dim for enc in self._k_encoders]\n", " self._max_dim = max(self._k_dims)\n", "\n", " def forward(self, patches):\n", " h = self._proj(patches)\n", "\n", " geo_list = []\n", " vol2_list = []\n", " d2_list = []\n", "\n", " for enc in self._k_encoders:\n", " geo, vol2 = enc(h)\n", " d2 = geo[..., :-1] # All but last (volume)\n", "\n", " pad_size = self._max_dim - geo.shape[-1]\n", " if pad_size > 0:\n", " geo = F.pad(geo, (0, pad_size))\n", "\n", " geo_list.append(geo)\n", " vol2_list.append(vol2)\n", " d2_list.append(d2.mean(dim=-1)) # Mean d² per k\n", "\n", " k_channels = torch.stack(geo_list, dim=-2)\n", " vol2 = torch.stack(vol2_list, dim=-1)\n", " d2_mean = torch.stack(d2_list, dim=-1)\n", "\n", " return k_channels, vol2, d2_mean\n", "\n", "\n", "# ============================================================================\n", "# K-CHANNEL CROSS-ATTENTION\n", "# ============================================================================\n", "\n", "class KChannelCrossAttention(nn.Module):\n", " \"\"\"\n", " Cross-attention between k-levels.\n", " Each k-level attends to all other k-levels.\n", " \"\"\"\n", " def __init__(self, depth, feat_dim, num_heads=4):\n", " super().__init__()\n", " self._depth = depth\n", " self._feat_dim = feat_dim\n", " self._num_heads = num_heads\n", " self._head_dim = feat_dim // num_heads\n", "\n", " self._norm_q = nn.LayerNorm(feat_dim)\n", " self._norm_kv = nn.LayerNorm(feat_dim)\n", "\n", " self._to_q = nn.Linear(feat_dim, feat_dim)\n", " self._to_k = nn.Linear(feat_dim, feat_dim)\n", " self._to_v = nn.Linear(feat_dim, feat_dim)\n", " self._out = nn.Linear(feat_dim, feat_dim)\n", "\n", " self._scale = self._head_dim ** -0.5\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [B, P_h, P_w, K, F]\n", "\n", " Cross-attention across K dimension for each spatial location.\n", " \"\"\"\n", " B, Ph, Pw, K, F = x.shape\n", "\n", " # Reshape: [B*Ph*Pw, K, F]\n", " x_flat = x.view(B * Ph * Pw, K, F)\n", "\n", " q = self._to_q(self._norm_q(x_flat))\n", " k = self._to_k(self._norm_kv(x_flat))\n", " v = self._to_v(self._norm_kv(x_flat))\n", "\n", " # Multi-head: [B*Ph*Pw, K, H, D] -> [B*Ph*Pw, H, K, D]\n", " q = q.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", " k = k.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", " v = v.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", "\n", " attn = (q @ k.transpose(-2, -1)) * self._scale\n", " attn = attn.softmax(dim=-1)\n", "\n", " out = (attn @ v).transpose(1, 2).reshape(B * Ph * Pw, K, F)\n", " out = self._out(out)\n", "\n", " return x + out.view(B, Ph, Pw, K, F), attn.view(B, Ph, Pw, self._num_heads, K, K)\n", "\n", "\n", "# ============================================================================\n", "# SPATIAL ATTENTION\n", "# ============================================================================\n", "\n", "class SpatialAttention(nn.Module):\n", " \"\"\"\n", " Attention across spatial patches, preserving k-channel structure.\n", " \"\"\"\n", " def __init__(self, depth, feat_dim, num_heads=4):\n", " super().__init__()\n", " self._num_heads = num_heads\n", "\n", " total_dim = depth * feat_dim\n", " self._head_dim = total_dim // num_heads\n", "\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._to_qkv = nn.Linear(total_dim, 3 * total_dim)\n", " self._out = nn.Linear(total_dim, total_dim)\n", "\n", " self._scale = self._head_dim ** -0.5\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [B, P_h, P_w, K, F]\n", " \"\"\"\n", " B, Ph, Pw, K, F = x.shape\n", " N = Ph * Pw\n", "\n", " x_flat = x.view(B, N, K * F)\n", " x_norm = self._norm(x_flat)\n", "\n", " qkv = self._to_qkv(x_norm).chunk(3, dim=-1)\n", " q, k, v = [t.view(B, N, self._num_heads, self._head_dim).transpose(1, 2) for t in qkv]\n", "\n", " attn = (q @ k.transpose(-2, -1)) * self._scale\n", " attn = attn.softmax(dim=-1)\n", "\n", " out = (attn @ v).transpose(1, 2).reshape(B, N, K * F)\n", " out = self._out(out).view(B, Ph, Pw, K, F)\n", "\n", " return x + out, attn\n", "\n", "\n", "# ============================================================================\n", "# TRANSFORMER BLOCK\n", "# ============================================================================\n", "\n", "class GeoBlock(nn.Module):\n", " def __init__(self, depth, feat_dim, num_heads, mlp_ratio=4.0):\n", " super().__init__()\n", "\n", " # Cross-attention across k-levels\n", " self._k_attn = KChannelCrossAttention(depth, feat_dim, num_heads)\n", "\n", " # Spatial attention\n", " self._spatial_attn = SpatialAttention(depth, feat_dim, num_heads)\n", "\n", " # MLP\n", " total_dim = depth * feat_dim\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._mlp = nn.Sequential(\n", " nn.Linear(total_dim, int(total_dim * mlp_ratio)),\n", " nn.GELU(),\n", " nn.Linear(int(total_dim * mlp_ratio), total_dim),\n", " )\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [B, P_h, P_w, K, F]\n", " \"\"\"\n", " B, Ph, Pw, K, F = x.shape\n", "\n", " # K-channel cross-attention\n", " x, k_attn = self._k_attn(x)\n", "\n", " # Spatial attention\n", " x, s_attn = self._spatial_attn(x)\n", "\n", " # MLP\n", " x_flat = x.view(B, Ph, Pw, K * F)\n", " x_flat = x_flat + self._mlp(self._norm(x_flat))\n", " x = x_flat.view(B, Ph, Pw, K, F)\n", "\n", " return x, k_attn, s_attn\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=32,\n", " patch_size=8,\n", " in_chans=3,\n", " num_classes=10,\n", " depth=4,\n", " edim=8,\n", " hidden=128,\n", " num_heads=4,\n", " num_blocks=6,\n", " ):\n", " super().__init__()\n", "\n", " self._patch_size = patch_size\n", " self._ph = img_size // patch_size\n", " self._pw = img_size // patch_size\n", " self._patch_dim = patch_size * patch_size * in_chans\n", " self._depth = depth\n", "\n", " # Patch encoder\n", " self._patch_enc = PatchToKChannels(self._patch_dim, depth, edim, hidden)\n", " self._max_dim = self._patch_enc._max_dim\n", "\n", " # Project to larger feature dim\n", " self._feat_dim = 64\n", " self._proj_up = nn.Linear(self._max_dim, self._feat_dim)\n", "\n", " # Position embedding\n", " self._pos = nn.Parameter(torch.randn(1, self._ph, self._pw, depth, self._feat_dim) * 0.02)\n", "\n", " # Transformer blocks\n", " self._blocks = nn.ModuleList([\n", " GeoBlock(depth, self._feat_dim, num_heads)\n", " for _ in range(num_blocks)\n", " ])\n", "\n", " # Classification\n", " total_dim = depth * self._feat_dim\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._head = nn.Linear(total_dim, num_classes)\n", "\n", " # Store config\n", " self._config = {\n", " 'img_size': img_size,\n", " 'patch_size': patch_size,\n", " 'grid': f'{self._ph}x{self._pw}',\n", " 'depth': depth,\n", " 'k_dims': self._patch_enc._k_dims,\n", " 'feat_dim': self._feat_dim,\n", " 'num_blocks': num_blocks,\n", " }\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", "\n", " # Extract patches: [B, P_h, P_w, patch_dim]\n", " patches = x.unfold(2, self._patch_size, self._patch_size) \\\n", " .unfold(3, self._patch_size, self._patch_size)\n", " patches = patches.permute(0, 2, 3, 1, 4, 5).contiguous()\n", " patches = patches.view(B, self._ph, self._pw, -1)\n", "\n", " # Encode: [B, P_h, P_w, K, max_dim]\n", " k_channels, vol2, d2_mean = self._patch_enc(patches)\n", "\n", " # Project up: [B, P_h, P_w, K, feat_dim]\n", " k_channels = self._proj_up(k_channels)\n", " k_channels = k_channels + self._pos\n", "\n", " # Blocks\n", " k_attns, s_attns = [], []\n", " for blk in self._blocks:\n", " k_channels, k_attn, s_attn = blk(k_channels)\n", " k_attns.append(k_attn)\n", " s_attns.append(s_attn)\n", "\n", " # Classify\n", " pooled = k_channels.mean(dim=[1, 2]).flatten(1)\n", " logits = self._head(self._norm(pooled))\n", "\n", " return logits, {\n", " 'vol2': vol2, # [B, P_h, P_w, K]\n", " 'd2_mean': d2_mean, # [B, P_h, P_w, K]\n", " 'k_attns': k_attns, # List of [B, P_h, P_w, H, K, K]\n", " 's_attns': s_attns, # List of [B, H, N, N]\n", " }\n", "\n", "\n", "# ============================================================================\n", "# LOSS\n", "# ============================================================================\n", "\n", "def geometric_loss(logits, labels, info, ce_weight=1.0, validity_weight=0.1):\n", " ce = F.cross_entropy(logits, labels)\n", " vol2 = info['vol2']\n", " validity = F.relu(-vol2).mean()\n", "\n", " total = ce_weight * ce + validity_weight * validity\n", "\n", " return total, ce, validity\n", "\n", "\n", "# ============================================================================\n", "# METRICS\n", "# ============================================================================\n", "\n", "@torch.no_grad()\n", "def compute_metrics(info, depth):\n", " vol2 = info['vol2'] # [B, P_h, P_w, K]\n", " d2_mean = info['d2_mean'] # [B, P_h, P_w, K]\n", "\n", " metrics = {}\n", "\n", " # Per-k validity and volume\n", " for k in range(depth):\n", " v = vol2[..., k]\n", " d = d2_mean[..., k]\n", " metrics[f'k{k+1}_valid'] = (v > 0).float().mean().item()\n", " metrics[f'k{k+1}_vol2'] = v.mean().item()\n", " metrics[f'k{k+1}_d2'] = d.mean().item()\n", "\n", " # Overall\n", " metrics['valid_rate'] = (vol2 > 0).float().mean().item()\n", " metrics['vol2_mean'] = vol2.mean().item()\n", " metrics['vol2_std'] = vol2.std().item()\n", "\n", " # K-attention entropy (from last block)\n", " if info['k_attns']:\n", " k_attn = info['k_attns'][-1] # [B, P_h, P_w, H, K, K]\n", " k_attn_flat = k_attn.flatten(0, 3) # [B*P*H, K, K]\n", " entropy = -(k_attn_flat * (k_attn_flat + 1e-8).log()).sum(dim=-1).mean()\n", " metrics['k_attn_entropy'] = entropy.item()\n", "\n", " # Spatial attention entropy (from last block)\n", " if info['s_attns']:\n", " s_attn = info['s_attns'][-1] # [B, H, N, N]\n", " s_attn_flat = s_attn.flatten(0, 1) # [B*H, N, N]\n", " entropy = -(s_attn_flat * (s_attn_flat + 1e-8).log()).sum(dim=-1).mean()\n", " metrics['s_attn_entropy'] = entropy.item()\n", "\n", " return metrics\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " # CIFAR-10 transforms\n", " train_transform = transforms.Compose([\n", " transforms.RandomCrop(32, padding=4),\n", " transforms.RandomHorizontalFlip(),\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))\n", " ])\n", " test_transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))\n", " ])\n", "\n", " train_ds = datasets.CIFAR10('./data', train=True, download=True, transform=train_transform)\n", " test_ds = datasets.CIFAR10('./data', train=False, download=True, transform=test_transform)\n", " train_dl = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=6, pin_memory=True, persistent_workers=True)\n", " test_dl = DataLoader(test_ds, batch_size=512, shuffle=False, num_workers=4, pin_memory=True, persistent_workers=True)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=32,\n", " patch_size=8,\n", " in_chans=3,\n", " num_classes=10,\n", " depth=4,\n", " edim=16,\n", " hidden=64,\n", " num_heads=8,\n", " num_blocks=6,\n", " ).to(device)\n", "\n", " # Print config\n", " print(f\"\\nConfig:\")\n", " for k, v in model._config.items():\n", " print(f\" {k}: {v}\")\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\" params: {params:,}\")\n", "\n", " # Compile with reduce-overhead\n", " print(\"\\nCompiling model...\")\n", " model = torch.compile(model, mode=\"reduce-overhead\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=100)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 130)\n", "\n", " header = (\n", " f\"{'Ep':>3} | {'CE':>6} | {'Val':>6} | {'Tr':>6} | {'Te':>6} | {'Best':>6} | \"\n", " f\"{'k1':>5} | {'k2':>5} | {'k3':>5} | {'k4':>5} | \"\n", " f\"{'vol²':>8} | {'KAtt':>5} | {'SAtt':>5} | {'s/ep':>5}\"\n", " )\n", " print(header)\n", " print(\"-\" * 130)\n", "\n", " for ep in range(100):\n", " t0 = time.time()\n", "\n", " model.train()\n", " ce_sum, val_sum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device, non_blocking=True), lab.to(device, non_blocking=True)\n", "\n", " opt.zero_grad()\n", " logits, info = model(img)\n", " loss, ce, validity = geometric_loss(logits, lab, info)\n", " loss.backward()\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n", " opt.step()\n", "\n", " ce_sum += ce.item() * img.size(0)\n", " val_sum += validity.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " all_metrics = []\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device, non_blocking=True), lab.to(device, non_blocking=True)\n", " logits, info = model(img)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " all_metrics.append(compute_metrics(info, model._config['depth']))\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " # Aggregate metrics\n", " m = {}\n", " for key in all_metrics[0]:\n", " m[key] = sum(d[key] for d in all_metrics) / len(all_metrics)\n", "\n", " elapsed = time.time() - t0\n", "\n", " if ep % 5 == 0 or ep == 99:\n", " print(\n", " f\"{ep+1:3d} | {ce_sum/tot:6.4f} | {val_sum/tot:6.4f} | {tr_acc:6.2%} | {te_acc:6.2%} | {best:6.2%} | \"\n", " f\"{m['k1_valid']:5.1%} | {m['k2_valid']:5.1%} | {m['k3_valid']:5.1%} | {m['k4_valid']:5.1%} | \"\n", " f\"{m['vol2_mean']:8.2e} | {m.get('k_attn_entropy', 0):5.2f} | {m.get('s_attn_entropy', 0):5.2f} | {elapsed:5.1f}\"\n", " )\n", "\n", " # Detailed per-k stats every 20 epochs\n", " if ep % 20 == 0 or ep == 99:\n", " print(f\" Per-k details:\")\n", " for k in range(model._config['depth']):\n", " print(f\" k={k+1}: valid={m[f'k{k+1}_valid']:.1%} vol²={m[f'k{k+1}_vol2']:.2e} d²={m[f'k{k+1}_d2']:.4f}\")\n", "\n", " print(\"=\" * 130)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "id": "VVqUhAsd094b", "outputId": "d07620b9-a5b2-4f64-a538-cded1a6ae353" }, "execution_count": 8, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "\n", "Config:\n", " img_size: 32\n", " patch_size: 8\n", " grid: 4x4\n", " depth: 4\n", " k_dims: [2, 4, 7, 11]\n", " feat_dim: 64\n", " num_blocks: 6\n", " params: 4,874,922\n", "\n", "Compiling model...\n", "\n", "Training...\n", "==================================================================================================================================\n", " Ep | CE | Val | Tr | Te | Best | k1 | k2 | k3 | k4 | vol² | KAtt | SAtt | s/ep\n", "----------------------------------------------------------------------------------------------------------------------------------\n" ] }, { "output_type": "stream", "name": "stderr", "text": [ "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n", "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n", "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n" ] }, { "output_type": "stream", "name": "stdout", "text": [ " 1 | 10.5853 | 0.0000 | 19.10% | 25.67% | 25.67% | 100.0% | 100.0% | 100.0% | 100.0% | 3.66e-01 | 1.34 | 1.73 | 124.4\n", " Per-k details:\n", " k=1: valid=100.0% vol²=1.18e+00 d²=1.1759\n", " k=2: valid=100.0% vol²=2.69e-01 d²=1.1436\n", " k=3: valid=100.0% vol²=1.68e-02 d²=1.0621\n", " k=4: valid=100.0% vol²=5.69e-04 d²=1.0712\n", " 6 | 7.6766 | 0.0000 | 43.43% | 45.24% | 45.24% | 100.0% | 100.0% | 100.0% | 100.0% | 3.72e-01 | 1.32 | 0.35 | 5.1\n", " 11 | 6.9712 | 0.0000 | 49.01% | 50.56% | 50.70% | 100.0% | 100.0% | 100.0% | 100.0% | 3.71e-01 | 1.20 | 0.38 | 5.0\n", " 16 | 6.3937 | 0.0000 | 53.54% | 53.82% | 53.82% | 100.0% | 100.0% | 100.0% | 100.0% | 3.54e-01 | 1.04 | 0.46 | 5.0\n", " 21 | 5.8676 | 0.0000 | 57.77% | 58.21% | 58.21% | 100.0% | 100.0% | 100.0% | 100.0% | 3.49e-01 | 0.93 | 0.55 | 5.0\n", " Per-k details:\n", " k=1: valid=100.0% vol²=1.14e+00 d²=1.1359\n", " k=2: valid=100.0% vol²=2.44e-01 d²=1.1750\n", " k=3: valid=100.0% vol²=1.44e-02 d²=1.0294\n", " k=4: valid=100.0% vol²=6.06e-04 d²=1.1132\n", " 26 | 5.4143 | 0.0000 | 61.06% | 59.13% | 60.33% | 100.0% | 100.0% | 100.0% | 100.0% | 3.39e-01 | 0.88 | 0.72 | 5.1\n", " 31 | 4.9114 | 0.0000 | 64.64% | 63.27% | 63.27% | 100.0% | 100.0% | 100.0% | 100.0% | 3.36e-01 | 0.81 | 0.84 | 5.1\n", " 36 | 4.4394 | 0.0000 | 68.23% | 65.53% | 65.53% | 100.0% | 100.0% | 100.0% | 100.0% | 3.28e-01 | 0.81 | 1.02 | 5.2\n", " 41 | 3.9912 | 0.0000 | 71.22% | 66.72% | 66.72% | 100.0% | 100.0% | 100.0% | 100.0% | 3.27e-01 | 0.79 | 1.17 | 5.1\n", " Per-k details:\n", " k=1: valid=100.0% vol²=1.07e+00 d²=1.0727\n", " k=2: valid=100.0% vol²=2.20e-01 d²=1.1206\n", " k=3: valid=100.0% vol²=1.34e-02 d²=1.0162\n", " k=4: valid=100.0% vol²=5.99e-04 d²=1.1258\n", " 46 | 3.4967 | 0.0000 | 74.78% | 67.02% | 67.40% | 100.0% | 100.0% | 100.0% | 100.0% | 3.17e-01 | 0.82 | 1.30 | 5.0\n", " 51 | 3.0084 | 0.0000 | 78.43% | 67.87% | 68.08% | 100.0% | 100.0% | 100.0% | 100.0% | 3.20e-01 | 0.83 | 1.39 | 5.0\n" ] }, { "output_type": "error", "ename": "KeyboardInterrupt", "evalue": "", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipython-input-1997069037.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 552\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 553\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"__main__\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 554\u001b[0;31m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m/tmp/ipython-input-1997069037.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 499\u001b[0m \u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 500\u001b[0m \u001b[0mloss\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mce\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalidity\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgeometric_loss\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogits\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlab\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfo\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 501\u001b[0;31m \u001b[0mloss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbackward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 502\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mutils\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclip_grad_norm_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparameters\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1.0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 503\u001b[0m \u001b[0mopt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/_tensor.py\u001b[0m in \u001b[0;36mbackward\u001b[0;34m(self, gradient, retain_graph, create_graph, inputs)\u001b[0m\n\u001b[1;32m 623\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 624\u001b[0m )\n\u001b[0;32m--> 625\u001b[0;31m torch.autograd.backward(\n\u001b[0m\u001b[1;32m 626\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgradient\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mretain_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcreate_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 627\u001b[0m )\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/autograd/__init__.py\u001b[0m in \u001b[0;36mbackward\u001b[0;34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[0m\n\u001b[1;32m 352\u001b[0m \u001b[0;31m# some Python versions print out the first line of a multi-line function\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 353\u001b[0m \u001b[0;31m# calls in the traceback and some print out the last line\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 354\u001b[0;31m _engine_run_backward(\n\u001b[0m\u001b[1;32m 355\u001b[0m \u001b[0mtensors\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 356\u001b[0m \u001b[0mgrad_tensors_\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/torch/autograd/graph.py\u001b[0m in \u001b[0;36m_engine_run_backward\u001b[0;34m(t_outputs, *args, **kwargs)\u001b[0m\n\u001b[1;32m 839\u001b[0m \u001b[0munregister_hooks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_register_logging_hooks_on_whole_graph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt_outputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 840\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 841\u001b[0;31m return Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass\n\u001b[0m\u001b[1;32m 842\u001b[0m \u001b[0mt_outputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 843\u001b[0m ) # Calls into the C++ engine to run the backward pass\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork - Fixed Bottleneck\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "import time\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", "\n", " vol2 = self._prefactor * torch.linalg.det(cm)\n", "\n", " return d2_pairs, vol2\n", "\n", "\n", "# ============================================================================\n", "# K-SIMPLEX CHANNEL ENCODER - FIXED: KEEPS VERTEX FEATURES\n", "# ============================================================================\n", "\n", "class KSimplexChannel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, k, in_dim, edim, feat_dim):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", " self._edim = edim\n", " self._feat_dim = feat_dim\n", "\n", " self._cm = CMValidator(k)\n", " self._geo_dim = self._cm._npairs + 1 # d² + vol²\n", "\n", " factory = SimplexFactory(k=k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " self.register_buffer('_template', factory.build_torch(dtype=torch.float32))\n", "\n", " # Input → vertex coordinates (for CM)\n", " self._to_coords = nn.Linear(in_dim, self._nv * edim)\n", "\n", " # Input → vertex features (KEPT, not discarded)\n", " self._to_feats = nn.Linear(in_dim, self._nv * feat_dim)\n", "\n", " # Geometry modulates features\n", " self._geo_gate = nn.Sequential(\n", " nn.Linear(self._geo_dim, feat_dim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Output: aggregate vertex features + geometry\n", " self._out_dim = feat_dim + self._geo_dim\n", "\n", " @property\n", " def out_dim(self):\n", " return self._out_dim\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [..., in_dim]\n", "\n", " Returns:\n", " out: [..., feat_dim + geo_dim] (vertex features + geometry)\n", " vol2: [...]\n", " \"\"\"\n", " # Vertex coordinates for CM\n", " coords = self._to_coords(x).unflatten(-1, (self._nv, self._edim))\n", " verts = self._template + self.BASE_DEFORM * coords\n", "\n", " # Vertex features (the actual learned representation)\n", " vert_feats = self._to_feats(x).unflatten(-1, (self._nv, self._feat_dim))\n", "\n", " # CM validation\n", " d2, vol2 = self._cm(verts)\n", " geo = torch.cat([d2, vol2.unsqueeze(-1)], dim=-1)\n", "\n", " # Gate features by geometry (valid simplices pass more info)\n", " gate = self._geo_gate(geo)\n", " validity = torch.sigmoid(vol2 * 1e6).unsqueeze(-1)\n", "\n", " # Aggregate vertex features (mean) with gating\n", " feat_agg = vert_feats.mean(dim=-2) * gate * validity\n", "\n", " # Output: features + geometry\n", " out = torch.cat([feat_agg, geo], dim=-1)\n", "\n", " return out, vol2, d2.mean(dim=-1)\n", "\n", "\n", "# ============================================================================\n", "# PATCH TO K-SIMPLEX CHANNELS - FIXED\n", "# ============================================================================\n", "\n", "class PatchToKChannels(nn.Module):\n", " def __init__(self, patch_dim, depth, edim, feat_dim, hidden=256):\n", " super().__init__()\n", " self._depth = depth\n", " self._edim = edim\n", " self._feat_dim = feat_dim\n", "\n", " self._proj = nn.Sequential(\n", " nn.Linear(patch_dim, hidden),\n", " nn.LayerNorm(hidden),\n", " nn.GELU(),\n", " nn.Linear(hidden, hidden),\n", " nn.LayerNorm(hidden),\n", " nn.GELU(),\n", " )\n", "\n", " self._k_encoders = nn.ModuleList([\n", " KSimplexChannel(k=k+1, in_dim=hidden, edim=edim, feat_dim=feat_dim)\n", " for k in range(depth)\n", " ])\n", "\n", " self._k_out_dims = [enc.out_dim for enc in self._k_encoders]\n", " self._max_out_dim = max(self._k_out_dims)\n", "\n", " def forward(self, patches):\n", " \"\"\"\n", " patches: [B, P_h, P_w, patch_dim]\n", "\n", " Returns:\n", " k_channels: [B, P_h, P_w, K, max_out_dim]\n", " vol2: [B, P_h, P_w, K]\n", " d2_mean: [B, P_h, P_w, K]\n", " \"\"\"\n", " h = self._proj(patches)\n", "\n", " out_list = []\n", " vol2_list = []\n", " d2_list = []\n", "\n", " for enc in self._k_encoders:\n", " out, vol2, d2_mean = enc(h)\n", "\n", " # Pad to max_out_dim\n", " pad_size = self._max_out_dim - out.shape[-1]\n", " if pad_size > 0:\n", " out = F.pad(out, (0, pad_size))\n", "\n", " out_list.append(out)\n", " vol2_list.append(vol2)\n", " d2_list.append(d2_mean)\n", "\n", " k_channels = torch.stack(out_list, dim=-2)\n", " vol2 = torch.stack(vol2_list, dim=-1)\n", " d2_mean = torch.stack(d2_list, dim=-1)\n", "\n", " return k_channels, vol2, d2_mean\n", "\n", "\n", "# ============================================================================\n", "# K-CHANNEL CROSS-ATTENTION\n", "# ============================================================================\n", "\n", "class KChannelCrossAttention(nn.Module):\n", " def __init__(self, depth, feat_dim, num_heads=4, dropout=0.1):\n", " super().__init__()\n", " self._depth = depth\n", " self._feat_dim = feat_dim\n", " self._num_heads = num_heads\n", " self._head_dim = feat_dim // num_heads\n", "\n", " self._norm_q = nn.LayerNorm(feat_dim)\n", " self._norm_kv = nn.LayerNorm(feat_dim)\n", "\n", " self._to_q = nn.Linear(feat_dim, feat_dim)\n", " self._to_k = nn.Linear(feat_dim, feat_dim)\n", " self._to_v = nn.Linear(feat_dim, feat_dim)\n", " self._out = nn.Linear(feat_dim, feat_dim)\n", " self._drop = nn.Dropout(dropout)\n", "\n", " self._scale = self._head_dim ** -0.5\n", "\n", " def forward(self, x):\n", " B, Ph, Pw, K, F = x.shape\n", "\n", " x_flat = x.view(B * Ph * Pw, K, F)\n", "\n", " q = self._to_q(self._norm_q(x_flat))\n", " k = self._to_k(self._norm_kv(x_flat))\n", " v = self._to_v(self._norm_kv(x_flat))\n", "\n", " q = q.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", " k = k.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", " v = v.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", "\n", " attn = (q @ k.transpose(-2, -1)) * self._scale\n", " attn = attn.softmax(dim=-1)\n", " attn = self._drop(attn)\n", "\n", " out = (attn @ v).transpose(1, 2).reshape(B * Ph * Pw, K, F)\n", " out = self._out(out)\n", " out = self._drop(out)\n", "\n", " return x + out.view(B, Ph, Pw, K, F)\n", "\n", "\n", "# ============================================================================\n", "# SPATIAL ATTENTION\n", "# ============================================================================\n", "\n", "class SpatialAttention(nn.Module):\n", " def __init__(self, depth, feat_dim, num_heads=4, dropout=0.1):\n", " super().__init__()\n", " self._num_heads = num_heads\n", "\n", " total_dim = depth * feat_dim\n", " self._head_dim = total_dim // num_heads\n", "\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._to_qkv = nn.Linear(total_dim, 3 * total_dim)\n", " self._out = nn.Linear(total_dim, total_dim)\n", " self._drop = nn.Dropout(dropout)\n", "\n", " self._scale = self._head_dim ** -0.5\n", "\n", " def forward(self, x):\n", " B, Ph, Pw, K, F = x.shape\n", " N = Ph * Pw\n", "\n", " x_flat = x.view(B, N, K * F)\n", " x_norm = self._norm(x_flat)\n", "\n", " qkv = self._to_qkv(x_norm).chunk(3, dim=-1)\n", " q, k, v = [t.view(B, N, self._num_heads, self._head_dim).transpose(1, 2) for t in qkv]\n", "\n", " attn = (q @ k.transpose(-2, -1)) * self._scale\n", " attn = attn.softmax(dim=-1)\n", " attn = self._drop(attn)\n", "\n", " out = (attn @ v).transpose(1, 2).reshape(B, N, K * F)\n", " out = self._out(out)\n", " out = self._drop(out)\n", "\n", " return x + out.view(B, Ph, Pw, K, F)\n", "\n", "\n", "# ============================================================================\n", "# TRANSFORMER BLOCK\n", "# ============================================================================\n", "\n", "class GeoBlock(nn.Module):\n", " def __init__(self, depth, feat_dim, num_heads, mlp_ratio=4.0, dropout=0.1):\n", " super().__init__()\n", "\n", " self._k_attn = KChannelCrossAttention(depth, feat_dim, num_heads, dropout)\n", " self._spatial_attn = SpatialAttention(depth, feat_dim, num_heads, dropout)\n", "\n", " total_dim = depth * feat_dim\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._mlp = nn.Sequential(\n", " nn.Linear(total_dim, int(total_dim * mlp_ratio)),\n", " nn.GELU(),\n", " nn.Dropout(dropout),\n", " nn.Linear(int(total_dim * mlp_ratio), total_dim),\n", " nn.Dropout(dropout),\n", " )\n", "\n", " def forward(self, x):\n", " B, Ph, Pw, K, F = x.shape\n", "\n", " x = self._k_attn(x)\n", " x = self._spatial_attn(x)\n", "\n", " x_flat = x.view(B, Ph, Pw, K * F)\n", " x_flat = x_flat + self._mlp(self._norm(x_flat))\n", " x = x_flat.view(B, Ph, Pw, K, F)\n", "\n", " return x\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=32,\n", " patch_size=4, # Smaller patches = more spatial resolution\n", " in_chans=3,\n", " num_classes=10,\n", " depth=4,\n", " edim=16, # Larger embedding dim\n", " feat_dim=64, # Feature dim per k-level\n", " hidden=256, # Larger hidden\n", " num_heads=8,\n", " num_blocks=8,\n", " dropout=0.1,\n", " ):\n", " super().__init__()\n", "\n", " self._patch_size = patch_size\n", " self._ph = img_size // patch_size\n", " self._pw = img_size // patch_size\n", " self._patch_dim = patch_size * patch_size * in_chans\n", " self._depth = depth\n", " self._feat_dim = feat_dim\n", "\n", " # Patch encoder\n", " self._patch_enc = PatchToKChannels(self._patch_dim, depth, edim, feat_dim, hidden)\n", " self._max_out_dim = self._patch_enc._max_out_dim\n", "\n", " # Project to uniform feat_dim for attention\n", " self._proj = nn.Linear(self._max_out_dim, feat_dim)\n", "\n", " # Position embedding\n", " self._pos = nn.Parameter(torch.randn(1, self._ph, self._pw, depth, feat_dim) * 0.02)\n", "\n", " # Blocks\n", " self._blocks = nn.ModuleList([\n", " GeoBlock(depth, feat_dim, num_heads, dropout=dropout)\n", " for _ in range(num_blocks)\n", " ])\n", "\n", " # Classification\n", " total_dim = depth * feat_dim\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._head = nn.Sequential(\n", " nn.Linear(total_dim, total_dim),\n", " nn.GELU(),\n", " nn.Dropout(dropout),\n", " nn.Linear(total_dim, num_classes),\n", " )\n", "\n", " self._config = {\n", " 'img_size': img_size,\n", " 'patch_size': patch_size,\n", " 'grid': f'{self._ph}x{self._pw}',\n", " 'depth': depth,\n", " 'edim': edim,\n", " 'feat_dim': feat_dim,\n", " 'k_out_dims': self._patch_enc._k_out_dims,\n", " 'num_blocks': num_blocks,\n", " 'dropout': dropout,\n", " }\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", "\n", " # Extract patches\n", " patches = x.unfold(2, self._patch_size, self._patch_size) \\\n", " .unfold(3, self._patch_size, self._patch_size)\n", " patches = patches.permute(0, 2, 3, 1, 4, 5).contiguous()\n", " patches = patches.view(B, self._ph, self._pw, -1)\n", "\n", " # Encode\n", " k_channels, vol2, d2_mean = self._patch_enc(patches)\n", "\n", " # Project to uniform dim\n", " k_channels = self._proj(k_channels)\n", " k_channels = k_channels + self._pos\n", "\n", " # Blocks\n", " for blk in self._blocks:\n", " k_channels = blk(k_channels)\n", "\n", " # Classify\n", " pooled = k_channels.mean(dim=[1, 2]).flatten(1)\n", " logits = self._head(self._norm(pooled))\n", "\n", " return logits, {'vol2': vol2, 'd2_mean': d2_mean}\n", "\n", "\n", "# ============================================================================\n", "# LOSS & METRICS\n", "# ============================================================================\n", "\n", "def geometric_loss(logits, labels, info, ce_weight=1.0, validity_weight=0.1):\n", " ce = F.cross_entropy(logits, labels)\n", " vol2 = info['vol2']\n", " validity = F.relu(-vol2).mean()\n", " total = ce_weight * ce + validity_weight * validity\n", " return total, ce, validity\n", "\n", "\n", "@torch.no_grad()\n", "def compute_metrics(info, depth):\n", " vol2 = info['vol2']\n", " d2_mean = info['d2_mean']\n", "\n", " m = {'valid_rate': (vol2 > 0).float().mean().item()}\n", " for k in range(depth):\n", " m[f'k{k+1}_valid'] = (vol2[..., k] > 0).float().mean().item()\n", " m[f'k{k+1}_vol2'] = vol2[..., k].mean().item()\n", " m[f'k{k+1}_d2'] = d2_mean[..., k].mean().item()\n", " return m\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " train_transform = transforms.Compose([\n", " transforms.RandomCrop(32, padding=4),\n", " transforms.RandomHorizontalFlip(),\n", " transforms.AutoAugment(transforms.AutoAugmentPolicy.CIFAR10),\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))\n", " ])\n", " test_transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))\n", " ])\n", "\n", " train_ds = datasets.CIFAR10('./data', train=True, download=True, transform=train_transform)\n", " test_ds = datasets.CIFAR10('./data', train=False, download=True, transform=test_transform)\n", " train_dl = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=6, pin_memory=True)\n", " test_dl = DataLoader(test_ds, batch_size=512, shuffle=False, num_workers=4, pin_memory=True)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=32,\n", " patch_size=8, # 8x8 grid\n", " in_chans=3,\n", " num_classes=10,\n", " depth=4,\n", " edim=32,\n", " feat_dim=128,\n", " hidden=512,\n", " num_heads=4,\n", " num_blocks=8,\n", " dropout=0.1,\n", " ).to(device)\n", "\n", " print(f\"\\nConfig:\")\n", " for k, v in model._config.items():\n", " print(f\" {k}: {v}\")\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\" params: {params:,}\")\n", "\n", " print(\"\\nCompiling...\")\n", " model = torch.compile(model, mode=\"reduce-overhead\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=100)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 120)\n", " print(f\"{'Ep':>3} | {'CE':>6} | {'Val':>6} | {'Tr':>6} | {'Te':>6} | {'Best':>6} | \"\n", " f\"{'k1':>5} | {'k2':>5} | {'k3':>5} | {'k4':>5} | {'s/ep':>5}\")\n", " print(\"-\" * 120)\n", "\n", " for ep in range(100):\n", " t0 = time.time()\n", "\n", " model.train()\n", " ce_sum, val_sum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device, non_blocking=True), lab.to(device, non_blocking=True)\n", "\n", " opt.zero_grad()\n", " logits, info = model(img)\n", " loss, ce, val = geometric_loss(logits, lab, info)\n", " loss.backward()\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n", " opt.step()\n", "\n", " ce_sum += ce.item() * img.size(0)\n", " val_sum += val.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " metrics_agg = []\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device, non_blocking=True), lab.to(device, non_blocking=True)\n", " logits, info = model(img)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " metrics_agg.append(compute_metrics(info, model._config['depth']))\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " m = {k: sum(d[k] for d in metrics_agg) / len(metrics_agg) for k in metrics_agg[0]}\n", " elapsed = time.time() - t0\n", "\n", " if ep % 5 == 0 or ep == 99:\n", " print(f\"{ep+1:3d} | {ce_sum/tot:6.4f} | {val_sum/tot:6.4f} | {tr_acc:6.2%} | {te_acc:6.2%} | {best:6.2%} | \"\n", " f\"{m['k1_valid']:5.1%} | {m['k2_valid']:5.1%} | {m['k3_valid']:5.1%} | {m['k4_valid']:5.1%} | {elapsed:5.1f}\")\n", "\n", " if ep % 20 == 0 or ep == 99:\n", " print(f\" k-details: \" + \" | \".join(\n", " f\"k{k+1}: vol²={m[f'k{k+1}_vol2']:.2e} d²={m[f'k{k+1}_d2']:.3f}\"\n", " for k in range(model._config['depth'])\n", " ))\n", "\n", " print(\"=\" * 120)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "cellView": "form", "id": "bSdSvsYBCvvh", "outputId": "7036a621-52ba-4a1b-df76-ee9e6954d26d" }, "execution_count": 9, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "\n", "Config:\n", " img_size: 32\n", " patch_size: 4\n", " grid: 8x8\n", " depth: 4\n", " edim: 16\n", " feat_dim: 64\n", " k_out_dims: [66, 68, 71, 75]\n", " num_blocks: 8\n", " dropout: 0.1\n", " params: 6,912,362\n", "\n", "Compiling...\n", "\n", "Training...\n", "========================================================================================================================\n", " Ep | CE | Val | Tr | Te | Best | k1 | k2 | k3 | k4 | s/ep\n", "------------------------------------------------------------------------------------------------------------------------\n" ] }, { "output_type": "stream", "name": "stderr", "text": [ "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n", "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n", "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n", "/usr/local/lib/python3.12/dist-packages/torch/_inductor/lowering.py:1744: FutureWarning: `torch._prims_common.check` is deprecated and will be removed in the future. Please use `torch._check*` functions instead.\n", " check(\n" ] }, { "output_type": "stream", "name": "stdout", "text": [ " 1 | 10.5158 | 0.0000 | 19.14% | 26.45% | 26.45% | 100.0% | 100.0% | 100.0% | 100.0% | 201.8\n", " k-details: k1: vol²=9.10e-01 d²=0.910 | k2: vol²=1.63e-01 d²=0.932 | k3: vol²=1.17e-02 d²=0.941 | k4: vol²=4.67e-04 d²=0.955\n", " 6 | 7.9527 | 0.0000 | 41.10% | 51.10% | 51.10% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 11 | 7.0038 | 0.0000 | 49.44% | 58.15% | 58.15% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 16 | 6.3388 | 0.0000 | 54.49% | 62.76% | 62.76% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 21 | 5.9750 | 0.0000 | 57.18% | 65.30% | 66.68% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " k-details: k1: vol²=9.02e-01 d²=0.902 | k2: vol²=1.03e-01 d²=0.750 | k3: vol²=4.54e-03 d²=0.680 | k4: vol²=1.28e-04 d²=0.691\n", " 26 | 5.6326 | 0.0000 | 59.76% | 69.83% | 69.83% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 31 | 5.3184 | 0.0000 | 62.05% | 70.82% | 71.15% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 36 | 5.0402 | 0.0000 | 63.99% | 71.96% | 72.37% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 41 | 4.7983 | 0.0000 | 65.98% | 74.46% | 74.46% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " k-details: k1: vol²=1.03e+00 d²=1.028 | k2: vol²=1.17e-01 d²=0.804 | k3: vol²=4.87e-03 d²=0.689 | k4: vol²=1.42e-04 d²=0.712\n", " 46 | 4.5811 | 0.0000 | 67.53% | 75.72% | 75.72% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 51 | 4.3392 | 0.0000 | 69.25% | 76.94% | 77.45% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 56 | 4.0985 | 0.0000 | 70.81% | 78.93% | 78.93% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 61 | 3.8610 | 0.0000 | 72.54% | 78.36% | 79.45% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " k-details: k1: vol²=1.01e+00 d²=1.008 | k2: vol²=1.20e-01 d²=0.812 | k3: vol²=5.66e-03 d²=0.712 | k4: vol²=1.35e-04 d²=0.704\n", " 66 | 3.7021 | 0.0000 | 73.70% | 80.21% | 80.46% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 71 | 3.5193 | 0.0000 | 74.97% | 81.27% | 81.27% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 76 | 3.3362 | 0.0000 | 76.15% | 81.62% | 81.84% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 81 | 3.1914 | 0.0000 | 77.41% | 82.43% | 82.43% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " k-details: k1: vol²=9.96e-01 d²=0.996 | k2: vol²=1.19e-01 d²=0.810 | k3: vol²=6.31e-03 d²=0.738 | k4: vol²=1.52e-04 d²=0.727\n", " 86 | 3.0744 | 0.0000 | 78.21% | 83.17% | 83.17% | 100.0% | 100.0% | 100.0% | 100.0% | 20.8\n", " 91 | 2.9843 | 0.0000 | 78.73% | 83.02% | 83.17% | 100.0% | 100.0% | 100.0% | 100.0% | 20.9\n", " 96 | 2.9350 | 0.0000 | 79.13% | 83.19% | 83.19% | 100.0% | 100.0% | 100.0% | 100.0% | 20.9\n", "100 | 2.9431 | 0.0000 | 79.19% | 83.11% | 83.19% | 100.0% | 100.0% | 100.0% | 100.0% | 20.9\n", " k-details: k1: vol²=9.94e-01 d²=0.994 | k2: vol²=1.18e-01 d²=0.805 | k3: vol²=6.45e-03 d²=0.743 | k4: vol²=1.56e-04 d²=0.735\n", "========================================================================================================================\n", "Best: 83.19%\n" ] } ] }, { "cell_type": "code", "source": [ "#@title Geometric Patchwork - Fixed Bottleneck\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "import math\n", "from itertools import combinations\n", "import time\n", "\n", "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n", "print(f\"Device: {device}\")\n", "\n", "from geovocab2.shapes.factory.simplex_factory import SimplexFactory\n", "\n", "# ============================================================================\n", "# CAYLEY-MENGER VALIDATOR\n", "# ============================================================================\n", "\n", "class CMValidator(nn.Module):\n", " def __init__(self, k):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", "\n", " pairs = list(combinations(range(self._nv), 2))\n", " self._npairs = len(pairs)\n", " self.register_buffer('_pi', torch.tensor([p[0] for p in pairs], dtype=torch.long))\n", " self.register_buffer('_pj', torch.tensor([p[1] for p in pairs], dtype=torch.long))\n", "\n", " sign = (-1.0) ** (k + 1)\n", " fact = math.factorial(k)\n", " self._prefactor = sign / ((2.0 ** k) * (fact ** 2))\n", "\n", " def forward(self, verts):\n", " gram = torch.einsum('...ve,...we->...vw', verts, verts)\n", " norms = torch.diagonal(gram, dim1=-2, dim2=-1)\n", " d2_mat = norms.unsqueeze(-1) + norms.unsqueeze(-2) - 2 * gram\n", " d2_mat = F.relu(d2_mat)\n", "\n", " d2_pairs = d2_mat[..., self._pi, self._pj]\n", "\n", " shape = d2_mat.shape[:-2]\n", " V = d2_mat.shape[-1]\n", " cm = torch.zeros(*shape, V+1, V+1, device=d2_mat.device, dtype=d2_mat.dtype)\n", " cm[..., 0, 1:] = 1.0\n", " cm[..., 1:, 0] = 1.0\n", " cm[..., 1:, 1:] = d2_mat\n", "\n", " vol2 = self._prefactor * torch.linalg.det(cm)\n", "\n", " return d2_pairs, vol2\n", "\n", "\n", "# ============================================================================\n", "# K-SIMPLEX CHANNEL ENCODER - FIXED: KEEPS VERTEX FEATURES\n", "# ============================================================================\n", "\n", "class KSimplexChannel(nn.Module):\n", " BASE_DEFORM = 0.05\n", "\n", " def __init__(self, k, in_dim, edim, feat_dim):\n", " super().__init__()\n", " self._k = k\n", " self._nv = k + 1\n", " self._edim = edim\n", " self._feat_dim = feat_dim\n", "\n", " self._cm = CMValidator(k)\n", " self._geo_dim = self._cm._npairs + 1 # d² + vol²\n", "\n", " factory = SimplexFactory(k=k, embed_dim=edim, method=\"regular\", scale=1.0)\n", " self.register_buffer('_template', factory.build_torch(dtype=torch.float32))\n", "\n", " # Input → vertex coordinates (for CM)\n", " self._to_coords = nn.Linear(in_dim, self._nv * edim)\n", "\n", " # Input → vertex features (KEPT, not discarded)\n", " self._to_feats = nn.Linear(in_dim, self._nv * feat_dim)\n", "\n", " # Geometry modulates features\n", " self._geo_gate = nn.Sequential(\n", " nn.Linear(self._geo_dim, feat_dim),\n", " nn.Sigmoid(),\n", " )\n", "\n", " # Output: aggregate vertex features + geometry\n", " self._out_dim = feat_dim + self._geo_dim\n", "\n", " @property\n", " def out_dim(self):\n", " return self._out_dim\n", "\n", " def forward(self, x):\n", " \"\"\"\n", " x: [..., in_dim]\n", "\n", " Returns:\n", " out: [..., feat_dim + geo_dim] (vertex features + geometry)\n", " vol2: [...]\n", " \"\"\"\n", " # Vertex coordinates for CM\n", " coords = self._to_coords(x).unflatten(-1, (self._nv, self._edim))\n", " verts = self._template + self.BASE_DEFORM * coords\n", "\n", " # Vertex features (the actual learned representation)\n", " vert_feats = self._to_feats(x).unflatten(-1, (self._nv, self._feat_dim))\n", "\n", " # CM validation\n", " d2, vol2 = self._cm(verts)\n", " geo = torch.cat([d2, vol2.unsqueeze(-1)], dim=-1)\n", "\n", " # Gate features by geometry (valid simplices pass more info)\n", " gate = self._geo_gate(geo)\n", " validity = torch.sigmoid(vol2 * 1e6).unsqueeze(-1)\n", "\n", " # Aggregate vertex features (mean) with gating\n", " feat_agg = vert_feats.mean(dim=-2) * gate * validity\n", "\n", " # Output: features + geometry\n", " out = torch.cat([feat_agg, geo], dim=-1)\n", "\n", " return out, vol2, d2.mean(dim=-1)\n", "\n", "\n", "# ============================================================================\n", "# PATCH TO K-SIMPLEX CHANNELS - FIXED\n", "# ============================================================================\n", "\n", "class PatchToKChannels(nn.Module):\n", " def __init__(self, patch_dim, depth, edim, feat_dim, hidden=256):\n", " super().__init__()\n", " self._depth = depth\n", " self._edim = edim\n", " self._feat_dim = feat_dim\n", "\n", " self._proj = nn.Sequential(\n", " nn.Linear(patch_dim, hidden),\n", " nn.LayerNorm(hidden),\n", " nn.GELU(),\n", " nn.Linear(hidden, hidden),\n", " nn.LayerNorm(hidden),\n", " nn.GELU(),\n", " )\n", "\n", " self._k_encoders = nn.ModuleList([\n", " KSimplexChannel(k=k+1, in_dim=hidden, edim=edim, feat_dim=feat_dim)\n", " for k in range(depth)\n", " ])\n", "\n", " self._k_out_dims = [enc.out_dim for enc in self._k_encoders]\n", " self._max_out_dim = max(self._k_out_dims)\n", "\n", " def forward(self, patches):\n", " \"\"\"\n", " patches: [B, P_h, P_w, patch_dim]\n", "\n", " Returns:\n", " k_channels: [B, P_h, P_w, K, max_out_dim]\n", " vol2: [B, P_h, P_w, K]\n", " d2_mean: [B, P_h, P_w, K]\n", " \"\"\"\n", " h = self._proj(patches)\n", "\n", " out_list = []\n", " vol2_list = []\n", " d2_list = []\n", "\n", " for enc in self._k_encoders:\n", " out, vol2, d2_mean = enc(h)\n", "\n", " # Pad to max_out_dim\n", " pad_size = self._max_out_dim - out.shape[-1]\n", " if pad_size > 0:\n", " out = F.pad(out, (0, pad_size))\n", "\n", " out_list.append(out)\n", " vol2_list.append(vol2)\n", " d2_list.append(d2_mean)\n", "\n", " k_channels = torch.stack(out_list, dim=-2)\n", " vol2 = torch.stack(vol2_list, dim=-1)\n", " d2_mean = torch.stack(d2_list, dim=-1)\n", "\n", " return k_channels, vol2, d2_mean\n", "\n", "\n", "# ============================================================================\n", "# K-CHANNEL CROSS-ATTENTION\n", "# ============================================================================\n", "\n", "class KChannelCrossAttention(nn.Module):\n", " def __init__(self, depth, feat_dim, num_heads=4, dropout=0.1):\n", " super().__init__()\n", " self._depth = depth\n", " self._feat_dim = feat_dim\n", " self._num_heads = num_heads\n", " self._head_dim = feat_dim // num_heads\n", "\n", " self._norm_q = nn.LayerNorm(feat_dim)\n", " self._norm_kv = nn.LayerNorm(feat_dim)\n", "\n", " self._to_q = nn.Linear(feat_dim, feat_dim)\n", " self._to_k = nn.Linear(feat_dim, feat_dim)\n", " self._to_v = nn.Linear(feat_dim, feat_dim)\n", " self._out = nn.Linear(feat_dim, feat_dim)\n", " self._drop = nn.Dropout(dropout)\n", "\n", " self._scale = self._head_dim ** -0.5\n", "\n", " def forward(self, x):\n", " B, Ph, Pw, K, F = x.shape\n", "\n", " x_flat = x.view(B * Ph * Pw, K, F)\n", "\n", " q = self._to_q(self._norm_q(x_flat))\n", " k = self._to_k(self._norm_kv(x_flat))\n", " v = self._to_v(self._norm_kv(x_flat))\n", "\n", " q = q.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", " k = k.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", " v = v.view(-1, K, self._num_heads, self._head_dim).transpose(1, 2)\n", "\n", " attn = (q @ k.transpose(-2, -1)) * self._scale\n", " attn = attn.softmax(dim=-1)\n", " attn = self._drop(attn)\n", "\n", " out = (attn @ v).transpose(1, 2).reshape(B * Ph * Pw, K, F)\n", " out = self._out(out)\n", " out = self._drop(out)\n", "\n", " return x + out.view(B, Ph, Pw, K, F)\n", "\n", "\n", "# ============================================================================\n", "# SPATIAL ATTENTION\n", "# ============================================================================\n", "\n", "class SpatialAttention(nn.Module):\n", " def __init__(self, depth, feat_dim, num_heads=4, dropout=0.1):\n", " super().__init__()\n", " self._num_heads = num_heads\n", "\n", " total_dim = depth * feat_dim\n", " self._head_dim = total_dim // num_heads\n", "\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._to_qkv = nn.Linear(total_dim, 3 * total_dim)\n", " self._out = nn.Linear(total_dim, total_dim)\n", " self._drop = nn.Dropout(dropout)\n", "\n", " self._scale = self._head_dim ** -0.5\n", "\n", " def forward(self, x):\n", " B, Ph, Pw, K, F = x.shape\n", " N = Ph * Pw\n", "\n", " x_flat = x.view(B, N, K * F)\n", " x_norm = self._norm(x_flat)\n", "\n", " qkv = self._to_qkv(x_norm).chunk(3, dim=-1)\n", " q, k, v = [t.view(B, N, self._num_heads, self._head_dim).transpose(1, 2) for t in qkv]\n", "\n", " attn = (q @ k.transpose(-2, -1)) * self._scale\n", " attn = attn.softmax(dim=-1)\n", " attn = self._drop(attn)\n", "\n", " out = (attn @ v).transpose(1, 2).reshape(B, N, K * F)\n", " out = self._out(out)\n", " out = self._drop(out)\n", "\n", " return x + out.view(B, Ph, Pw, K, F)\n", "\n", "\n", "# ============================================================================\n", "# TRANSFORMER BLOCK\n", "# ============================================================================\n", "\n", "class GeoBlock(nn.Module):\n", " def __init__(self, depth, feat_dim, num_heads, mlp_ratio=4.0, dropout=0.1):\n", " super().__init__()\n", "\n", " self._k_attn = KChannelCrossAttention(depth, feat_dim, num_heads, dropout)\n", " self._spatial_attn = SpatialAttention(depth, feat_dim, num_heads, dropout)\n", "\n", " total_dim = depth * feat_dim\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._mlp = nn.Sequential(\n", " nn.Linear(total_dim, int(total_dim * mlp_ratio)),\n", " nn.GELU(),\n", " nn.Dropout(dropout),\n", " nn.Linear(int(total_dim * mlp_ratio), total_dim),\n", " nn.Dropout(dropout),\n", " )\n", "\n", " def forward(self, x):\n", " B, Ph, Pw, K, F = x.shape\n", "\n", " x = self._k_attn(x)\n", " x = self._spatial_attn(x)\n", "\n", " x_flat = x.view(B, Ph, Pw, K * F)\n", " x_flat = x_flat + self._mlp(self._norm(x_flat))\n", " x = x_flat.view(B, Ph, Pw, K, F)\n", "\n", " return x\n", "\n", "\n", "# ============================================================================\n", "# FULL MODEL\n", "# ============================================================================\n", "\n", "class GeometricPatchworkViT(nn.Module):\n", " def __init__(\n", " self,\n", " img_size=32,\n", " patch_size=4, # Smaller patches = more spatial resolution\n", " in_chans=3,\n", " num_classes=10,\n", " depth=4,\n", " edim=16, # Larger embedding dim\n", " feat_dim=64, # Feature dim per k-level\n", " hidden=256, # Larger hidden\n", " num_heads=8,\n", " num_blocks=8,\n", " dropout=0.1,\n", " ):\n", " super().__init__()\n", "\n", " self._patch_size = patch_size\n", " self._ph = img_size // patch_size\n", " self._pw = img_size // patch_size\n", " self._patch_dim = patch_size * patch_size * in_chans\n", " self._depth = depth\n", " self._feat_dim = feat_dim\n", "\n", " # Patch encoder\n", " self._patch_enc = PatchToKChannels(self._patch_dim, depth, edim, feat_dim, hidden)\n", " self._max_out_dim = self._patch_enc._max_out_dim\n", "\n", " # Project to uniform feat_dim for attention\n", " self._proj = nn.Linear(self._max_out_dim, feat_dim)\n", "\n", " # Position embedding\n", " self._pos = nn.Parameter(torch.randn(1, self._ph, self._pw, depth, feat_dim) * 0.02)\n", "\n", " # Blocks\n", " self._blocks = nn.ModuleList([\n", " GeoBlock(depth, feat_dim, num_heads, dropout=dropout)\n", " for _ in range(num_blocks)\n", " ])\n", "\n", " # Classification\n", " total_dim = depth * feat_dim\n", " self._norm = nn.LayerNorm(total_dim)\n", " self._head = nn.Sequential(\n", " nn.Linear(total_dim, total_dim),\n", " nn.GELU(),\n", " nn.Dropout(dropout),\n", " nn.Linear(total_dim, num_classes),\n", " )\n", "\n", " self._config = {\n", " 'img_size': img_size,\n", " 'patch_size': patch_size,\n", " 'grid': f'{self._ph}x{self._pw}',\n", " 'depth': depth,\n", " 'edim': edim,\n", " 'feat_dim': feat_dim,\n", " 'k_out_dims': self._patch_enc._k_out_dims,\n", " 'num_blocks': num_blocks,\n", " 'dropout': dropout,\n", " }\n", "\n", " def forward(self, x):\n", " B = x.shape[0]\n", "\n", " # Extract patches\n", " patches = x.unfold(2, self._patch_size, self._patch_size) \\\n", " .unfold(3, self._patch_size, self._patch_size)\n", " patches = patches.permute(0, 2, 3, 1, 4, 5).contiguous()\n", " patches = patches.view(B, self._ph, self._pw, -1)\n", "\n", " # Encode\n", " k_channels, vol2, d2_mean = self._patch_enc(patches)\n", "\n", " # Project to uniform dim\n", " k_channels = self._proj(k_channels)\n", " k_channels = k_channels + self._pos\n", "\n", " # Blocks\n", " for blk in self._blocks:\n", " k_channels = blk(k_channels)\n", "\n", " # Classify\n", " pooled = k_channels.mean(dim=[1, 2]).flatten(1)\n", " logits = self._head(self._norm(pooled))\n", "\n", " return logits, {'vol2': vol2, 'd2_mean': d2_mean}\n", "\n", "\n", "# ============================================================================\n", "# LOSS & METRICS\n", "# ============================================================================\n", "\n", "def geometric_loss(logits, labels, info, ce_weight=1.0, validity_weight=0.1):\n", " ce = F.cross_entropy(logits, labels)\n", " vol2 = info['vol2']\n", " validity = F.relu(-vol2).mean()\n", " total = ce_weight * ce + validity_weight * validity\n", " return total, ce, validity\n", "\n", "\n", "@torch.no_grad()\n", "def compute_metrics(info, depth):\n", " vol2 = info['vol2']\n", " d2_mean = info['d2_mean']\n", "\n", " m = {'valid_rate': (vol2 > 0).float().mean().item()}\n", " for k in range(depth):\n", " m[f'k{k+1}_valid'] = (vol2[..., k] > 0).float().mean().item()\n", " m[f'k{k+1}_vol2'] = vol2[..., k].mean().item()\n", " m[f'k{k+1}_d2'] = d2_mean[..., k].mean().item()\n", " return m\n", "\n", "\n", "# ============================================================================\n", "# TRAIN\n", "# ============================================================================\n", "\n", "def train():\n", " train_transform = transforms.Compose([\n", " transforms.RandomCrop(32, padding=4),\n", " transforms.RandomHorizontalFlip(),\n", " transforms.AutoAugment(transforms.AutoAugmentPolicy.CIFAR10),\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))\n", " ])\n", " test_transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))\n", " ])\n", "\n", " train_ds = datasets.CIFAR10('./data', train=True, download=True, transform=train_transform)\n", " test_ds = datasets.CIFAR10('./data', train=False, download=True, transform=test_transform)\n", " train_dl = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=6, pin_memory=True)\n", " test_dl = DataLoader(test_ds, batch_size=512, shuffle=False, num_workers=4, pin_memory=True)\n", "\n", " print(\"\\nBuilding model...\")\n", " model = GeometricPatchworkViT(\n", " img_size=32,\n", " patch_size=4, # 8x8 grid\n", " in_chans=3,\n", " num_classes=10,\n", " depth=4,\n", " edim=16,\n", " feat_dim=64,\n", " hidden=256,\n", " num_heads=8,\n", " num_blocks=8,\n", " dropout=0.1,\n", " ).to(device)\n", "\n", " print(f\"\\nConfig:\")\n", " for k, v in model._config.items():\n", " print(f\" {k}: {v}\")\n", "\n", " params = sum(p.numel() for p in model.parameters())\n", " print(f\" params: {params:,}\")\n", "\n", " print(\"\\nCompiling...\")\n", " model = torch.compile(model, mode=\"reduce-overhead\")\n", "\n", " opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.05)\n", " sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=100)\n", "\n", " best = 0\n", " print(\"\\nTraining...\")\n", " print(\"=\" * 120)\n", " print(f\"{'Ep':>3} | {'CE':>6} | {'Val':>6} | {'Tr':>6} | {'Te':>6} | {'Best':>6} | \"\n", " f\"{'k1':>5} | {'k2':>5} | {'k3':>5} | {'k4':>5} | {'s/ep':>5}\")\n", " print(\"-\" * 120)\n", "\n", " for ep in range(300):\n", " t0 = time.time()\n", "\n", " model.train()\n", " ce_sum, val_sum, cor, tot = 0, 0, 0, 0\n", "\n", " for img, lab in train_dl:\n", " img, lab = img.to(device, non_blocking=True), lab.to(device, non_blocking=True)\n", "\n", " opt.zero_grad()\n", " logits, info = model(img)\n", " loss, ce, val = geometric_loss(logits, lab, info)\n", " loss.backward()\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n", " opt.step()\n", "\n", " ce_sum += ce.item() * img.size(0)\n", " val_sum += val.item() * img.size(0)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", "\n", " tr_acc = cor / tot\n", "\n", " model.eval()\n", " cor, tot = 0, 0\n", " metrics_agg = []\n", " with torch.no_grad():\n", " for img, lab in test_dl:\n", " img, lab = img.to(device, non_blocking=True), lab.to(device, non_blocking=True)\n", " logits, info = model(img)\n", " cor += (logits.argmax(1) == lab).sum().item()\n", " tot += img.size(0)\n", " metrics_agg.append(compute_metrics(info, model._config['depth']))\n", "\n", " te_acc = cor / tot\n", " sched.step()\n", " if te_acc > best:\n", " best = te_acc\n", "\n", " m = {k: sum(d[k] for d in metrics_agg) / len(metrics_agg) for k in metrics_agg[0]}\n", " elapsed = time.time() - t0\n", "\n", " if ep % 5 == 0 or ep == 99:\n", " print(f\"{ep+1:3d} | {ce_sum/tot:6.4f} | {val_sum/tot:6.4f} | {tr_acc:6.2%} | {te_acc:6.2%} | {best:6.2%} | \"\n", " f\"{m['k1_valid']:5.1%} | {m['k2_valid']:5.1%} | {m['k3_valid']:5.1%} | {m['k4_valid']:5.1%} | {elapsed:5.1f}\")\n", "\n", " if ep % 20 == 0 or ep == 99:\n", " print(f\" k-details: \" + \" | \".join(\n", " f\"k{k+1}: vol²={m[f'k{k+1}_vol2']:.2e} d²={m[f'k{k+1}_d2']:.3f}\"\n", " for k in range(model._config['depth'])\n", " ))\n", "\n", " print(\"=\" * 120)\n", " print(f\"Best: {best:.2%}\")\n", " return model\n", "\n", "\n", "if __name__ == \"__main__\":\n", " model = train()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Z1_7pl34MqvQ", "outputId": "04732ef2-22fb-4db5-d409-d6188d0a2ea4" }, "execution_count": 12, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Device: cuda\n", "\n", "Building model...\n", "\n", "Config:\n", " img_size: 32\n", " patch_size: 4\n", " grid: 8x8\n", " depth: 4\n", " edim: 16\n", " feat_dim: 64\n", " k_out_dims: [66, 68, 71, 75]\n", " num_blocks: 8\n", " dropout: 0.1\n", " params: 6,912,362\n", "\n", "Compiling...\n", "\n", "Training...\n", "========================================================================================================================\n", " Ep | CE | Val | Tr | Te | Best | k1 | k2 | k3 | k4 | s/ep\n", "------------------------------------------------------------------------------------------------------------------------\n", " 1 | 10.7341 | 0.0000 | 17.61% | 22.68% | 22.68% | 100.0% | 100.0% | 100.0% | 100.0% | 37.1\n", " k-details: k1: vol²=1.27e+00 d²=1.270 | k2: vol²=2.13e-01 d²=1.052 | k3: vol²=2.36e-02 d²=1.168 | k4: vol²=1.11e-03 d²=1.171\n", " 6 | 8.1430 | 0.0000 | 39.54% | 47.90% | 47.90% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 11 | 6.9631 | 0.0000 | 49.49% | 59.71% | 59.71% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", " 16 | 6.3790 | 0.0000 | 54.18% | 62.40% | 63.07% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 21 | 5.9825 | 0.0000 | 57.42% | 66.64% | 66.64% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " k-details: k1: vol²=9.97e-01 d²=0.997 | k2: vol²=1.08e-01 d²=0.773 | k3: vol²=8.13e-03 d²=0.908 | k4: vol²=1.70e-04 d²=0.769\n", " 26 | 5.6247 | 0.0000 | 59.81% | 68.86% | 68.86% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 31 | 5.3297 | 0.0000 | 62.29% | 70.94% | 70.94% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 36 | 5.0943 | 0.0000 | 63.86% | 73.07% | 73.07% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 41 | 4.8257 | 0.0000 | 65.78% | 74.38% | 74.63% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " k-details: k1: vol²=9.75e-01 d²=0.975 | k2: vol²=1.11e-01 d²=0.779 | k3: vol²=7.68e-03 d²=0.874 | k4: vol²=1.21e-04 d²=0.719\n", " 46 | 4.5609 | 0.0000 | 67.71% | 75.12% | 75.72% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", " 51 | 4.3283 | 0.0000 | 69.25% | 77.77% | 77.77% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 56 | 4.1123 | 0.0000 | 70.77% | 78.38% | 78.50% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 61 | 3.8848 | 0.0000 | 72.38% | 79.06% | 79.52% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " k-details: k1: vol²=9.58e-01 d²=0.958 | k2: vol²=1.11e-01 d²=0.780 | k3: vol²=6.92e-03 d²=0.835 | k4: vol²=1.16e-04 d²=0.711\n", " 66 | 3.6766 | 0.0000 | 73.93% | 79.97% | 80.63% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 71 | 3.4712 | 0.0000 | 75.13% | 81.30% | 81.30% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 76 | 3.3240 | 0.0000 | 76.67% | 81.82% | 81.82% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 81 | 3.1819 | 0.0000 | 77.33% | 82.45% | 82.45% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " k-details: k1: vol²=9.40e-01 d²=0.940 | k2: vol²=1.15e-01 d²=0.796 | k3: vol²=7.40e-03 d²=0.857 | k4: vol²=1.26e-04 d²=0.725\n", " 86 | 3.0697 | 0.0000 | 78.18% | 82.46% | 82.46% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " 91 | 2.9876 | 0.0000 | 78.74% | 82.80% | 82.83% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", " 96 | 2.9530 | 0.0000 | 79.06% | 82.78% | 82.88% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", "100 | 2.9485 | 0.0000 | 79.08% | 82.74% | 82.88% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " k-details: k1: vol²=9.23e-01 d²=0.923 | k2: vol²=1.17e-01 d²=0.802 | k3: vol²=7.46e-03 d²=0.859 | k4: vol²=1.30e-04 d²=0.731\n", "101 | 2.9604 | 0.0000 | 79.25% | 82.74% | 82.88% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", " k-details: k1: vol²=9.23e-01 d²=0.923 | k2: vol²=1.17e-01 d²=0.802 | k3: vol²=7.46e-03 d²=0.859 | k4: vol²=1.30e-04 d²=0.731\n", "106 | 2.9602 | 0.0000 | 79.07% | 82.76% | 82.88% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", "111 | 2.9279 | 0.0000 | 79.12% | 82.67% | 82.88% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", "116 | 3.0012 | 0.0000 | 78.77% | 82.71% | 82.88% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", "121 | 3.0037 | 0.0000 | 78.68% | 82.63% | 82.88% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", " k-details: k1: vol²=9.23e-01 d²=0.923 | k2: vol²=1.16e-01 d²=0.799 | k3: vol²=7.33e-03 d²=0.854 | k4: vol²=1.31e-04 d²=0.733\n", "126 | 3.0616 | 0.0000 | 78.21% | 82.52% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", "131 | 3.1285 | 0.0000 | 77.99% | 81.94% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", "136 | 3.1699 | 0.0000 | 77.45% | 81.73% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", "141 | 3.2558 | 0.0000 | 76.87% | 82.36% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", " k-details: k1: vol²=9.26e-01 d²=0.926 | k2: vol²=1.18e-01 d²=0.804 | k3: vol²=6.88e-03 d²=0.823 | k4: vol²=1.40e-04 d²=0.737\n", "146 | 3.2993 | 0.0000 | 76.66% | 81.21% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", "151 | 3.3385 | 0.0000 | 76.40% | 80.96% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", "156 | 3.3985 | 0.0000 | 76.07% | 81.65% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.0\n", "161 | 3.4146 | 0.0000 | 75.87% | 81.12% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.1\n", " k-details: k1: vol²=9.40e-01 d²=0.940 | k2: vol²=1.16e-01 d²=0.799 | k3: vol²=6.68e-03 d²=0.814 | k4: vol²=1.39e-04 d²=0.732\n", "166 | 3.4092 | 0.0000 | 75.85% | 81.13% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "171 | 3.4158 | 0.0000 | 75.81% | 82.12% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "176 | 3.3912 | 0.0000 | 76.09% | 80.91% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "181 | 3.3550 | 0.0000 | 76.16% | 81.88% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", " k-details: k1: vol²=9.66e-01 d²=0.966 | k2: vol²=1.15e-01 d²=0.799 | k3: vol²=5.96e-03 d²=0.788 | k4: vol²=1.28e-04 d²=0.718\n", "186 | 3.2810 | 0.0000 | 76.82% | 81.05% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "191 | 3.2636 | 0.0000 | 76.79% | 82.75% | 82.93% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "196 | 3.0914 | 0.0000 | 78.19% | 82.86% | 83.14% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "201 | 3.0576 | 0.0000 | 78.38% | 82.47% | 83.14% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", " k-details: k1: vol²=9.91e-01 d²=0.991 | k2: vol²=1.07e-01 d²=0.776 | k3: vol²=4.87e-03 d²=0.741 | k4: vol²=1.01e-04 d²=0.671\n", "206 | 2.9441 | 0.0000 | 79.48% | 82.73% | 83.36% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "211 | 2.8108 | 0.0000 | 80.09% | 83.97% | 83.97% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "216 | 2.7292 | 0.0000 | 80.61% | 84.44% | 84.44% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "221 | 2.6265 | 0.0000 | 81.48% | 83.51% | 84.44% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", " k-details: k1: vol²=9.92e-01 d²=0.992 | k2: vol²=1.05e-01 d²=0.765 | k3: vol²=4.42e-03 d²=0.722 | k4: vol²=9.80e-05 d²=0.652\n", "226 | 2.4497 | 0.0000 | 82.78% | 84.68% | 84.83% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "231 | 2.3336 | 0.0000 | 83.54% | 84.98% | 84.98% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "236 | 2.1651 | 0.0000 | 84.70% | 85.17% | 85.19% | 100.0% | 100.0% | 100.0% | 100.0% | 21.3\n", "241 | 2.0805 | 0.0000 | 85.44% | 85.26% | 85.50% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", " k-details: k1: vol²=9.93e-01 d²=0.993 | k2: vol²=1.06e-01 d²=0.765 | k3: vol²=4.37e-03 d²=0.710 | k4: vol²=1.12e-04 d²=0.673\n", "246 | 1.8865 | 0.0000 | 86.71% | 85.79% | 86.17% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "251 | 1.7596 | 0.0000 | 87.56% | 85.88% | 86.17% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "256 | 1.6192 | 0.0000 | 88.61% | 86.30% | 86.70% | 100.0% | 100.0% | 100.0% | 100.0% | 21.3\n", "261 | 1.5038 | 0.0000 | 89.36% | 86.39% | 86.74% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", " k-details: k1: vol²=9.50e-01 d²=0.950 | k2: vol²=1.09e-01 d²=0.777 | k3: vol²=4.59e-03 d²=0.728 | k4: vol²=1.16e-04 d²=0.679\n", "266 | 1.3770 | 0.0000 | 90.35% | 86.66% | 86.76% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "271 | 1.2501 | 0.0000 | 91.18% | 87.24% | 87.28% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "276 | 1.1740 | 0.0000 | 91.95% | 87.18% | 87.33% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "281 | 1.0817 | 0.0000 | 92.54% | 87.66% | 87.73% | 100.0% | 100.0% | 100.0% | 100.0% | 21.3\n", " k-details: k1: vol²=9.39e-01 d²=0.939 | k2: vol²=1.10e-01 d²=0.779 | k3: vol²=4.71e-03 d²=0.728 | k4: vol²=1.29e-04 d²=0.697\n", "286 | 1.0079 | 0.0000 | 92.83% | 87.71% | 87.80% | 100.0% | 100.0% | 100.0% | 100.0% | 21.3\n", "291 | 0.9460 | 0.0000 | 93.37% | 87.77% | 87.86% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "296 | 0.9487 | 0.0000 | 93.33% | 87.82% | 87.91% | 100.0% | 100.0% | 100.0% | 100.0% | 21.2\n", "========================================================================================================================\n", "Best: 87.98%\n" ] } ] } ] }