<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eke Chukwudi</title>
    <description>The latest articles on DEV Community by Eke Chukwudi (@eke_chukwudi_90).</description>
    <link>https://dev.to/eke_chukwudi_90</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3962628%2F5d8a167a-7016-4c62-a2ab-4bbb9e220592.jpg</url>
      <title>DEV Community: Eke Chukwudi</title>
      <link>https://dev.to/eke_chukwudi_90</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eke_chukwudi_90"/>
    <language>en</language>
    <item>
      <title>Building Medical AI for the Other 90%: A Field Report from a Solo Developer</title>
      <dc:creator>Eke Chukwudi</dc:creator>
      <pubDate>Wed, 03 Jun 2026 22:05:50 +0000</pubDate>
      <link>https://dev.to/eke_chukwudi_90/building-medical-ai-for-the-other-90-a-field-report-from-a-solo-developer-24df</link>
      <guid>https://dev.to/eke_chukwudi_90/building-medical-ai-for-the-other-90-a-field-report-from-a-solo-developer-24df</guid>
      <description>&lt;p&gt;&lt;strong&gt;Notes on architecture, licensing landmines, and why I'm building offline-first medical AI for community health workers — not radiologists.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why this exists&lt;br&gt;
There is no shortage of medical AI startups, and almost all of them are building for the same user: a radiologist or pathologist working in a well-resourced hospital with cloud connectivity, an electronic health record, and the budget to license a SaaS dashboard. That market is real, but it's also crowded, slow to adopt, and over-served.&lt;/p&gt;

&lt;p&gt;The user I care about is different. She is a Community Health Extension Worker — sometimes called a CHEW — working in a district clinic in sub-Saharan Africa, often the only clinical-grade contact a village has with the formal health system. She does not have an internet connection during the visit. She is the screening layer, the referral decision, and the patient education function all in one role. Her tools are her training, a smartphone, and whatever cheap peripherals she can carry in a shirt pocket: a digital stethoscope, sometimes a $400 smartphone-mounted fundus adapter, occasionally a portable ultrasound probe.&lt;/p&gt;

&lt;p&gt;This is the deployment context that should shape medical AI architecture. It mostly doesn't.&lt;/p&gt;

&lt;p&gt;What that constraint forces&lt;br&gt;
Three architectural decisions follow directly from the deployment context, and they are non-negotiable:&lt;/p&gt;

&lt;p&gt;Offline-first. Every inference path must run on a phone CPU or a district-clinic laptop with no internet. That means ONNX export with INT8 quantization, sub-50MB quantized weights for phone-tier models, and inference budgets measured in seconds, not milliseconds.&lt;/p&gt;

&lt;p&gt;Foundation encoders held frozen, with small trainable heads. Training a 12-billion-parameter model from scratch is not happening on the budget that serves this user. But a frozen pretrained encoder plus a small task-specific trainable head is a well-known pattern that scales cleanly across modalities. I've now applied the same dual-stage architecture across echocardiography (video), ECG signals, cervical cytology images, chest X-rays, CT volumes, and digital pathology slides. The encoder differs per modality. The training pattern does not.&lt;/p&gt;

&lt;p&gt;License-clean from day one. This one bit me hard, and it's the most non-obvious lesson of the past few months.&lt;/p&gt;

&lt;p&gt;The license trap&lt;br&gt;
If you go to HuggingFace and look at the model cards for the most-cited medical AI foundation models — vision-language pathology FMs, chest X-ray DINOv2 derivatives, multimodal biomedical CLIP models — many of them carry license tags like Apache 2.0 or MIT. Those tags govern the model code: the inference scripts, the training pipeline, the architecture definition.&lt;/p&gt;

&lt;p&gt;The weights, in several cases, are governed by something different. Not by a separate LICENSE file. By a paragraph buried in the model card README.&lt;/p&gt;

&lt;p&gt;The phrase I've seen now in multiple model cards from major labs reads, almost verbatim: "Any deployed use case commercial or otherwise is out of scope." That language doesn't appear in the legal LICENSE file, which is what most engineers check. It appears in the README, which is what compliance teams read.&lt;/p&gt;

&lt;p&gt;For a system intended for clinical deployment, those weights are radioactive. The legal license permits commercial use. The maintainers' written intent does not support it. Most institutional legal reviews respect maintainer intent.&lt;/p&gt;

&lt;p&gt;This is not a critique of those labs — they have legitimate reasons to gate deployment, including liability and regulatory considerations. It is a critique of the assumption that public, "permissive-licensed" medical AI is deployment-ready by default. Often, it isn't.&lt;/p&gt;

&lt;p&gt;The deploy-clean foundation model stack, encoders that are truly usable in a clinical product without a license addendum, is meaningfully smaller than the published benchmarks suggest.&lt;/p&gt;

&lt;p&gt;What I've built so far&lt;br&gt;
The system I'm building treats each clinical modality as a plug-in module that bolts onto a shared orchestration layer. The orchestration layer handles model routing, retrieval-augmented citation from medical guidelines, and report generation. The modules contribute domain-specific encoders and clinical heads.&lt;/p&gt;

&lt;p&gt;Current status, internal validation tier on public benchmarks:&lt;/p&gt;

&lt;p&gt;Echocardiography. Frozen video foundation model, attention pooling over clip embeddings, binary reduced-ejection-fraction classification. Internal AUC in the high 0.80s with calibrated recall.&lt;/p&gt;

&lt;p&gt;ECG. Frozen wav2vec-class encoder, linear probe across 27 SNOMED arrhythmia classes. Macro AUC around 0.91. Atrial fibrillation AUC around 0.90.&lt;/p&gt;

&lt;p&gt;Cervical cytology. Pap-smear cell type classification, lab-tier deployment, very high AUC on the public benchmark.&lt;/p&gt;

&lt;p&gt;Chest X-ray (tuberculosis). Promoted internally on a public benchmark; I'm being deliberately conservative about claiming clinical-grade performance here because the published benchmark is known to be heterogeneous.&lt;/p&gt;

&lt;p&gt;CT (lung nodule malignancy). A ConvNeXt-Tiny per-slice encoder with attention pooling across slices, AUC around 0.79. This is the locked baseline I'm now trying to beat with a multi-scale CT foundation model architecture.&lt;/p&gt;

&lt;p&gt;Pathology (lung cancer subtyping). A generic DINOv2 encoder applied to histopathology tiles, with a small Gated Attention MIL head, hitting AUC 0.85 on the public TCGA-LUNG split. This number is interesting precisely because the encoder has no pathology-specific pretraining; the headroom from a deploy-clean medical pathology FM is significant when one exists.&lt;/p&gt;

&lt;p&gt;Mammography. This is where the journey was most educational. The first five iterations on the legacy CBIS-DDSM benchmark plateaued around 0.65–0.70 AUC regardless of encoder choice. The performance ceiling was the dataset, not the architecture. Switching to the cleaner VinDr-Mammo benchmark with explicit multi-view per-breast aggregation lifted the number from 0.65 to 0.84 in a single iteration. The architectural pattern was right; the data was the bottleneck.&lt;/p&gt;

&lt;p&gt;Report orchestration. Built. Includes retrieval-augmented citation from a vector store of clinical guideline literature, with a structured output handler that flags grounded vs ungrounded claims in the generated report.&lt;/p&gt;

&lt;p&gt;The pieces that are not yet built and that I am being explicit about: AlphaFold-based therapeutic reasoning, longitudinal patient state representation, cross-modal contrastive alignment for joint queries across imaging and lab data, and a clinically-validated user interface for the community health worker. Each of these is in the architectural plan; none of them is shipping.&lt;/p&gt;

&lt;p&gt;What I learned that I think generalizes&lt;br&gt;
Three takeaways for anyone building in this space.&lt;/p&gt;

&lt;p&gt;First, the deployment context is the architecture. If you start from the deployment user, not the dataset, not the encoder, not the benchmark, the architectural choices become much easier and much more constrained. Offline-first eliminates entire categories of design options. Phone-tier inference forces aggressive quantization. The frozen-encoder + trainable-head pattern emerges naturally from the compute budget, and it has the side benefit of being deeply reusable across modalities.&lt;/p&gt;

&lt;p&gt;Second, license claims live in the README, not the LICENSE file. This is the single most important non-obvious thing I've learned in the past few months. Read the model card. Twice. Forward it to whoever does your compliance review.&lt;/p&gt;

&lt;p&gt;Third, dataset choice and aggregation strategy often dominate encoder choice. Across five mammography iterations with three different encoders, the AUC ceiling was bounded by the dataset until I switched datasets. The encoder was a small lever compared to the data. This is the boring lesson everyone says they know and almost no one acts on.&lt;/p&gt;

&lt;p&gt;If you work on this&lt;br&gt;
I'm a solo developer. The next several months are about pushing the remaining modules through internal validation, building the AlphaFold therapeutic reasoning layer, and beginning conversations about deployment partnerships in target settings.&lt;/p&gt;

&lt;p&gt;If you work on low-resource health systems, on medical imaging foundation models, on regulatory pathways for AI-as-medical-device, or on community health worker training programs; I'd genuinely like to talk.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>learning</category>
      <category>deeplearning</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Intercepting Gradients in PyTorch: Preprocess the Update Before Your Optimizer Sees It</title>
      <dc:creator>Eke Chukwudi</dc:creator>
      <pubDate>Mon, 01 Jun 2026 12:52:00 +0000</pubDate>
      <link>https://dev.to/eke_chukwudi_90/intercepting-gradients-in-pytorch-preprocess-the-update-before-your-optimizer-sees-it-5ckj</link>
      <guid>https://dev.to/eke_chukwudi_90/intercepting-gradients-in-pytorch-preprocess-the-update-before-your-optimizer-sees-it-5ckj</guid>
      <description>&lt;p&gt;Most people tune the optimizer. Almost nobody touches the thing the optimizer actually eats: the gradient. But the gap between &lt;code&gt;loss.backward()&lt;/code&gt; and &lt;code&gt;optimizer.step()&lt;/code&gt; is a real hook point, and you can do useful work there. This is a short, runnable guide to intercepting gradients and transforming them before the update lands.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;A training step is really four moves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Forward pass: compute predictions and loss&lt;/li&gt;
&lt;li&gt;Backward pass: &lt;code&gt;loss.backward()&lt;/code&gt; fills &lt;code&gt;parameter.grad&lt;/code&gt; for every parameter&lt;/li&gt;
&lt;li&gt;Update: &lt;code&gt;optimizer.step()&lt;/code&gt; reads those &lt;code&gt;.grad&lt;/code&gt; tensors and adjusts the weights&lt;/li&gt;
&lt;li&gt;Reset: &lt;code&gt;optimizer.zero_grad()&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The window we care about is between steps 2 and 3. After &lt;code&gt;backward()&lt;/code&gt;, the gradients exist as plain tensors in &lt;code&gt;p.grad&lt;/code&gt;. The optimizer has not touched them yet. That is where we intervene.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: a baseline step
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch.nn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReLU&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Adam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;loss_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MSELoss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard. Now let's get between the last two lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: transform the gradients in place
&lt;/h2&gt;

&lt;p&gt;After &lt;code&gt;backward()&lt;/code&gt;, iterate the parameters and modify each &lt;code&gt;.grad&lt;/code&gt; before stepping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;soft_threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# shrink every gradient component toward zero by lam
&lt;/span&gt;    &lt;span class="c1"&gt;# this is the core operation behind wavelet denoising
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;soft_threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire idea. Anything you can express as a function of a tensor, you can apply to the gradient: clipping, smoothing, denoising, sign-based updates, masking. The optimizer never knows the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: make it reusable and optimizer-agnostic
&lt;/h2&gt;

&lt;p&gt;Editing the training loop by hand gets messy. Wrap it instead, so the transform works with any first-order optimizer without changing your loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GradTransform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optimizer&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;param_groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage is a drop-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Adam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GradTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;soft_threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# training loop is unchanged
&lt;/span&gt;&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;Adam&lt;/code&gt; for &lt;code&gt;SGD&lt;/code&gt; and it still works, because the wrapper only touches &lt;code&gt;.grad&lt;/code&gt;, which every first-order optimizer reads the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quicker alternative: tensor hooks
&lt;/h2&gt;

&lt;p&gt;If you want the transform to fire automatically during the backward pass instead of after it, register a hook directly on a parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;soft_threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook runs as the gradient is computed. The wrapper approach is usually easier to reason about because everything happens in one obvious place, but hooks are handy when you want the change to be invisible to the rest of your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  One honest warning
&lt;/h2&gt;

&lt;p&gt;Intercepting gradients is powerful, which means it is also a good way to quietly break training. A transform that helps on a noisy problem can actively hurt on a clean one, where the gradient signal was fine to begin with. So treat any gradient transform as a hypothesis, not a free win: run it against an untouched baseline on your actual task, with the same seeds, and keep it only if the numbers say so. The hook is easy. Earning the improvement is the hard part.&lt;/p&gt;

&lt;p&gt;If you want to see this idea taken all the way, soft-thresholding the gradient is exactly the building block behind WaveGuard, a gradient denoiser I built that swaps the flat threshold for a Haar wavelet transform and gates it so it stays quiet when the gradient is already clean. I benchmarked it honestly, including a task where it actively made things worse. &lt;/p&gt;

&lt;p&gt;Write-up here: &lt;a href="https://medium.com/@chukwudieke61/adam-cant-hear-the-signal-through-the-noise-waveguard-can-ffb1d8963a38" rel="noopener noreferrer"&gt;https://medium.com/@chukwudieke61/adam-cant-hear-the-signal-through-the-noise-waveguard-can-ffb1d8963a38&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Code here: &lt;a href="https://github.com/Harry-Potter20/wavelet-grad" rel="noopener noreferrer"&gt;https://github.com/Harry-Potter20/wavelet-grad&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>deeplearning</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
