{"id":104481,"date":"2026-07-15T19:27:36","date_gmt":"2026-07-15T19:27:36","guid":{"rendered":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/"},"modified":"2026-07-15T19:27:36","modified_gmt":"2026-07-15T19:27:36","slug":"building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides","status":"publish","type":"post","link":"https:\/\/youzum.net\/ja\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/","title":{"rendered":"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides"},"content":{"rendered":"<p class=\"wp-block-paragraph\">In this tutorial, we implement a<a href=\"https:\/\/github.com\/google\/gin-config\"> <strong>Gin Config<\/strong><\/a>\u2013controlled PyTorch experiment pipeline in which the executable training code remains stable. At the same time, the experimental degrees of freedom are moved into declarative configuration files. We construct a nonlinear spiral binary classification task, define a configurable MLP with scoped architectural variants, and expose parameters for the optimizer, scheduler, loss, batching, seeding, and training loop via @gin.configurable bindings. We use Gin\u2019s scoped references to instantiate separate model configurations, runtime bindings to override selected parameters without editing source code, and operative config export to capture the exact resolved configuration that produces each training run.<\/p>\n<h2 class=\"wp-block-heading\"><strong>Installing Gin Config and Building the Spiral Dataset<\/strong><\/h2>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">!pip -q install gin-config\nimport os\nimport json\nimport math\nimport random\nimport textwrap\nfrom pathlib import Path\nimport gin\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import TensorDataset, DataLoader\nimport matplotlib.pyplot as plt\nROOT = Path(\"\/content\/gin_config_sharp_tutorial\")\nCONFIG_DIR = ROOT \/ \"configs\"\nRUN_DIR = ROOT \/ \"runs\"\nCONFIG_DIR.mkdir(parents=True, exist_ok=True)\nRUN_DIR.mkdir(parents=True, exist_ok=True)\ngin.clear_config()\n@gin.configurable\ndef seed_everything(seed=42):\n   random.seed(seed)\n   np.random.seed(seed)\n   torch.manual_seed(seed)\n   torch.cuda.manual_seed_all(seed)\n   return seed\n@gin.configurable\ndef make_spiral_dataset(\n   n_per_class=gin.REQUIRED,\n   noise=0.18,\n   rotations=1.75,\n   train_fraction=0.8,\n   seed=0,\n):\n   rng = np.random.default_rng(seed)\n   radius_0 = np.linspace(0.05, 1.0, n_per_class)\n   theta_0 = rotations * 2 * np.pi * radius_0\n   theta_0 += rng.normal(0.0, noise, size=n_per_class)\n   x0 = np.stack(\n       [\n           radius_0 * np.cos(theta_0),\n           radius_0 * np.sin(theta_0),\n       ],\n       axis=1,\n   )\n   radius_1 = np.linspace(0.05, 1.0, n_per_class)\n   theta_1 = rotations * 2 * np.pi * radius_1 + np.pi\n   theta_1 += rng.normal(0.0, noise, size=n_per_class)\n   x1 = np.stack(\n       [\n           radius_1 * np.cos(theta_1),\n           radius_1 * np.sin(theta_1),\n       ],\n       axis=1,\n   )\n   x = np.concatenate([x0, x1], axis=0).astype(np.float32)\n   y = np.concatenate(\n       [\n           np.zeros((n_per_class, 1)),\n           np.ones((n_per_class, 1)),\n       ],\n       axis=0,\n   ).astype(np.float32)\n   order = rng.permutation(len(x))\n   x = x[order]\n   y = y[order]\n   split = int(train_fraction * len(x))\n   x_train, y_train = x[:split], y[:split]\n   x_val, y_val = x[split:], y[split:]\n   mean = x_train.mean(axis=0, keepdims=True)\n   std = x_train.std(axis=0, keepdims=True) + 1e-8\n   x_train = (x_train - mean) \/ std\n   x_val = (x_val - mean) \/ std\n   return {\n       \"train\": (\n           torch.tensor(x_train),\n           torch.tensor(y_train),\n       ),\n       \"val\": (\n           torch.tensor(x_val),\n           torch.tensor(y_val),\n       ),\n       \"metadata\": {\n           \"n_train\": int(len(x_train)),\n           \"n_val\": int(len(x_val)),\n           \"n_features\": int(x_train.shape[1]),\n           \"noise\": float(noise),\n           \"rotations\": float(rotations),\n           \"seed\": int(seed),\n       },\n   }\n@gin.configurable(denylist=[\"x\", \"y\"])\ndef make_loader(\n   x,\n   y,\n   batch_size=128,\n   shuffle=True,\n   seed=0,\n):\n   generator = torch.Generator()\n   generator.manual_seed(seed)\n   dataset = TensorDataset(x, y)\n   return DataLoader(\n       dataset,\n       batch_size=batch_size,\n       shuffle=shuffle,\n       generator=generator,\n       drop_last=False,\n   )\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We start by installing Gin Config and importing the core Python libraries, PyTorch, NumPy, and the plotting libraries required for the experiment. We create a clean project directory structure and reset Gin\u2019s global configuration state so the notebook runs reproducibly. We then define the seed function, generate a nonlinear spiral dataset, and build a configurable DataLoader that Gin can control through external bindings.<\/p>\n<h2 class=\"wp-block-heading\"><strong>Defining a Gin-Configurable MLP, Optimizer, and Scheduler<\/strong><\/h2>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">def activation_layer(name):\n   name = name.lower()\n   if name == \"relu\":\n       return nn.ReLU()\n   if name == \"gelu\":\n       return nn.GELU()\n   if name == \"tanh\":\n       return nn.Tanh()\n   if name == \"silu\":\n       return nn.SiLU()\n   raise ValueError(f\"Unknown activation: {name}\")\n@gin.configurable\nclass MLP(nn.Module):\n   def __init__(\n       self,\n       input_dim=gin.REQUIRED,\n       hidden_dims=(64, 64),\n       output_dim=1,\n       activation=\"gelu\",\n       dropout=0.0,\n       use_layernorm=False,\n   ):\n       super().__init__()\n       layers = []\n       current_dim = input_dim\n       for hidden_dim in hidden_dims:\n           layers.append(nn.Linear(current_dim, hidden_dim))\n           if use_layernorm:\n               layers.append(nn.LayerNorm(hidden_dim))\n           layers.append(activation_layer(activation))\n           if dropout &gt; 0:\n               layers.append(nn.Dropout(dropout))\n           current_dim = hidden_dim\n       layers.append(nn.Linear(current_dim, output_dim))\n       self.network = nn.Sequential(*layers)\n   def forward(self, x):\n       return self.network(x)\n@gin.configurable(denylist=[\"params\"])\ndef make_optimizer(\n   params,\n   name=\"adamw\",\n   lr=3e-3,\n   weight_decay=1e-3,\n   momentum=0.9,\n):\n   name = name.lower()\n   if name == \"adamw\":\n       return torch.optim.AdamW(\n           params,\n           lr=lr,\n           weight_decay=weight_decay,\n       )\n   if name == \"sgd\":\n       return torch.optim.SGD(\n           params,\n           lr=lr,\n           momentum=momentum,\n           weight_decay=weight_decay,\n       )\n   raise ValueError(f\"Unknown optimizer: {name}\")\n@gin.configurable(denylist=[\"optimizer\"])\ndef make_cosine_scheduler(\n   optimizer,\n   total_epochs=60,\n   warmup_epochs=5,\n   min_lr_factor=0.05,\n):\n   def lr_lambda(epoch):\n       if epoch &lt; warmup_epochs:\n           return float(epoch + 1) \/ float(max(1, warmup_epochs))\n       progress = (epoch - warmup_epochs) \/ float(\n           max(1, total_epochs - warmup_epochs)\n       )\n       cosine = 0.5 * (1.0 + math.cos(math.pi * progress))\n       return min_lr_factor + (1.0 - min_lr_factor) * cosine\n   return torch.optim.lr_scheduler.LambdaLR(\n       optimizer,\n       lr_lambda=lr_lambda,\n   )\n@gin.configurable\ndef bce_with_logits_loss(\n   logits,\n   targets,\n   label_smoothing=0.0,\n):\n   if label_smoothing &gt; 0:\n       targets = targets * (1.0 - label_smoothing) + 0.5 * label_smoothing\n   return F.binary_cross_entropy_with_logits(logits, targets)\n@torch.no_grad()\ndef evaluate(model, loader, loss_fn, device):\n   model.eval()\n   total_loss = 0.0\n   total_correct = 0\n   total_count = 0\n   for x, y in loader:\n       x = x.to(device)\n       y = y.to(device)\n       logits = model(x)\n       loss = loss_fn(logits, y)\n       probs = torch.sigmoid(logits)\n       preds = (probs &gt;= 0.5).float()\n       total_loss += loss.item() * len(x)\n       total_correct += (preds == y).sum().item()\n       total_count += len(x)\n   return {\n       \"loss\": total_loss \/ total_count,\n       \"accuracy\": total_correct \/ total_count,\n   }\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We define the neural network building blocks that form the configurable model and the training utilities. We create an MLP class whose architecture, activation function, dropout, and layer normalization behavior are controlled through Gin rather than hardcoded values. We also implement configurable optimizer, scheduler, loss, and evaluation functions so the training pipeline remains modular and experiment-ready.<\/p>\n<h2 class=\"wp-block-heading\"><strong>Implementing the Training Loop and Experiment Runner<\/strong><\/h2>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">@gin.configurable(\n   denylist=[\n       \"model\",\n       \"optimizer\",\n       \"scheduler\",\n       \"train_loader\",\n       \"val_loader\",\n       \"device\",\n   ]\n)\ndef fit(\n   model,\n   optimizer,\n   scheduler,\n   train_loader,\n   val_loader,\n   device,\n   epochs=60,\n   grad_clip_norm=1.0,\n   log_every=10,\n   loss_fn=bce_with_logits_loss,\n):\n   history = []\n   for epoch in range(1, epochs + 1):\n       model.train()\n       for x, y in train_loader:\n           x = x.to(device)\n           y = y.to(device)\n           optimizer.zero_grad(set_to_none=True)\n           logits = model(x)\n           loss = loss_fn(logits, y)\n           loss.backward()\n           if grad_clip_norm is not None:\n               nn.utils.clip_grad_norm_(\n                   model.parameters(),\n                   grad_clip_norm,\n               )\n           optimizer.step()\n       if scheduler is not None:\n           scheduler.step()\n       train_metrics = evaluate(\n           model,\n           train_loader,\n           loss_fn,\n           device,\n       )\n       val_metrics = evaluate(\n           model,\n           val_loader,\n           loss_fn,\n           device,\n       )\n       lr = optimizer.param_groups[0][\"lr\"]\n       row = {\n           \"epoch\": epoch,\n           \"lr\": lr,\n           \"train_loss\": train_metrics[\"loss\"],\n           \"train_accuracy\": train_metrics[\"accuracy\"],\n           \"val_loss\": val_metrics[\"loss\"],\n           \"val_accuracy\": val_metrics[\"accuracy\"],\n       }\n       history.append(row)\n       if epoch == 1 or epoch % log_every == 0 or epoch == epochs:\n           print(\n               f\"epoch={epoch:03d} | \"\n               f\"lr={lr:.6f} | \"\n               f\"train_loss={row['train_loss']:.4f} | \"\n               f\"train_acc={row['train_accuracy']:.3f} | \"\n               f\"val_loss={row['val_loss']:.4f} | \"\n               f\"val_acc={row['val_accuracy']:.3f}\"\n           )\n   return history\n@gin.configurable\ndef run_experiment(\n   tag=gin.REQUIRED,\n   model=gin.REQUIRED,\n   dataset_fn=make_spiral_dataset,\n   optimizer_factory=make_optimizer,\n   scheduler_factory=make_cosine_scheduler,\n   prefer_gpu=True,\n):\n   seed_everything()\n   device = \"cuda\" if prefer_gpu and torch.cuda.is_available() else \"cpu\"\n   data = dataset_fn()\n   x_train, y_train = data[\"train\"]\n   x_val, y_val = data[\"val\"]\n   train_loader = make_loader(\n       x_train,\n       y_train,\n       shuffle=True,\n   )\n   val_loader = make_loader(\n       x_val,\n       y_val,\n       shuffle=False,\n   )\n   model = model.to(device)\n   optimizer = optimizer_factory(model.parameters())\n   scheduler = None\n   if scheduler_factory is not None:\n       scheduler = scheduler_factory(optimizer)\n   print(\"n\" + \"=\" * 80)\n   print(f\"Experiment: {tag}\")\n   print(\"=\" * 80)\n   print(f\"Device: {device}\")\n   print(f\"Dataset: {data['metadata']}\")\n   print(f\"Parameters: {sum(p.numel() for p in model.parameters()):,}\")\n   history = fit(\n       model=model,\n       optimizer=optimizer,\n       scheduler=scheduler,\n       train_loader=train_loader,\n       val_loader=val_loader,\n       device=device,\n   )\n   result = {\n       \"tag\": tag,\n       \"device\": device,\n       \"metadata\": data[\"metadata\"],\n       \"parameters\": sum(p.numel() for p in model.parameters()),\n       \"final\": history[-1],\n       \"history\": history,\n   }\n   return result\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We implement the main training loop, in which the model performs forward passes, computes binary cross-entropy loss, backpropagates gradients, applies gradient clipping, and updates parameters. We evaluate the model after each epoch on both the training and validation sets, while storing loss, accuracy, and learning rate history. We then define the top-level experiment runner that connects the dataset, model, optimizer, scheduler, and training loop through Gin-managed dependencies.<\/p>\n<h2 class=\"wp-block-heading\"><strong>Writing Gin Config Files with Scoped Bindings and Runtime Overrides<\/strong><\/h2>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">BASE_CONFIG = CONFIG_DIR \/ \"base.gin\"\nCOMPACT_CONFIG = CONFIG_DIR \/ \"compact_adamw.gin\"\nWIDE_CONFIG = CONFIG_DIR \/ \"wide_sgd.gin\"\nBASE_CONFIG.write_text(\n   textwrap.dedent(\n       \"\"\"\n       SEED = 123\n       N_PER_CLASS = 900\n       EPOCHS = 50\n       BATCH = 128\n       seed_everything.seed = %SEED\n       make_spiral_dataset.n_per_class = %N_PER_CLASS\n       make_spiral_dataset.noise = 0.20\n       make_spiral_dataset.rotations = 1.85\n       make_spiral_dataset.train_fraction = 0.80\n       make_spiral_dataset.seed = %SEED\n       make_loader.batch_size = %BATCH\n       make_loader.seed = %SEED\n       MLP.input_dim = 2\n       MLP.output_dim = 1\n       MLP.activation = 'gelu'\n       MLP.dropout = 0.05\n       MLP.use_layernorm = True\n       make_optimizer.name = 'adamw'\n       make_optimizer.lr = 0.003\n       make_optimizer.weight_decay = 0.001\n       make_optimizer.momentum = 0.9\n       make_cosine_scheduler.total_epochs = %EPOCHS\n       make_cosine_scheduler.warmup_epochs = 5\n       make_cosine_scheduler.min_lr_factor = 0.05\n       bce_with_logits_loss.label_smoothing = 0.02\n       fit.epochs = %EPOCHS\n       fit.grad_clip_norm = 1.0\n       fit.log_every = 10\n       fit.loss_fn = @bce_with_logits_loss\n       run_experiment.dataset_fn = @make_spiral_dataset\n       run_experiment.optimizer_factory = @make_optimizer\n       run_experiment.scheduler_factory = @make_cosine_scheduler\n       run_experiment.prefer_gpu = True\n       \"\"\"\n   ).strip()\n)\nCOMPACT_CONFIG.write_text(\n   textwrap.dedent(\n       f\"\"\"\n       include '{BASE_CONFIG.as_posix()}'\n       run_experiment.tag = 'compact_gelu_adamw'\n       run_experiment.model = @compact\/MLP()\n       compact\/MLP.hidden_dims = (64, 64, 64)\n       compact\/MLP.dropout = 0.05\n       compact\/MLP.use_layernorm = True\n       make_optimizer.name = 'adamw'\n       make_optimizer.lr = 0.003\n       make_optimizer.weight_decay = 0.001\n       \"\"\"\n   ).strip()\n)\nWIDE_CONFIG.write_text(\n   textwrap.dedent(\n       f\"\"\"\n       include '{BASE_CONFIG.as_posix()}'\n       run_experiment.tag = 'wide_relu_sgd'\n       run_experiment.model = @wide\/MLP()\n       wide\/MLP.hidden_dims = (128, 128, 128, 64)\n       wide\/MLP.activation = 'relu'\n       wide\/MLP.dropout = 0.02\n       wide\/MLP.use_layernorm = True\n       make_optimizer.name = 'sgd'\n       make_optimizer.lr = 0.035\n       make_optimizer.momentum = 0.92\n       make_optimizer.weight_decay = 0.0005\n       bce_with_logits_loss.label_smoothing = 0.0\n       \"\"\"\n   ).strip()\n)\ndef run_from_gin_file(config_path, runtime_bindings=None):\n   runtime_bindings = runtime_bindings or []\n   gin.clear_config()\n   gin.parse_config_files_and_bindings(\n       config_files=[str(config_path)],\n       bindings=runtime_bindings,\n       skip_unknown=False,\n       finalize_config=True,\n   )\n   print(\"nLoaded config file:\")\n   print(config_path)\n   print(\"nSelected queried parameters:\")\n   print(\"fit.epochs =\", gin.query_parameter(\"fit.epochs\"))\n   print(\"make_loader.batch_size =\", gin.query_parameter(\"make_loader.batch_size\"))\n   print(\"make_spiral_dataset.noise =\", gin.query_parameter(\"make_spiral_dataset.noise\"))\n   try:\n       gin.bind_parameter(\"fit.epochs\", 999)\n   except RuntimeError as error:\n       print(\"nConfig lock check:\")\n       print(str(error).splitlines()[0])\n   result = run_experiment()\n   tag = result[\"tag\"]\n   out_dir = RUN_DIR \/ tag\n   out_dir.mkdir(parents=True, exist_ok=True)\n   result_path = out_dir \/ \"result.json\"\n   operative_path = out_dir \/ \"operative_config.gin\"\n   result_path.write_text(json.dumps(result, indent=2))\n   operative_path.write_text(gin.operative_config_str())\n   print(\"nSaved:\")\n   print(result_path)\n   print(operative_path)\n   return result, operative_path\ncompact_result, compact_operative = run_from_gin_file(\n   COMPACT_CONFIG,\n   runtime_bindings=[\n       \"fit.epochs = 45\",\n       \"make_spiral_dataset.noise = 0.18\",\n       \"run_experiment.tag = 'compact_gelu_adamw_runtime_override'\",\n   ],\n)\nwide_result, wide_operative = run_from_gin_file(\n   WIDE_CONFIG,\n   runtime_bindings=[\n       \"fit.epochs = 45\",\n       \"make_spiral_dataset.noise = 0.18\",\n       \"run_experiment.tag = 'wide_relu_sgd_runtime_override'\",\n   ],\n)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We create the actual Gin configuration files that control the experiment without modifying the Python source code. We define a shared base configuration and then compose two scoped experiments: a compact GELU-based AdamW model and a wider ReLU-based SGD model. We also demonstrate runtime overrides, parameter queries, config locking, result serialization, and operative config export for reproducible experiment tracking.<\/p>\n<h2 class=\"wp-block-heading\"><strong>Comparing Results and Exporting the Operative Config<\/strong><\/h2>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">def plot_metric(results, metric, title):\n   plt.figure(figsize=(9, 4))\n   for result in results:\n       epochs = [row[\"epoch\"] for row in result[\"history\"]]\n       values = [row[metric] for row in result[\"history\"]]\n       plt.plot(epochs, values, label=result[\"tag\"])\n   plt.xlabel(\"Epoch\")\n   plt.ylabel(metric)\n   plt.title(title)\n   plt.grid(True, alpha=0.3)\n   plt.legend()\n   plt.tight_layout()\n   plt.show()\nplot_metric(\n   [compact_result, wide_result],\n   \"val_loss\",\n   \"Validation Loss Controlled by Gin Config\",\n)\nplot_metric(\n   [compact_result, wide_result],\n   \"val_accuracy\",\n   \"Validation Accuracy Controlled by Gin Config\",\n)\nsummary = [\n   {\n       \"tag\": compact_result[\"tag\"],\n       \"params\": compact_result[\"parameters\"],\n       \"val_loss\": compact_result[\"final\"][\"val_loss\"],\n       \"val_accuracy\": compact_result[\"final\"][\"val_accuracy\"],\n   },\n   {\n       \"tag\": wide_result[\"tag\"],\n       \"params\": wide_result[\"parameters\"],\n       \"val_loss\": wide_result[\"final\"][\"val_loss\"],\n       \"val_accuracy\": wide_result[\"final\"][\"val_accuracy\"],\n   },\n]\nprint(\"n\" + \"=\" * 80)\nprint(\"Final comparison\")\nprint(\"=\" * 80)\nfor row in summary:\n   print(\n       f\"{row['tag']} | \"\n       f\"params={row['params']:,} | \"\n       f\"val_loss={row['val_loss']:.4f} | \"\n       f\"val_acc={row['val_accuracy']:.3f}\"\n   )\nprint(\"n\" + \"=\" * 80)\nprint(\"Compact experiment operative config preview\")\nprint(\"=\" * 80)\nprint(compact_operative.read_text()[:2500])\nprint(\"n\" + \"=\" * 80)\nprint(\"Generated files\")\nprint(\"=\" * 80)\nfor path in sorted(ROOT.rglob(\"*\")):\n   if path.is_file():\n       print(path)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We visualize the validation loss and validation accuracy curves for both Gin-controlled experiments. We summarize the final parameter counts, validation losses, and validation accuracies to clearly compare the two configurations. We also print the operative configuration and the generated files, which provide a complete record of the exact settings used during execution.<\/p>\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n<p class=\"wp-block-paragraph\">In conclusion, we have a reproducible experiment-management workflow that demonstrates how Gin Config improves control, traceability, and modularity in PyTorch projects. We ran multiple scoped experiments from composed .gin files, compared AdamW and SGD training behavior under controlled dataset and epoch settings, verified configuration locking after parsing, and saved both metrics and operative configs for later inspection. It gives us a pattern for scaling Colab experiments into research-grade pipelines, in which model architecture, optimization strategy, data generation, and training schedules must be systematically adjusted without breaking the core implementation.<\/p>\n<p class=\"wp-block-paragraph\">\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<\/p><p class=\"wp-block-paragraph\">\n<\/p><p class=\"wp-block-paragraph\">Check out the\u00a0<strong><a href=\"https:\/\/github.com\/MARKTECHPOST-AI-MEDIA-INC\/AI-Agents-Projects-Tutorials\/blob\/main\/ML%20Project%20Codes\/gin_config_reproducible_pytorch_experiments_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">Full Codes with Notebook here<\/a>.\u00a0<\/strong>Also,\u00a0feel free to follow us on\u00a0<strong><a href=\"https:\/\/x.com\/intent\/follow?screen_name=marktechpost\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Twitter<\/mark><\/a><\/strong>\u00a0and don\u2019t forget to join our\u00a0<strong><a href=\"https:\/\/www.reddit.com\/r\/machinelearningnews\/\" target=\"_blank\" rel=\"noreferrer noopener\">150k+ML SubReddit<\/a><\/strong>\u00a0and Subscribe to\u00a0<strong><a href=\"https:\/\/www.aidevsignals.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">our Newsletter<\/a><\/strong>. Wait! are you on telegram?\u00a0<strong><a href=\"https:\/\/t.me\/machinelearningresearchnews\" target=\"_blank\" rel=\"noreferrer noopener\">now you can join us on telegram as well.<\/a><\/strong><\/p>\n<p class=\"wp-block-paragraph\">Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.?\u00a0<strong><a href=\"https:\/\/forms.gle\/wbash1wF6efRj8G58\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Connect with us<\/mark><\/a><\/strong><\/p>\n<p>The post <a href=\"https:\/\/www.marktechpost.com\/2026\/07\/15\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\">Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides<\/a> appeared first on <a href=\"https:\/\/www.marktechpost.com\/\">MarkTechPost<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we implement a Gin Config\u2013controlled PyTorch experiment pipeline in which the executable training code remains stable. At the same time, the experimental degrees of freedom are moved into declarative configuration files. We construct a nonlinear spiral binary classification task, define a configurable MLP with scoped architectural variants, and expose parameters for the optimizer, scheduler, loss, batching, seeding, and training loop via @gin.configurable bindings. We use Gin\u2019s scoped references to instantiate separate model configurations, runtime bindings to override selected parameters without editing source code, and operative config export to capture the exact resolved configuration that produces each training run. Installing Gin Config and Building the Spiral Dataset Copy CodeCopiedUse a different Browser !pip -q install gin-config import os import json import math import random import textwrap from pathlib import Path import gin import numpy as np import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import TensorDataset, DataLoader import matplotlib.pyplot as plt ROOT = Path(&#8220;\/content\/gin_config_sharp_tutorial&#8221;) CONFIG_DIR = ROOT \/ &#8220;configs&#8221; RUN_DIR = ROOT \/ &#8220;runs&#8221; CONFIG_DIR.mkdir(parents=True, exist_ok=True) RUN_DIR.mkdir(parents=True, exist_ok=True) gin.clear_config() @gin.configurable def seed_everything(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) return seed @gin.configurable def make_spiral_dataset( n_per_class=gin.REQUIRED, noise=0.18, rotations=1.75, train_fraction=0.8, seed=0, ): rng = np.random.default_rng(seed) radius_0 = np.linspace(0.05, 1.0, n_per_class) theta_0 = rotations * 2 * np.pi * radius_0 theta_0 += rng.normal(0.0, noise, size=n_per_class) x0 = np.stack( [ radius_0 * np.cos(theta_0), radius_0 * np.sin(theta_0), ], axis=1, ) radius_1 = np.linspace(0.05, 1.0, n_per_class) theta_1 = rotations * 2 * np.pi * radius_1 + np.pi theta_1 += rng.normal(0.0, noise, size=n_per_class) x1 = np.stack( [ radius_1 * np.cos(theta_1), radius_1 * np.sin(theta_1), ], axis=1, ) x = np.concatenate([x0, x1], axis=0).astype(np.float32) y = np.concatenate( [ np.zeros((n_per_class, 1)), np.ones((n_per_class, 1)), ], axis=0, ).astype(np.float32) order = rng.permutation(len(x)) x = x[order] y = y[order] split = int(train_fraction * len(x)) x_train, y_train = x[:split], y[:split] x_val, y_val = x[split:], y[split:] mean = x_train.mean(axis=0, keepdims=True) std = x_train.std(axis=0, keepdims=True) + 1e-8 x_train = (x_train &#8211; mean) \/ std x_val = (x_val &#8211; mean) \/ std return { &#8220;train&#8221;: ( torch.tensor(x_train), torch.tensor(y_train), ), &#8220;val&#8221;: ( torch.tensor(x_val), torch.tensor(y_val), ), &#8220;metadata&#8221;: { &#8220;n_train&#8221;: int(len(x_train)), &#8220;n_val&#8221;: int(len(x_val)), &#8220;n_features&#8221;: int(x_train.shape[1]), &#8220;noise&#8221;: float(noise), &#8220;rotations&#8221;: float(rotations), &#8220;seed&#8221;: int(seed), }, } @gin.configurable(denylist=[&#8220;x&#8221;, &#8220;y&#8221;]) def make_loader( x, y, batch_size=128, shuffle=True, seed=0, ): generator = torch.Generator() generator.manual_seed(seed) dataset = TensorDataset(x, y) return DataLoader( dataset, batch_size=batch_size, shuffle=shuffle, generator=generator, drop_last=False, ) We start by installing Gin Config and importing the core Python libraries, PyTorch, NumPy, and the plotting libraries required for the experiment. We create a clean project directory structure and reset Gin\u2019s global configuration state so the notebook runs reproducibly. We then define the seed function, generate a nonlinear spiral dataset, and build a configurable DataLoader that Gin can control through external bindings. Defining a Gin-Configurable MLP, Optimizer, and Scheduler Copy CodeCopiedUse a different Browser def activation_layer(name): name = name.lower() if name == &#8220;relu&#8221;: return nn.ReLU() if name == &#8220;gelu&#8221;: return nn.GELU() if name == &#8220;tanh&#8221;: return nn.Tanh() if name == &#8220;silu&#8221;: return nn.SiLU() raise ValueError(f&#8221;Unknown activation: {name}&#8221;) @gin.configurable class MLP(nn.Module): def __init__( self, input_dim=gin.REQUIRED, hidden_dims=(64, 64), output_dim=1, activation=&#8221;gelu&#8221;, dropout=0.0, use_layernorm=False, ): super().__init__() layers = [] current_dim = input_dim for hidden_dim in hidden_dims: layers.append(nn.Linear(current_dim, hidden_dim)) if use_layernorm: layers.append(nn.LayerNorm(hidden_dim)) layers.append(activation_layer(activation)) if dropout &gt; 0: layers.append(nn.Dropout(dropout)) current_dim = hidden_dim layers.append(nn.Linear(current_dim, output_dim)) self.network = nn.Sequential(*layers) def forward(self, x): return self.network(x) @gin.configurable(denylist=[&#8220;params&#8221;]) def make_optimizer( params, name=&#8221;adamw&#8221;, lr=3e-3, weight_decay=1e-3, momentum=0.9, ): name = name.lower() if name == &#8220;adamw&#8221;: return torch.optim.AdamW( params, lr=lr, weight_decay=weight_decay, ) if name == &#8220;sgd&#8221;: return torch.optim.SGD( params, lr=lr, momentum=momentum, weight_decay=weight_decay, ) raise ValueError(f&#8221;Unknown optimizer: {name}&#8221;) @gin.configurable(denylist=[&#8220;optimizer&#8221;]) def make_cosine_scheduler( optimizer, total_epochs=60, warmup_epochs=5, min_lr_factor=0.05, ): def lr_lambda(epoch): if epoch &lt; warmup_epochs: return float(epoch + 1) \/ float(max(1, warmup_epochs)) progress = (epoch &#8211; warmup_epochs) \/ float( max(1, total_epochs &#8211; warmup_epochs) ) cosine = 0.5 * (1.0 + math.cos(math.pi * progress)) return min_lr_factor + (1.0 &#8211; min_lr_factor) * cosine return torch.optim.lr_scheduler.LambdaLR( optimizer, lr_lambda=lr_lambda, ) @gin.configurable def bce_with_logits_loss( logits, targets, label_smoothing=0.0, ): if label_smoothing &gt; 0: targets = targets * (1.0 &#8211; label_smoothing) + 0.5 * label_smoothing return F.binary_cross_entropy_with_logits(logits, targets) @torch.no_grad() def evaluate(model, loader, loss_fn, device): model.eval() total_loss = 0.0 total_correct = 0 total_count = 0 for x, y in loader: x = x.to(device) y = y.to(device) logits = model(x) loss = loss_fn(logits, y) probs = torch.sigmoid(logits) preds = (probs &gt;= 0.5).float() total_loss += loss.item() * len(x) total_correct += (preds == y).sum().item() total_count += len(x) return { &#8220;loss&#8221;: total_loss \/ total_count, &#8220;accuracy&#8221;: total_correct \/ total_count, } We define the neural network building blocks that form the configurable model and the training utilities. We create an MLP class whose architecture, activation function, dropout, and layer normalization behavior are controlled through Gin rather than hardcoded values. We also implement configurable optimizer, scheduler, loss, and evaluation functions so the training pipeline remains modular and experiment-ready. Implementing the Training Loop and Experiment Runner Copy CodeCopiedUse a different Browser @gin.configurable( denylist=[ &#8220;model&#8221;, &#8220;optimizer&#8221;, &#8220;scheduler&#8221;, &#8220;train_loader&#8221;, &#8220;val_loader&#8221;, &#8220;device&#8221;, ] ) def fit( model, optimizer, scheduler, train_loader, val_loader, device, epochs=60, grad_clip_norm=1.0, log_every=10, loss_fn=bce_with_logits_loss, ): history = [] for epoch in range(1, epochs + 1): model.train() for x, y in train_loader: x = x.to(device) y = y.to(device) optimizer.zero_grad(set_to_none=True) logits = model(x) loss = loss_fn(logits, y) loss.backward() if grad_clip_norm is not None: nn.utils.clip_grad_norm_( model.parameters(), grad_clip_norm, ) optimizer.step() if scheduler is not None: scheduler.step() train_metrics = evaluate( model, train_loader, loss_fn, device, ) val_metrics = evaluate( model, val_loader, loss_fn, device, ) lr = optimizer.param_groups[0][&#8220;lr&#8221;] row = { &#8220;epoch&#8221;: epoch, &#8220;lr&#8221;: lr, &#8220;train_loss&#8221;: train_metrics[&#8220;loss&#8221;], &#8220;train_accuracy&#8221;: train_metrics[&#8220;accuracy&#8221;], &#8220;val_loss&#8221;: val_metrics[&#8220;loss&#8221;], &#8220;val_accuracy&#8221;: val_metrics[&#8220;accuracy&#8221;], } history.append(row) if epoch == 1 or epoch % log_every == 0 or epoch == epochs: print( f&#8221;epoch={epoch:03d} | &#8221; f&#8221;lr={lr:.6f} | &#8221; f&#8221;train_loss={row[&#8216;train_loss&#8217;]:.4f} | &#8221; f&#8221;train_acc={row[&#8216;train_accuracy&#8217;]:.3f} | &#8221; f&#8221;val_loss={row[&#8216;val_loss&#8217;]:.4f} | &#8221; f&#8221;val_acc={row[&#8216;val_accuracy&#8217;]:.3f}&#8221; ) return history @gin.configurable def run_experiment( tag=gin.REQUIRED, model=gin.REQUIRED, dataset_fn=make_spiral_dataset, optimizer_factory=make_optimizer, scheduler_factory=make_cosine_scheduler, prefer_gpu=True, ): seed_everything() device = &#8220;cuda&#8221; if prefer_gpu and torch.cuda.is_available() else &#8220;cpu&#8221; data = dataset_fn() x_train, y_train = data[&#8220;train&#8221;] x_val, y_val = data[&#8220;val&#8221;] train_loader = make_loader( x_train, y_train, shuffle=True, ) val_loader = make_loader( x_val, y_val, shuffle=False, ) model = model.to(device) optimizer = optimizer_factory(model.parameters()) scheduler = None if scheduler_factory is not None: scheduler = scheduler_factory(optimizer) print(&#8220;n&#8221; + &#8220;=&#8221; * 80) print(f&#8221;Experiment: {tag}&#8221;) print(&#8220;=&#8221; *<\/p>","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pmpro_default_level":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_pvb_checkbox_block_on_post":false,"footnotes":""},"categories":[52,5,7,1],"tags":[],"class_list":["post-104481","post","type-post","status-publish","format-standard","hentry","category-ai-club","category-committee","category-news","category-uncategorized","pmpro-has-access"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides - YouZum<\/title>\n<meta name=\"description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/youzum.net\/ja\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\" \/>\n<meta property=\"og:locale\" content=\"ja_JP\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides - YouZum\" \/>\n<meta property=\"og:description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta property=\"og:url\" content=\"https:\/\/youzum.net\/ja\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\" \/>\n<meta property=\"og:site_name\" content=\"YouZum\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/DroneAssociationTH\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-15T19:27:36+00:00\" \/>\n<meta name=\"author\" content=\"admin NU\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u57f7\u7b46\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin NU\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u63a8\u5b9a\u8aad\u307f\u53d6\u308a\u6642\u9593\" \/>\n\t<meta name=\"twitter:data2\" content=\"12\u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\"},\"author\":{\"name\":\"admin NU\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\"},\"headline\":\"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides\",\"datePublished\":\"2026-07-15T19:27:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\"},\"wordCount\":674,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"articleSection\":[\"AI\",\"Committee\",\"News\",\"Uncategorized\"],\"inLanguage\":\"ja\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\",\"url\":\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\",\"name\":\"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides - YouZum\",\"isPartOf\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#website\"},\"datePublished\":\"2026-07-15T19:27:36+00:00\",\"description\":\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\",\"breadcrumb\":{\"@id\":\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#breadcrumb\"},\"inLanguage\":\"ja\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/youzum.net\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/yousum.gpucore.co\/#website\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"name\":\"YouSum\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/yousum.gpucore.co\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"ja\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\",\"name\":\"Drone Association Thailand\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ja\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"width\":300,\"height\":300,\"caption\":\"Drone Association Thailand\"},\"image\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/DroneAssociationTH\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\",\"name\":\"admin NU\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ja\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"caption\":\"admin NU\"},\"url\":\"https:\/\/youzum.net\/ja\/members\/adminnu\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides - YouZum","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/youzum.net\/ja\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/","og_locale":"ja_JP","og_type":"article","og_title":"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides - YouZum","og_description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","og_url":"https:\/\/youzum.net\/ja\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/","og_site_name":"YouZum","article_publisher":"https:\/\/www.facebook.com\/DroneAssociationTH\/","article_published_time":"2026-07-15T19:27:36+00:00","author":"admin NU","twitter_card":"summary_large_image","twitter_misc":{"\u57f7\u7b46\u8005":"admin NU","\u63a8\u5b9a\u8aad\u307f\u53d6\u308a\u6642\u9593":"12\u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#article","isPartOf":{"@id":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/"},"author":{"name":"admin NU","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c"},"headline":"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides","datePublished":"2026-07-15T19:27:36+00:00","mainEntityOfPage":{"@id":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/"},"wordCount":674,"commentCount":0,"publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"articleSection":["AI","Committee","News","Uncategorized"],"inLanguage":"ja","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/","url":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/","name":"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides - YouZum","isPartOf":{"@id":"https:\/\/yousum.gpucore.co\/#website"},"datePublished":"2026-07-15T19:27:36+00:00","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","breadcrumb":{"@id":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#breadcrumb"},"inLanguage":"ja","potentialAction":[{"@type":"ReadAction","target":["https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/youzum.net\/building-a-gin-config-controlled-pytorch-pipeline-with-configurable-mlp-variants-cosine-scheduling-and-runtime-parameter-overrides\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/youzum.net\/"},{"@type":"ListItem","position":2,"name":"Building a Gin Config Controlled PyTorch Pipeline with Configurable MLP Variants, Cosine Scheduling, and Runtime Parameter Overrides"}]},{"@type":"WebSite","@id":"https:\/\/yousum.gpucore.co\/#website","url":"https:\/\/yousum.gpucore.co\/","name":"YouSum","description":"","publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/yousum.gpucore.co\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"ja"},{"@type":"Organization","@id":"https:\/\/yousum.gpucore.co\/#organization","name":"Drone Association Thailand","url":"https:\/\/yousum.gpucore.co\/","logo":{"@type":"ImageObject","inLanguage":"ja","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","width":300,"height":300,"caption":"Drone Association Thailand"},"image":{"@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/DroneAssociationTH\/"]},{"@type":"Person","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c","name":"admin NU","image":{"@type":"ImageObject","inLanguage":"ja","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","caption":"admin NU"},"url":"https:\/\/youzum.net\/ja\/members\/adminnu\/"}]}},"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"admin NU","author_link":"https:\/\/youzum.net\/ja\/members\/adminnu\/"},"rttpg_comment":0,"rttpg_category":"<a href=\"https:\/\/youzum.net\/ja\/category\/ai-club\/\" rel=\"category tag\">AI<\/a> <a href=\"https:\/\/youzum.net\/ja\/category\/committee\/\" rel=\"category tag\">Committee<\/a> <a href=\"https:\/\/youzum.net\/ja\/category\/news\/\" rel=\"category tag\">News<\/a> <a href=\"https:\/\/youzum.net\/ja\/category\/uncategorized\/\" rel=\"category tag\">Uncategorized<\/a>","rttpg_excerpt":"In this tutorial, we implement a Gin Config\u2013controlled PyTorch experiment pipeline in which the executable training code remains stable. At the same time, the experimental degrees of freedom are moved into declarative configuration files. We construct a nonlinear spiral binary classification task, define a configurable MLP with scoped architectural variants, and expose parameters for the&hellip;","_links":{"self":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/posts\/104481","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/comments?post=104481"}],"version-history":[{"count":0,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/posts\/104481\/revisions"}],"wp:attachment":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/media?parent=104481"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/categories?post=104481"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/tags?post=104481"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}