<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Logit Solutions AI]]></title><description><![CDATA[Logit Solutions AI]]></description><link>https://logitsolutionsai.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 07:55:02 GMT</lastBuildDate><atom:link href="https://logitsolutionsai.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Speech-to-text can't tell you if an AI voice said a name right]]></title><description><![CDATA[We make short, AI-voiced lessons. Before a video ships, every sentence goes through automatic checks, and then a person listens. The automatic checks kept…]]></description><link>https://logitsolutionsai.hashnode.dev/speech-to-text-can-t-tell-you-if-an-ai-voice-said-a-name-right</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/speech-to-text-can-t-tell-you-if-an-ai-voice-said-a-name-right</guid><category><![CDATA[AI]]></category><category><![CDATA[Python]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[Machine Learning]]></category><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Tue, 10 Nov 2026 23:27:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae077dfdc63aa1bdfa3a0bd/4fd4fff5-1b16-4d49-804d-48d36e2c5e13.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We make short, AI-voiced lessons. Before a video ships, every sentence goes through automatic checks, and then a person listens. The automatic checks kept failing in one place: names.</p>
<p>This post explains why a transcript can't catch a mispronounced name, and the three-part check we built instead. All numbers are our own measurements. The code is open source and linked at the end.</p>
<h2>The problem: a transcript hears what it expects</h2>
<p>The usual way to check a voice-over is to transcribe it and compare the transcript with the script. That works for missing or swapped words. For names it fails in both directions.</p>
<ul>
<li><strong>A wrong read can pass.</strong> A speech-to-text model is trained to write likely text. When a voice gets a name almost right, the model often writes the correct spelling anyway, and the mistake disappears from the transcript.</li>
<li><strong>A right read can fail.</strong> When the model doesn't know a name, it spells it some other way, even when the voice said it perfectly.</li>
</ul>
<p>Our old check did exactly this. On 32 test reads of 8 hard names, it passed only 8 of the 22 correct reads. It did fail all 10 wrong reads, but only because it failed almost every name. It couldn't tell a right read from a wrong one.</p>
<h2>Three ears instead of one</h2>
<p>Every take now goes through three listeners, each built differently:</p>
<ol>
<li><strong>Words.</strong> A speech-to-text model transcribes the take, and we compare the transcript with the script. This catches missing, extra or wrong words.</li>
<li><strong>Letters, with no language model.</strong> A second recognizer writes what it hears letter by letter, with no idea which words are likely. It catches the slips the first one smooths over, small dropped words ("from zero [to] two hundred") and takes that cut off early.</li>
<li><strong>Sounds.</strong> A third model writes the phones it hears, in IPA. For every hard word we keep a researched pronunciation, also in IPA, and compare the two sound by sound.</li>
</ol>
<p>Here is one real failing take. The name should sound like <strong>/ʃ ɪ v ɔ n/</strong>. The voice said <strong>/j ɑ b ə n/</strong>. Only the third ear sees that difference. The old check gave this take the same verdict as a correct read.</p>
<h2>Comparing sounds fairly</h2>
<p>A plain phone error rate counts every difference the same. That is too blunt: some differences are an accent, others make a different word.</p>
<p>So the comparison weighs each substitution by phonetic features (close sounds cost less than distant ones) and tolerates accent-level variation. The pass/fail rule is still strict: one clearly wrong sound fails the word. A false alarm costs one re-take; a misread name that ships costs more.</p>
<h2>The target matters as much as the check</h2>
<p>A sound-by-sound check is only as good as the pronunciation it compares against. So each hard word gets a pronunciation with a source, such as an encyclopedia's IPA, a dictionary entry or a native recording. It goes into a small lexicon file, together with the respelling the TTS engine reads.</p>
<p>The workflow becomes: find the risky words in the script, research each one with a source, write it in the TTS engine's syntax, record, then check every take.</p>
<h2>Results</h2>
<p><strong>On our own takes.</strong> 8 hard names, each read by 2 voices, once from a researched respelling and once from the raw spelling: 32 reads, 22 right and 10 wrong.</p>
<ul>
<li>Correct reads that pass: <strong>8 of 22 before, 22 of 22 after</strong></li>
<li>Wrong reads caught: <strong>10 of 10, before and after</strong></li>
</ul>
<p>One honest caveat: these 32 takes are where we set the thresholds, so treat this as a fit, not a test.</p>
<p><strong>On a held-out benchmark.</strong> There is no public benchmark for TTS name pronunciation. The closest public test of the core step (did this word sound like its target phones?) is a dataset of 2,500 recordings, 15,967 words, read by non-native English speakers and scored word by word by five experts. We ran the sound check on it with no training on the dataset.</p>
<ul>
<li>Spotting a word with an incorrect phone: <strong>ROC-AUC 0.843</strong>, against 0.788 for a plain phone error rate</li>
<li>On words of two or more syllables, where names live: <strong>0.858</strong>, against 0.838</li>
<li>Strict by design: on learners' speech it flags 27% of words and catches 78% of the words the experts marked wrong. TTS voices make far fewer accent-level slips than learners do.</li>
</ul>
<p><strong>Speed.</strong> Everything runs on a laptop CPU, with about 3.5 GB of models. With the models kept loaded, a short take is checked in 3 to 4 seconds.</p>
<h2>What it doesn't do yet</h2>
<ul>
<li>The name results come from our own takes, because no public benchmark for TTS names exists yet.</li>
<li>The sound check needs a researched pronunciation. A word that isn't in the lexicon only gets the word and letter checks.</li>
<li>It doesn't replace a person. We run three checks that fail in different ways, keep a record of every failure, and someone still listens before anything ships.</li>
</ul>
<h2>The code</h2>
<p>It's open source under the MIT licence, runs locally and sends no data anywhere. It works as a command-line tool, or as a skill an AI coding agent can follow:</p>
<p><a href="https://github.com/logitsolutionsai/say-it-right">https://github.com/logitsolutionsai/say-it-right</a></p>
<p><em>This post was written with AI assistance. The measurements are ours, and the scripts that produce them are in the repository.</em></p>
]]></content:encoded></item><item><title><![CDATA[Batch normalization, visually: the reset that keeps deep networks steady]]></title><description><![CDATA[Deep Learning, Visually #20 Watch the Short: youtube.com/shorts/yYxpjlvW69s A deep network is a chain of layers. Each layer takes a weighted sum of the…]]></description><link>https://logitsolutionsai.hashnode.dev/batch-normalization-visually-the-reset-that-keeps-deep-networks-steady</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/batch-normalization-visually-the-reset-that-keeps-deep-networks-steady</guid><category><![CDATA[Deep Learning]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[pytorch]]></category><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Mon, 09 Nov 2026 17:19:57 GMT</pubDate><content:encoded><![CDATA[<p><em>Deep Learning, Visually #20</em></p>
<p><strong>Watch the Short:</strong> <a href="https://youtube.com/shorts/yYxpjlvW69s">youtube.com/shorts/yYxpjlvW69s</a></p>
<h2>Why the numbers drift</h2>
<p>A deep network is a chain of layers. Each layer takes a weighted sum of the activations from the layer before it, passes that sum through an activation function, and hands the result to the next layer.</p>
<p>Training changes the weights at every step. So every layer keeps seeing inputs that have shifted since the last step: a new average, a new spread. In a deep network, those shifts pile up. With a big learning rate, the numbers can drift so far that they blow up, and training dies.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gfzzska51q41kxbgqpvk.png" alt="A plain network at a big learning rate: the numbers run off the chart and the weights flash red." /></p>
<h2>What batch norm does</h2>
<p>Batch normalization adds a reset after each layer. For each neuron, over the mini-batch, it does three things:</p>
<ol>
<li>It finds the average (μ_B) and the spread (σ_B) of the neuron's weighted sums.</li>
<li>It slides them so their average sits at zero, and squeezes them so their spread is one.</li>
<li>It applies a learned scale (γ) and a learned shift (β), so each layer can still pick its own range.</li>
</ol>
<p>In one line:</p>
<p>y = γ · (x − μ_B) / √(σ_B² + ε) + β</p>
<p>It sits after the weighted sum and before the activation function: Linear → BatchNorm → ReLU. Because β already gives that layer a shift, the layer needs no bias. In conv nets, there is one average and one spread per channel, taken over the mini-batch and every position.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/nil64kz70ih2ptvvtoao.png" alt="One neuron's 60 weighted sums: centered, squeezed to a set spread, then scaled and shifted by the learned γ and β." /></p>
<h2>Training and testing</h2>
<p>During training, batch norm uses each mini-batch's own average and spread, and keeps running averages of them. At test time it uses those saved running averages. In PyTorch, model.train() and model.eval() switch between the two. The defaults are eps = 1e-5 and momentum = 0.1.</p>
<h2>What it bought</h2>
<p>Sergey Ioffe and Christian Szegedy, at Google, tested it on Inception, an image network:</p>
<ul>
<li><strong>Inception:</strong> learning rate 0.0015. 31.0M steps to its best accuracy, 72.2%.</li>
<li><strong>BN-x5:</strong> learning rate 0.0075 (5×). 2.1M steps to reach 72.2%, about 14 times fewer. Best 73.0%.</li>
<li><strong>BN-x30:</strong> learning rate 0.045 (30×). 2.7M steps to reach 72.2%. Best 74.8%.</li>
</ul>
<p>BN-x5 also used changes that batch norm made possible: no dropout, weight decay cut 5×, a learning rate lowered 6× faster, no local response normalization, better shuffling and fewer photo distortions.</p>
<p>With sigmoid as the activation function, plain Inception never beat 0.1% accuracy, while BN-x5-Sigmoid reached 69.8%.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/age7pb0ugz93cl57lple.png" alt="The paper's result: a learning rate five times bigger, and the same accuracy in fourteen times fewer steps." /></p>
<h2>Our toy test</h2>
<p>We tried it on scikit-learn's 8×8 digits, with 1,500 training images and 297 test images. The network had 8 hidden layers of 64 ReLU neurons with a He start, trained by plain SGD on mini-batches of 60.</p>
<p>First, a sweep: three seeds, 1,000 steps each.</p>
<ul>
<li><strong>Plain:</strong> it trained at learning rates 0.03 and 0.1 (96–99% test accuracy). At 0.3, two of three seeds blew up. At 0.5, all three did. At 1.0, two blew up and the third stayed at 7%. At 2.0, all three blew up.</li>
<li><strong>Batch norm:</strong> every learning rate up to 2.0 trained (96–99%).</li>
</ul>
<p>Then a race at learning rate 0.5, five times the plain net's biggest safe one, same start, same mini-batches.</p>
<ul>
<li><strong>Plain:</strong> the training loss fell from 3.2 to 0.67 by step 100, then jumped to 25 at step 130 and hit NaN at step 135. Test accuracy went from 83% at step 100 to 8% at step 132. Other seeds blew up at steps 128 and 348: the exact step is chaotic.</li>
<li><strong>Batch norm:</strong> in layers 2, 4, 6 and 8, the middle stayed at 0.06 or less and the spread stayed between 0.99 and 1.06. Training loss fell to 0.06 by step 125. Test accuracy reached 96% by step 100 and 98% by step 135.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ur9v1asxuncy3m81mpkk.png" alt="The race, same learning rate: the plain network blows up and its loss shoots off the chart; with batch norm the cloud stays centered and the loss stays low." /></p>
<h2>Why does it work?</h2>
<p>The paper's idea is "internal covariate shift": each layer's inputs keep changing during training, and batch norm reduces that. Santurkar et al. (2018) tested this and argue that batch norm mostly makes the loss landscape smoother. Bjorck et al. (2018) find that it mainly allows larger learning rates. This is still debated.</p>
<h2>Limits and cousins</h2>
<p>Small mini-batches hurt it, because their averages are noisy. At a batch size of 2, Group Norm had 10.6% lower error than batch norm (ResNet-50, ImageNet). Transformers use Layer Norm, which normalizes one example at a time.</p>
<h2>Try it in PyTorch</h2>
<pre><code class="language-python">nn.Sequential(nn.Linear(256, 256, bias=False), nn.BatchNorm1d(256), nn.ReLU())
nn.Sequential(nn.Conv2d(64, 64, 3, padding=1, bias=False), nn.BatchNorm2d(64), nn.ReLU())
</code></pre>
<h2>References</h2>
<ul>
<li>Ioffe, S. &amp; Szegedy, C. (2015). Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. ICML 2015, PMLR 37:448–456. <a href="https://proceedings.mlr.press/v37/ioffe15.html">https://proceedings.mlr.press/v37/ioffe15.html</a> · <a href="https://arxiv.org/abs/1502.03167">https://arxiv.org/abs/1502.03167</a></li>
<li>Santurkar, S., Tsipras, D., Ilyas, A. &amp; Madry, A. (2018). How Does Batch Normalization Help Optimization? NeurIPS 2018. <a href="https://papers.nips.cc/paper/2018/hash/905056c1ac1dad141560467e0a99e1cf-Abstract.html">https://papers.nips.cc/paper/2018/hash/905056c1ac1dad141560467e0a99e1cf-Abstract.html</a> · <a href="https://arxiv.org/abs/1805.11604">https://arxiv.org/abs/1805.11604</a></li>
<li>Bjorck, J., Gomes, C., Selman, B. &amp; Weinberger, K. Q. (2018). Understanding Batch Normalization. <a href="https://arxiv.org/abs/1806.02375">https://arxiv.org/abs/1806.02375</a></li>
<li>Wu, Y. &amp; He, K. (2018). Group Normalization. <a href="https://arxiv.org/abs/1803.08494">https://arxiv.org/abs/1803.08494</a></li>
<li>Ba, J. L., Kiros, J. R. &amp; Hinton, G. E. (2016). Layer Normalization. <a href="https://arxiv.org/abs/1607.06450">https://arxiv.org/abs/1607.06450</a></li>
<li>PyTorch docs: <a href="https://docs.pytorch.org/docs/stable/generated/torch.nn.BatchNorm1d.html">BatchNorm1d</a> · <a href="https://docs.pytorch.org/docs/stable/generated/torch.nn.BatchNorm2d.html">BatchNorm2d</a></li>
<li>CS878 Deep Learning (Fall 2023), Lecture 12–13: Regularization &amp; Transfer Learning, Batch Normalization slides.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Why did deep networks stop learning?]]></title><description><![CDATA[Vanishing and exploding gradients: why blame fades or blows up on its way back through a deep network, and the fixes that made deep learning work]]></description><link>https://logitsolutionsai.hashnode.dev/why-did-deep-networks-stop-learning</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/why-did-deep-networks-stop-learning</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Wed, 04 Nov 2026 14:25:59 GMT</pubDate><content:encoded><![CDATA[<h2>Blame travels backward</h2>
<p>When a neural network makes a mistake, training works out how much each dial (each weight) was to blame. That blame starts at the answer and travels backward, layer by layer, so every dial knows which way to turn. This is backpropagation.</p>
<p>On the way back, the blame passes through each layer's bend, the activation function. At every layer it gets multiplied by that bend's slope. This is the chain rule: one slope per layer, all multiplied together. In a deep network, that long chain of multiplications can shrink to almost nothing, or grow out of control.</p>
<h2>A quarter at a time</h2>
<p>Older networks used an S-shaped bend, the sigmoid: σ(s) = 1 / (1 + e⁻ˢ). Its slope is σ(s)(1 − σ(s)). That slope is steepest in the middle, at s = 0, where it is exactly 0.25. Everywhere else it is smaller, and far from the middle it is almost zero (the lecture calls these neurons saturated: they "kill the gradient flow").</p>
<p>So each sigmoid layer passes back at most a quarter of the blame. Ten layers in a row, even at their steepest, pass back 0.25¹⁰ = 0.00000095 of it. That is less than a millionth.</p>
<p>We measured it in a real network: 10 layers of 256 sigmoid neurons with a good (Xavier) start, 512 random inputs and targets, and a squared-error mistake score (numpy's default_rng(0)). Here is the blame that reached each layer's dials, compared with layer 10:</p>
<ul>
<li>Layer 10: 1</li>
<li>Layer 9: 0.19</li>
<li>Layer 8: 0.051</li>
<li>Layer 7: 0.013</li>
<li>Layer 6: 0.003</li>
<li>Layer 5: 0.00069</li>
<li>Layer 4: 0.00015</li>
<li>Layer 3: 0.000035</li>
<li>Layer 2: 0.0000079</li>
<li>Layer 1: 0.00000031</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/w71pqw7hd8qsmm17blgq.png" alt="The blame shrinks plane by plane: layer 1 gets 0.0000003 of what layer 10 gets." /></p>
<p><em>The blame shrinks plane by plane: layer 1 gets 0.0000003 of what layer 10 gets.</em></p>
<p>The first layer gets about three ten-millionths of the blame, so its dials barely move. This is the <strong>vanishing gradient problem</strong>. Sepp Hochreiter worked it out in his 1991 diploma thesis at the Technical University of Munich, long before deep learning took off. Bengio, Simard and Frasconi analysed the same problem in 1994.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/9qhqt4ci4wy2j6bl5l92.png" alt="Sepp Hochreiter's 1991 diploma thesis, " /></p>
<p><em>Sepp Hochreiter's 1991 diploma thesis, "Untersuchungen zu dynamischen neuronalen Netzen" (Studies of dynamic neural networks).</em></p>
<h2>Too big: exploding blame</h2>
<p>The chain can also go the other way. If the dials start too big, the blame grows at every layer instead of shrinking. We took a healthy ReLU network and made every dial 2× too big. The blame reaching layer 1 was about 500,000× bigger than in the healthy network (512,003× when measured). With dials just 1.5× too big, it was still about 2,000× bigger.</p>
<p>Huge blame means huge steps. The lecture warns that one giant step can throw the dials into a terrible spot with a large mistake, and in the worst case the numbers overflow to Inf or NaN, and training has to restart from an earlier checkpoint. The lecture also points out that this is not only a problem for recurrent networks: it hits all very deep networks.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gb27fe8hal2ib2w1z0cu.png" alt="Gradient clipping: a cap line, and every layer's blame scaled down to fit under it." /></p>
<p><em>Gradient clipping: a cap line, and every layer's blame scaled down to fit under it.</em></p>
<h2>The fixes</h2>
<ul>
<li><strong>ReLU.</strong> ReLU is max(0, s). When a neuron is on, its slope is exactly 1, so it passes the blame back at full strength. In the same 10-layer network with ReLU and a He start, the blame stayed between 0.4 and 2.1 of layer 10's at every layer (layer 1: 0.42). It helps a lot, but it is not a cure-all: a neuron that is off passes back nothing, and the dials can still start too big.</li>
<li><strong>Good starting dials.</strong> Starting the dials at the right size keeps the blame level from layer to layer (Deep Learning, Visually #18).</li>
<li><strong>Batch normalization.</strong> It keeps each layer's values in a healthy range during training (coming in #20).</li>
<li><strong>Skip connections.</strong> Shortcuts that let the blame jump over layers, used in ResNets (coming in #35).</li>
<li><strong>Gradient clipping</strong>, for exploding blame. If the total size of the blame is bigger than a limit, scale all of it down to the limit. The direction stays the same; only the step gets smaller.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/p4bq45kh9gmqnvan44dk.png" alt="ReLU: the blame reaches the first layers at full strength." /></p>
<p><em>ReLU: the blame reaches the first layers at full strength.</em></p>
<h2>Try it in PyTorch</h2>
<p>Use ReLU layers with a He start, and clip the blame before every step:</p>
<pre><code class="language-python">import torch
import torch.nn as nn
 
layer = nn.Linear(256, 256)
nn.init.kaiming_normal_(layer.weight, nonlinearity="relu")
model = nn.Sequential(layer, nn.ReLU())
 
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
</code></pre>
<p>Or watch the blame vanish yourself. This sends one number through ten bends and asks how much blame comes back:</p>
<pre><code class="language-python">import torch as t
for f in(t.sigmoid,t.relu):
 x=t.ones(1).requires_grad_()
 y=x
 for _ in'x'*10:y=f(y)
 y.backward();print(x.grad)
</code></pre>
<p>It prints tensor([2.7848e-07]) for the sigmoid and tensor([1.]) for ReLU. Change 10 to 30: how small does the sigmoid's blame get now?</p>
<h2>References</h2>
<ul>
<li>Hochreiter, J. (1991). Untersuchungen zu dynamischen neuronalen Netzen. Diploma thesis, Technische Universität München. <a href="https://www.bioinf.jku.at/publications/older/3804.pdf">https://www.bioinf.jku.at/publications/older/3804.pdf</a></li>
<li>Bengio, Y., Simard, P., &amp; Frasconi, P. (1994). Learning long-term dependencies with gradient descent is difficult. IEEE Transactions on Neural Networks, 5(2), 157–166. <a href="https://doi.org/10.1109/72.279181">https://doi.org/10.1109/72.279181</a></li>
<li>Glorot, X., Bordes, A., &amp; Bengio, Y. (2011). Deep Sparse Rectifier Neural Networks. AISTATS, PMLR 15, 315–323. <a href="https://proceedings.mlr.press/v15/glorot11a.html">https://proceedings.mlr.press/v15/glorot11a.html</a></li>
<li>Pascanu, R., Mikolov, T., &amp; Bengio, Y. (2013). On the difficulty of training recurrent neural networks. ICML, PMLR 28(3), 1310–1318. <a href="https://proceedings.mlr.press/v28/pascanu13.html">https://proceedings.mlr.press/v28/pascanu13.html</a></li>
<li>CS878 Deep Learning, SEECS NUST: lecture 10–11 (slides 35–43) and lecture 23–24 (vanishing and exploding gradients, gradient clipping).</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Why does every AI start out random?]]></title><description><![CDATA[Weight initialization: why a network's dials must start different, and how big the random start should be]]></description><link>https://logitsolutionsai.hashnode.dev/why-does-every-ai-start-out-random</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/why-does-every-ai-start-out-random</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Wed, 04 Nov 2026 14:24:57 GMT</pubDate><content:encoded><![CDATA[<h2>Twin neurons</h2>
<p>A neural network learns by turning its dials, the weights. Every dial needs a first value. Why not give them all the same one?</p>
<p>Take a small network: 2 inputs, 4 hidden neurons, 1 output. Set every dial to 0.5 and every bias to 0. Now all four hidden neurons see exactly the same thing, so they compute the same value. They make the same mistake, so training gives each one the same push. After the push they are still identical, so the next step repeats it.</p>
<p>We trained this network for 2,000 steps. The four hidden neurons stayed exactly identical the whole time: the difference between them was 0.0 at every step. They are twins, forever.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/7w6qj0qq1ht5g4md2rap.png" alt="Every dial starts at 0.5: the four hidden neurons get the same push at every step. Step 2,000, still twins." /></p>
<p><em>Every dial starts at 0.5: the four hidden neurons get the same push at every step. Step 2,000, still twins.</em></p>
<p>Starting every dial at zero is even worse. Then nothing moves at all: every push is exactly zero, the network answers 0.5 for every input, and the loss stays at 0.693 forever (2 of 4 answers right, no better than a coin).</p>
<h2>The corner puzzle (this is XOR)</h2>
<p>The puzzle has four corners. The output should be 1 when exactly one input is 1: (0,1) and (1,0) give 1, while (0,0) and (1,1) give 0. This is the XOR problem.</p>
<p>Four twin neurons are no better than one neuron, and one neuron can only draw one straight line. As we saw in part 3, one straight line can't solve XOR. The same-start network gets stuck at 3 of 4: its outputs are 0.00, 0.67, 0.67 and 0.67, so the corner (1,1) is wrong, and the loss is stuck near 0.48.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uxpiqx99a40ubuiy24qu.png" alt="Four twins act like one neuron: one straight split. 3 of 4 right, the top-right corner is wrong." /></p>
<p><em>Four twins act like one neuron: one straight split. 3 of 4 right, the top-right corner is wrong.</em></p>
<p>Now start the dials at random instead (numpy's default_rng(1)). The first-layer dials are [[0.35, 0.82, 0.33, −1.30], [0.91, 0.45, −0.54, 0.58]], and the output dials come from the same generator. Every neuron begins a little different, so each gets its own push and learns its own part. The network gets all 4 corners right from step 18. By step 2,000 its outputs are 0.00, 1.00, 1.00 and 0.00. Its answer is no longer one straight split: it marks the two corners that should be 1 and leaves a clear diagonal band through the other two.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/lfw6alllq4c6vhx8kcrf.png" alt="Random start: four different neurons, four different pushes. 4 of 4 from step 18." /></p>
<p><em>Random start: four different neurons, four different pushes. 4 of 4 from step 18.</em></p>
<p>Over 20 random starts (seeds 0 to 19), 19 solve the puzzle.</p>
<h2>How random?</h2>
<p>Random isn't enough: the size of the random dials matters too. The CS878 lecture tests this on a deep network: 10 layers of 500 tanh neurons each, fed 1,000 random inputs. Every neuron adds up 500 inputs, each multiplied by its dial.</p>
<ul>
<li><strong>Dials too small (0.01):</strong> the spread of the values shrinks in every layer: 0.21, 0.048, 0.011, 0.0024 and on down to 0.0000003 at layer 10. The signal fades to nothing, and tiny values make tiny pushes, so training crawls.</li>
<li><strong>Dials too big (1.0):</strong> the sums get so large that tanh is pushed to its limit. In every layer, 93% of the values are stuck beyond ±0.95. There, tanh is almost flat, so the pushes vanish again.</li>
<li><strong>The right size (1/√500 ≈ 0.045):</strong> the spread stays healthy all the way down: 0.63, 0.49, 0.41, 0.36, 0.32, 0.29, 0.27, 0.25, 0.24, 0.23. About 7% of the values are stuck in layer 1, 0.4% in layer 2, and almost none after that.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/95e8v6p8n6xx1rnm8q2f.png" alt="The right dial size: healthy spreads from layer 1 to layer 10." /></p>
<p><em>The right dial size: healthy spreads from layer 1 to layer 10.</em></p>
<h2>The rule</h2>
<p>A neuron adds up its inputs: s = w₁x₁ + w₂x₂ + … + wₙxₙ. If the dials and the inputs are random with an average of zero, the spreads (variances) add up:</p>
<p><code>Var(s) = n · Var(w) · Var(x)</code></p>
<p>To keep the spread the same from one layer to the next, make n · Var(w) = 1, which gives Var(w) = 1/n: dials with a spread of 1/√n. More inputs, smaller starting dials. This is <strong>Xavier initialization</strong>, named after Xavier Glorot, who worked it out with Yoshua Bengio in 2010. Their paper also balances the backward pass, with Var(w) = 2 / (inputs + outputs). It suits tanh.</p>
<p>ReLU zeroes out half of the values, so ReLU layers need twice as much: Var(w) = 2/n. This is <strong>He initialization</strong> (Kaiming He and colleagues, 2015).</p>
<h2>Try it in PyTorch</h2>
<pre><code class="language-python">import torch.nn as nn
layer = nn.Linear(500, 500)
nn.init.xavier_normal_(layer.weight)                         # tanh layers
nn.init.kaiming_normal_(layer.weight, nonlinearity="relu")   # ReLU layers
</code></pre>
<p>For a 500-to-500 layer, xavier_normal_ draws dials with a spread of √(2 / (500 + 500)) = 1/√500 ≈ 0.045, the right size from the Short. kaiming_normal_ uses √(2/500) ≈ 0.063. PyTorch already starts every nn.Linear at random, in a range that shrinks as its inputs grow.</p>
<p>The Short's experiment fits in six lines:</p>
<pre><code class="language-python">import torch as t;n=500
for s in(.01,1,n**-.5):
  h=t.randn(1000,n)
  for _ in'x'*10:
   h=(h@t.randn(n,n)*s).tanh()
  print(f'{h.std():.1e}')
</code></pre>
<p>It prints the spread after 10 layers for each dial size: 3.0e-07 (fades to nothing), 9.8e-01 (stuck at ±1) and 2.3e-01 (healthy). Swap .tanh() for .relu(): which dial size keeps the signal alive now?</p>
<h2>References</h2>
<ul>
<li>Glorot, X., &amp; Bengio, Y. (2010). Understanding the difficulty of training deep feedforward neural networks. AISTATS, PMLR 9, 249–256. <a href="https://proceedings.mlr.press/v9/glorot10a.html">https://proceedings.mlr.press/v9/glorot10a.html</a></li>
<li>He, K., Zhang, X., Ren, S., &amp; Sun, J. (2015). Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. ICCV 2015. <a href="https://arxiv.org/abs/1502.01852">https://arxiv.org/abs/1502.01852</a></li>
<li>CS878 Deep Learning, SEECS NUST, lecture 10–11 (slides 52–72).</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Why slow down near the finish line?]]></title><description><![CDATA[Learning-rate schedules: start big, halve the step size every so often, and settle where one step size can't]]></description><link>https://logitsolutionsai.hashnode.dev/why-slow-down-near-the-finish-line</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/why-slow-down-near-the-finish-line</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sun, 01 Nov 2026 14:33:04 GMT</pubDate><content:encoded><![CDATA[<h2>Rolling down a bowl with noisy pushes</h2>
<p>Training an AI is like rolling a ball down a bowl. The ball's position is the AI's dials (its weights), the height is how many mistakes it makes, and the bottom means the fewest mistakes. Each training step moves the ball a little downhill:</p>
<p><code>w ← w − step size × push</code></p>
<p>The step size is the learning rate and the push is the gradient. But each push comes from just a few examples (a mini-batch), not all the data, so it always points a little off the true downhill direction.</p>
<p>The bowl in the Short: mistake = ½ · (w1² + 4 · w2²), a long oval with its bottom at (0, 0). The ball starts at (−4, 1.5), 4.27 from the bottom. Each push is the true slope (w1, 4 · w2) plus 0.6 × a normal random number in each direction, from numpy's default_rng(2252). All three runs below use the same noise and take 60 steps.</p>
<h2>Big steps bounce, small steps crawl</h2>
<p>With a big step size, 0.45, the ball is about 0.2 from the bottom by step 6. Then the noise takes over: near the bottom the true slope is tiny, so the push is mostly noise, and a big step turns it into a big jump past the middle. The ball bounces around and never settles. Over the last 10 steps it is 0.50 from the bottom on average.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4a214zjw3mey4zsk67ep.png" alt="Big steps: near the bottom by step 6, then every off push throws the ball past the middle. Step 60, still bouncing." /></p>
<p><em>Big steps: near the bottom by step 6, then every off push throws the ball past the middle. Step 60, still bouncing.</em></p>
<p>With a small step size, 0.02, the bounces are tiny, but the trip is slow: 3.33 from the bottom after 10 steps, 2.73 after 20, 1.75 after 40 and 1.21 after 60. Same number of steps, still far away.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3aozt8pjqh0bx0ui2esf.png" alt="Small steps: tiny bounces, but after the same 60 steps the ball is still 1.2 away. The big-step cloud stays as a grey ghost." /></p>
<p><em>Small steps: tiny bounces, but after the same 60 steps the ball is still 1.2 away. The big-step cloud stays as a grey ghost.</em></p>
<p>Lecture 09 puts it in one line: you need a high learning rate when far away and a low learning rate when close.</p>
<h2>Start big, then cut the step size in half</h2>
<p>A learning-rate schedule uses both. Start with the big step size, 0.45, and halve it every 10 steps: 0.45, 0.225, 0.113, 0.056, 0.028, 0.014.</p>
<p>The first 10 steps match the big-step run exactly. After that, each halving shrinks the bounces: the ball is 0.25 from the bottom at step 20, 0.14 at step 40 and 0.07 at step 60. Over the last 10 steps it averages 0.09, more than five times closer than big steps alone.</p>
<p>Over 2,000 different noise sequences, the last-10-step averages are: big steps 0.49, small steps 1.31, the schedule 0.09. That is about 5.6 times closer than big steps.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4d7fygkee6uhw516jkc0.png" alt="The staircase: 0.45, halved at steps 10, 20, 30, 40 and 50. Big steps end 0.5 away, the schedule 0.09 away." /></p>
<p><em>The staircase: 0.45, halved at steps 10, 20, 30, 40 and 50. Big steps end 0.5 away, the schedule 0.09 away.</em></p>
<h2>Other schedules</h2>
<p>Halving every n steps is called step decay. Lecture 09 lists other ways to shrink the learning rate α:</p>
<ul>
<li><strong>Step decay:</strong> multiply α by a decay rate (often 0.5) every n steps.</li>
<li><strong>Exponential decay:</strong> α = tᵉᵖᵒᶜʰ · α0, where the decay rate t is below 1, so α shrinks a little every epoch.</li>
<li><strong>α = α0 / (1 + decay_rate · epoch):</strong> with α0 = 0.1 and decay_rate = 1.0, the learning rate goes 0.1, 0.05, 0.033, 0.025 over epochs 0 to 3.</li>
</ul>
<p>A popular smooth option is the cosine schedule, which lowers the learning rate along half a cosine wave. SGDR (Loshchilov and Hutter) adds warm restarts: the learning rate decays, jumps back up, and decays again.</p>
<h2>Why it works</h2>
<p>In 1951, Herbert Robbins and Sutton Monro studied how to find an answer when every measurement is noisy, just like a ball pushed by a few examples at a time. They showed that noisy steps still settle if the step sizes aₜ shrink so that:</p>
<ul>
<li>Σ aₜ = ∞: added up, the steps are unlimited, so the ball can still travel as far as it needs.</li>
<li>Σ aₜ² &lt; ∞: the steps shrink fast enough for the noise they carry to average out.</li>
</ul>
<p>For example, aₜ = a0 / (1 + k · t) meets both. Halving every few steps is the practical version used in training. Strictly, halving forever adds up to a finite total and misses the first condition, but over a fixed training run it does the same job: big steps far away, small steps up close.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/rqzwj47yq935vn0hobeb.png" alt="Robbins and Monro, " /></p>
<p><em>Robbins and Monro, "A Stochastic Approximation Method" (1951): noisy steps settle when the step sizes shrink.</em></p>
<h2>Try it in PyTorch</h2>
<p>In PyTorch a schedule wraps the optimizer. This one halves the learning rate every 10 epochs:</p>
<pre><code class="language-python">scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
</code></pre>
<p>Call scheduler.step() once per epoch, after that epoch's optimizer steps. This article's schedule on its own:</p>
<pre><code class="language-python">import torch as t
o=t.optim.SGD([t.ones(1)],lr=.45)
s=t.optim.lr_scheduler.StepLR(o,10,.5)
for e in range(60):
  if e%10==0:print(e,s.get_last_lr())
  o.step();s.step()
</code></pre>
<p>It prints the staircase: 0 [0.45], 10 [0.225], 20 [0.1125], 30 [0.05625], 40 [0.028125], 50 [0.0140625]. Change 10 to 5 and run it again: how small is the step size by step 50 now?</p>
<h2>References</h2>
<ul>
<li>Robbins, H., &amp; Monro, S. (1951). A Stochastic Approximation Method. The Annals of Mathematical Statistics, 22(3), 400–407. <a href="https://doi.org/10.1214/aoms/1177729586">https://doi.org/10.1214/aoms/1177729586</a></li>
<li>Loshchilov, I., &amp; Hutter, F. (2016). SGDR: Stochastic Gradient Descent with Warm Restarts. <a href="https://arxiv.org/abs/1608.03983">https://arxiv.org/abs/1608.03983</a></li>
<li>CS231n notes, "Annealing the learning rate": <a href="https://cs231n.github.io/neural-networks-3/">https://cs231n.github.io/neural-networks-3/</a></li>
<li>CS878 Deep Learning, SEECS NUST, lecture 09 (slides 5–7).</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[What if every dial had its own step size?]]></title><description><![CDATA[Adam, step by step: why one shared step size fails, and how giving every dial its own fixes it]]></description><link>https://logitsolutionsai.hashnode.dev/what-if-every-dial-had-its-own-step-size</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/what-if-every-dial-had-its-own-step-size</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sun, 01 Nov 2026 14:32:09 GMT</pubDate><content:encoded><![CDATA[<h2>One step size for every dial</h2>
<p>An AI learns by turning dials. A network has millions of them, its weights, and training nudges every one a little at a time. To know which way to turn a dial, training measures how the mistake changes when that dial moves. That measurement is the dial's push, the gradient. It points downhill, toward a smaller mistake.</p>
<p>Plain gradient descent moves every dial with the same rule:</p>
<p><code>w ← w − step × push</code></p>
<p>There is one step size, and every dial shares it. The trouble is that hills differ. Take two dials. Both start at w = −2, and both make the smallest mistake at w = 0.</p>
<ul>
<li>The top dial sits on a steep, narrow hill: mistake = 25 · w². Its push at the start is 100.</li>
<li>The bottom dial sits on a wide, nearly flat hill: mistake = ½ · w². Its push is 2.</li>
</ul>
<p>Same position, but on the steep hill the push is fifty times bigger.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/jhawelx2mk1pnwzpa8gf.png" alt="Two dials, two hills: the same start, but the steep hill's push is fifty times bigger, and one slider sets the step size for both." /></p>
<p><em>Two dials, two hills: the same start, but the steep hill's push is fifty times bigger, and one slider sets the step size for both.</em></p>
<p>Try a big step size, 0.045. The steep dial jumps right past the bottom: from −2 to 2.5, then −3.125, then 3.906. Every jump is bigger than the last, and it flies off. Try a small step size, 0.015, which suits the steep hill: it settles in three hops (−0.5, −0.125, −0.031). Now the flat dial crawls. It needs 245 steps to get within 0.05 of the bottom. No single step size fits both.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gegvrzb62ngwzmxw32vr.png" alt="With a big step size the steep dial jumps past the bottom, higher each time, and flies off." /></p>
<p><em>With a big step size the steep dial jumps past the bottom, higher each time, and flies off.</em></p>
<h2>Each dial's own step size</h2>
<p>The fix is to stop sharing. Each dial remembers how big its pushes usually are, and divides its push by that. A dial with big pushes takes careful steps. A dial with tiny pushes takes bolder ones.</p>
<p>On the very first step, each push divided by its usual size is exactly 1 for both dials (100 out of 100, and 2 out of 2). So both dials move exactly the same distance: 0.1. After that their paths stay identical. They roll down together, overshoot a little to +0.26, swing back, and both stay within 0.05 of the bottom from step 71.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/b1bft0ycu9ljzh8r6msi.png" alt="Both dials take the same steps: a line joining them stays vertical as they roll down together." /></p>
<p><em>Both dials take the same steps: a line joining them stays vertical as they roll down together.</em></p>
<h2>What Adam computes</h2>
<p>This method is Adam, from a paper by Diederik Kingma and Jimmy Ba, published at ICLR 2015. The name comes from "adaptive moment estimation". It combines two ideas: momentum, from the last episode, and its own step size for every dial.</p>
<p>For each dial, g is its push at this step and t counts the steps (1, 2, 3, …). Adam keeps two running averages and turns them into a step:</p>
<ul>
<li>m = 0.9 · m + 0.1 · g (the average push: this is momentum)</li>
<li>v = 0.999 · v + 0.001 · g² (the average squared push: how big the pushes usually are)</li>
<li>m̂ = m / (1 − 0.9ᵗ) and v̂ = v / (1 − 0.999ᵗ) (m and v start at zero, so early on they come out too small; this corrects them)</li>
<li>step = lr · m̂ / (√v̂ + 1e-8)</li>
</ul>
<p>The 1e-8 is only there to avoid dividing by zero. The key part is the division by √v̂, which has the same size as the push. If a dial's pushes are all fifty times bigger, its m̂ and its √v̂ are both fifty times bigger, and they cancel. That is why the steep dial and the flat dial move the same.</p>
<h2>The steeper-or-flatter test</h2>
<p>Now make both hills a hundred times steeper, then a hundred times flatter, and keep every setting the same.</p>
<ul>
<li><strong>A hundred times steeper:</strong> the shared step size of 0.015 blows up on the steep hill. After one step the dial is at 148, after two at −10,952.</li>
<li><strong>A hundred times flatter:</strong> the same shared step size crawls. The flat dial needs 24,591 steps.</li>
<li><strong>Adam</strong> (step size 0.1) takes 71 steps on the original hills, 71 on the steeper ones and 71 on the flatter ones, on exactly the same path every time.</li>
</ul>
<p>That is a big part of why Adam is so popular: when a problem's scale changes, you don't have to hunt for a new step size.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/i851lllegsntn4p05aup.png" alt="The old way blows up or crawls for 24,591 steps; Adam takes the same path every time: 71 · 71 · 71." /></p>
<p><em>The old way blows up or crawls for 24,591 steps; Adam takes the same path every time: 71 · 71 · 71.</em></p>
<h2>Try it in PyTorch</h2>
<p>In PyTorch, Adam is one line. A learning rate of 1e-3 is the usual starting point:</p>
<pre><code class="language-python">opt = torch.optim.Adam(model.parameters(), lr=1e-3)
</code></pre>
<p>Here are the two dials from this article, for one step:</p>
<pre><code class="language-python">import torch as t
w=t.nn.Parameter(-2*t.ones(2))
o=t.optim.Adam([w],lr=.1)
L=25*w[0]**2+w[1]**2/2
L.backward();o.step()
print(w.data)
</code></pre>
<p>It prints <code>tensor([-1.9000, -1.9000])</code>. Both dials moved exactly 0.1, even though one push was fifty times bigger. Change 25 to 2500 and run it again: still 0.1.</p>
<h2>References</h2>
<ul>
<li>Kingma, D. P., &amp; Ba, J. (2015). Adam: A Method for Stochastic Optimization. ICLR 2015. <a href="https://arxiv.org/abs/1412.6980">https://arxiv.org/abs/1412.6980</a></li>
<li>CS878 Deep Learning, SEECS NUST, lectures 07–08 (course slides).</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[How does a rolling ball skip small bumps?]]></title><description><![CDATA[Momentum, step by step: give gradient descent a memory, and it rolls past the traps that stop it]]></description><link>https://logitsolutionsai.hashnode.dev/how-does-a-rolling-ball-skip-small-bumps</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/how-does-a-rolling-ball-skip-small-bumps</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 20:00:50 GMT</pubDate><content:encoded><![CDATA[<p>Plain gradient descent looks only at the slope under its feet. Every step is</p>
<p><code>w ← w − η ∇L(w)</code></p>
<p>and nothing else. That makes it simple, but it also makes it forgetful: the moment the ground goes flat, it stops. Momentum fixes this with one extra number per weight, a velocity that remembers the slopes it has already rolled down. The examples below follow the CS878 lectures on gradient descent and scaling the optimization, and every number comes from running the update rule.</p>
<h2>The trap</h2>
<p>Take one weight and a loss with two dips:</p>
<p><code>L(w) = 0.12 (w − 2.5)² − 0.55 · exp(−(w + 1)² / 0.12)</code></p>
<p>Start at A, w = −3.5, where the loss is 4.32. There is a shallow dip B at w = −0.904 (loss 0.881) and the real minimum C at w = 2.5 (loss 0). Between them sits a bump only 0.13 higher than B.</p>
<p>Plain gradient descent with η = 0.1 takes big steps on the steep slope, smaller and smaller ones as it nears B, and by step 21 it has arrived. At B the gradient is zero, so every step after that is zero too. At step 50, at step 100, it is still in B, at a loss of 0.88.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/i3armh5e8qp36q0voyuq.png" alt="Plain gradient descent stops in the shallow dip B: the gradient is zero there, so learning stops." /></p>
<p><em>Plain gradient descent stops in the shallow dip B: the gradient is zero there, so learning stops.</em></p>
<h2>Add a velocity</h2>
<p>Momentum keeps a velocity v and moves along it:</p>
<ul>
<li>v_t = β v_(t−1) + ∇L(w_t)</li>
<li>w_(t+1) = w_t − η v_t</li>
</ul>
<p>With β = 0.9, each step keeps 90% of the old velocity and adds the new gradient. (This is the form PyTorch uses. Lecture notes often write v = βv + (1 − β)∇L, which only rescales the learning rate.)</p>
<p>Run it from A with the same η = 0.1. By step 6 the ball is near B and its velocity is about 6. The gradient at B is zero, but the velocity isn't, so the ball keeps going: it clears the bump at step 7, overshoots all the way to w = 4.71, swings back without ever returning to the bump, and settles in C by step 89.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/kvagnzq2e0jiq8iem13f.png" alt="Momentum carries the ball over the bump; plain GD's ghost is still stuck in B." /></p>
<p><em>Momentum carries the ball over the bump; plain GD's ghost is still stuck in B.</em></p>
<h2>What v remembers</h2>
<p>Unroll the velocity and it becomes a weighted sum of every gradient so far:</p>
<p><code>v_t = ∇L_t + β ∇L_(t−1) + β² ∇L_(t−2) + …</code></p>
<p>The latest gradient counts fully, the one before 90%, then 81%, 73%, 66% and so on. The weights add up to 1/(1 − β) = 10, so momentum remembers about ten steps. With β = 0.1 they would be 1, 0.1, 0.01: it forgets almost at once. In practice β is usually around 0.9.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/zlrai7xxi65pbacb9yue.png" alt="Eight bars, heights β^k, sum 1/(1 − β) = 10: gradients that agree stack toward 10; gradients that flip cancel, staying within ±0.53." /></p>
<p><em>Eight bars, heights β^k, sum 1/(1 − β) = 10: gradients that agree stack toward 10; gradients that flip cancel, staying within ±0.53.</em></p>
<h2>Agree or cancel</h2>
<p>That memory does two different things depending on the gradients:</p>
<ul>
<li>When they agree (+1, +1, +1 …), they stack: v grows 1, 1.9, 2.71, 3.44 … toward 1/(1 − β) = 10. The step grows up to ten times.</li>
<li>When they flip sign (+1, −1, +1 …), they cancel: v goes 1, −0.1, 0.91, −0.18, 0.84, −0.25 … and settles at about ±1/(1 + β) ≈ 0.53. The step stays small.</li>
</ul>
<h2>A narrow valley</h2>
<p>Now two weights and a valley that is steep across and almost flat along: L = ½(w₁² + 50 w₂²), starting at (−4, 1), where the loss is 33. Count the steps until the loss drops below 0.001, with η = 0.015:</p>
<ul>
<li><strong>Plain GD: 298 steps.</strong> A bigger rate doesn't help: at η = 0.045, each step multiplies the across direction by 1 − 0.045 · 50 = −1.25 and it blows up. So plain GD has to crawl along the floor.</li>
<li><strong>Momentum (β = 0.9): 73 steps.</strong> The along direction keeps agreeing, so it builds speed there. But the across direction overshoots and swings from side to side.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cb2ow3u4ovjtwm1z4zj6.png" alt="The narrow valley: plain GD crawls along the floor; momentum is faster but swings across." /></p>
<p><em>The narrow valley: plain GD crawls along the floor; momentum is faster but swings across.</em></p>
<h2>Nesterov: look before you step</h2>
<p>Nesterov momentum changes one thing: it takes the gradient where the velocity is about to carry the weight, not where the weight is now.</p>
<p><code>v_t = β v_(t−1) + ∇L(w_t − η β v_(t−1))</code></p>
<p>If that look-ahead point is already uphill, the gradient there points back, and the step brakes before it overshoots. On the same valley, Nesterov needs <strong>18 steps</strong>, with almost no swing.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/scutqevd3lhzn3ba0rf4.png" alt="Nesterov looks ahead: the gradient at the look-ahead point brakes the swing. 298 · 73 · 18." /></p>
<p><em>Nesterov looks ahead: the gradient at the look-ahead point brakes the swing. 298 · 73 · 18.</em></p>
<h2>Try it in PyTorch</h2>
<p>Every weight in a network gets its own velocity, with the same shape as the weight. In PyTorch it is one line:</p>
<pre><code class="language-python">opt = torch.optim.SGD(model.parameters(), lr=0.015,
                      momentum=0.9, nesterov=True)
</code></pre>
<p>And the velocity itself, with gradients that flip sign:</p>
<pre><code class="language-python">v, out = 0.0, []
for g in [1, -1, 1, -1, 1]:
    v = 0.9 * v + g
    out.append(round(v, 2))
print(out)
</code></pre>
<p>It prints <code>[1.0, -0.1, 0.91, -0.18, 0.84]</code>. Change every g to 1 and v climbs toward 10.</p>
<h2>Key takeaways</h2>
<ul>
<li>Plain gradient descent has no memory: it stops wherever the slope is zero, even in a shallow dip.</li>
<li>Momentum keeps a velocity, v = βv + ∇L, and steps along it, so it can roll through flat spots and over small bumps.</li>
<li>v is a sum of past gradients weighted by β^k, about 1/(1 − β) = 10 steps of memory at β = 0.9.</li>
<li>Agreeing gradients stack (up to 10×); flipping ones cancel. That is why momentum speeds up along valleys.</li>
<li>Nesterov takes the gradient at the look-ahead point, so it brakes before overshooting: 298 → 73 → 18 steps in the narrow valley.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Why train on a few examples at a time?]]></title><description><![CDATA[Stochastic and mini-batch gradient descent: why every real training loop takes small, noisy steps]]></description><link>https://logitsolutionsai.hashnode.dev/why-train-on-a-few-examples-at-a-time</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/why-train-on-a-few-examples-at-a-time</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:59:46 GMT</pubDate><content:encoded><![CDATA[<p>Gradient descent, as we wrote it earlier in this series, has one quiet assumption. To take a single step, it looks at every training example. That is fine for four examples. It is not fine for a million.</p>
<p>Real training never does it that way. It takes a small random handful of examples, a mini-batch, and steps on that alone. This article shows why that works, what it costs, and how it looks in PyTorch. The numbers come from a tiny worked example and from the CS878 Deep Learning slides (lectures 07-08).</p>
<h2>The gradient is an average</h2>
<p>The loss of a model is an average over the training examples: L = (1/N) Σ ℓᵢ, where ℓᵢ is the loss on example i. A derivative of an average is the average of the derivatives, so the gradient is an average too: ∇L = (1/N) Σ ∇ℓᵢ.</p>
<p>Take a model with two weights, ŷ = w·x, a squared loss ℓᵢ = ½(ŷᵢ − yᵢ)², and four examples:</p>
<ul>
<li>x₁ = (4, −1), y₁ = 1</li>
<li>x₂ = (0, 1), y₂ = 3</li>
<li>x₃ = (1, 1), y₃ = 3</li>
<li>x₄ = (1, −1), y₄ = 1</li>
</ul>
<p>Start at w = (0, 0). There each example's gradient is −yᵢ·xᵢ, so the four arrows are (−4, 1), (0, −3), (−3, −3) and (−1, 1).</p>
<p>Put them tip to tail and they add up to (−8, −4). Divide by four and you get the true gradient, ∇L = (−2, −1). A step with learning rate ¼ moves the other way, to w = (½, ¼).</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vwadiucxi6z14jypuhln.png" alt="Four examples, four arrows, added tip to tail and divided by four: the true gradient (−2, −1)." /></p>
<p><em>Four examples, four arrows, added tip to tail and divided by four: the true gradient (−2, −1).</em></p>
<p>Now make it a million examples. The slides use n = 2²⁰, about a million images. Every single step must touch all million. That is a lot of work for one small move.</p>
<h2>A mini-batch is a cheap, honest guess</h2>
<p>So take a shortcut. Shuffle the data, cut it into small batches, and average the gradients of one batch only:</p>
<p>g_B = (1/B) Σ over i in the batch of ∇ℓᵢ</p>
<p>With our four examples and B = 2, say the shuffle gives batches {1, 3} and {2, 4}.</p>
<ul>
<li>Batch one: ½[(−4, 1) + (−3, −3)] = (−7/2, −1). Too long.</li>
<li>Batch two: ½[(0, −3) + (−1, 1)] = (−1/2, −1). Too short.</li>
</ul>
<p>One-step guesses from them would land at (7/8, ¼) and (1/8, ¼), on either side of the true landing point (½, ¼). But their average is ½[(−7/2, −1) + (−1/2, −1)] = (−2, −1), exactly the true gradient.</p>
<p>That is the key property. Over the random choice of batch, the expected value of g_B is ∇L. The estimate is noisy, but it is <strong>unbiased</strong>: it does not lean in any direction.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4j61pwys4m29ett09yku.png" alt="Batch one's gradient is too long, batch two's too short; their average lands exactly on the true gradient." /></p>
<p><em>Batch one's gradient is too long, batch two's too short; their average lands exactly on the true gradient.</em></p>
<h2>How noisy is it?</h2>
<p>If one example's gradient has a spread of σ around the true gradient, a batch of B examples has a spread of σ/√B (when the data set is much bigger than the batch).</p>
<p>That square root matters. Four times the batch costs four times the compute but only halves the noise. Going from 1 to 64 examples cuts the spread by a factor of 8. Past a point, a bigger batch buys very little extra accuracy for its cost. A small batch already points the right way.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/93vmc0716nzk3ko5b5jt.png" alt="The cloud of mini-batch gradients around the true one shrinks like σ/√B, while the cost grows like B." /></p>
<p><em>The cloud of mini-batch gradients around the true one shrinks like σ/√B, while the cost grows like B.</em></p>
<h2>A race on one budget</h2>
<p>Give three methods the same budget: one pass over 1,024 examples, called one <strong>epoch</strong>, with the same learning rate.</p>
<ul>
<li><strong>Full batch</strong> takes one careful step. After a whole epoch it is still far from the bottom.</li>
<li><strong>Pure SGD</strong> (B = 1) takes 1,024 shaky steps. It reaches the bottom but keeps jittering around it.</li>
<li><strong>Mini-batch</strong> (B = 64) takes 16 steady steps and ends near the bottom with only a small wobble.</li>
</ul>
<p>There is a hardware reason too. A GPU computes many examples in parallel. A batch of 64 is one GPU call that keeps all its lanes busy, so the mini-batch epoch is 16 calls instead of 1,024.</p>
<p>If you plot the loss against iterations, as the lecture does, full batch is smooth, SGD is jagged, and mini-batch sits in between. That middle ground is why mini-batch SGD is the default.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/acv81diw8e86v78qbhc8.png" alt="One pass over 1,024 examples, all three at once: full batch takes 1 step, B = 64 takes 16, B = 1 takes 1,024; a B = 64 step lights all 64 GPU lanes together." /></p>
<p><em>One pass over 1,024 examples, all three at once: full batch takes 1 step, B = 64 takes 16, B = 1 takes 1,024; a B = 64 step lights all 64 GPU lanes together.</em></p>
<h2>Try it in PyTorch</h2>
<p>In PyTorch the batch size lives in the <code>DataLoader</code>. One batch is one <strong>iteration</strong>, one optimizer step. One full pass over the data is an <strong>epoch</strong>.</p>
<pre><code class="language-python">loader = DataLoader(data, batch_size=64, shuffle=True)
opt = optim.SGD(net.parameters(), lr=0.1)
for epoch in range(3):
    for xb, yb in loader:
        loss = loss_fn(net(xb), yb)
        opt.zero_grad()
        loss.backward()
        opt.step()
</code></pre>
<p>MNIST has 60,000 training images. In batches of 64 that is ⌈60,000 / 64⌉ = 938 iterations per epoch, and the last batch holds the 32 images left over. You can check it without any real data:</p>
<pre><code class="language-python">import torch.utils.data as D
import torch
X = torch.zeros(60000, 1)
dl = D.DataLoader(X, 64)
*_, last = dl
print(len(dl), len(last))
</code></pre>
<p>Output:</p>
<pre><code class="language-python">938 32
</code></pre>
<p>Change 64 to 1 and you get pure SGD: 60,000 steps per epoch. Change it to 60000 and you are back to full batch: one step per epoch. The optimizer is the same <code>optim.SGD</code> either way. The batches are what make it mini-batch SGD.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/nmi247htidde4iyhvojn.png" alt="The PyTorch loop: one batch of 64 images flows through the network together, and 60,000 / 64 rounds up to 938 steps per epoch." /></p>
<p><em>The PyTorch loop: one batch of 64 images flows through the network together, and 60,000 / 64 rounds up to 938 steps per epoch.</em></p>
<h2>Noise can even help</h2>
<p>At a flat saddle point the true gradient is almost zero, so full-batch descent barely moves. A mini-batch gradient keeps a little noise, and that is enough to shake the weights loose and let them roll downhill.</p>
<p>The noise does not fix everything. In a long, narrow valley the steps still zig-zag from wall to wall. Taming that zig-zag is the job of momentum, the next idea in this series.</p>
<h2>Key takeaways</h2>
<ul>
<li>The loss is an average over examples, so its gradient is an average of per-example gradients.</li>
<li>A mini-batch gradient is a cheap estimate of the true gradient. It is noisy but unbiased: on average it is exactly right.</li>
<li>Its noise shrinks like σ/√B, so four times the batch only halves the noise at four times the cost.</li>
<li>In one epoch, mini-batches take many cheap steps that run in parallel on a GPU, between smooth full batch and jagged pure SGD.</li>
<li>In PyTorch, <code>batch_size</code> in the <code>DataLoader</code> sets it: 60,000 images in batches of 64 is 938 steps per epoch.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Too big, it explodes. Too small, it crawls.]]></title><description><![CDATA[The learning rate, step by step: why one number decides whether training works at all]]></description><link>https://logitsolutionsai.hashnode.dev/too-big-it-explodes-too-small-it-crawls</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/too-big-it-explodes-too-small-it-crawls</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:58:38 GMT</pubDate><content:encoded><![CDATA[<p>Backpropagation tells every weight which way to move: its gradient. It doesn't say how far. That's the job of the learning rate, written η (eta). Every step of gradient descent is</p>
<p><code>w ← w − η ∇L</code></p>
<p>The gradient comes from the model. The learning rate is a setting you choose, and it is the first one people tune. This article shows what it does on the smallest possible example, why there is a hard limit on how big it can be, and how practitioners pick it and change it during training. The ideas follow the CS878 Deep Learning lecture on learning rates.</p>
<h2>One weight on a bowl</h2>
<p>Take a single weight and the loss L(w) = w², a bowl with its bottom at w = 0. The gradient is 2w, so one step is</p>
<p><code>w ← w − η · 2w = (1 − 2η) · w</code></p>
<p>Start at w = 2, where the loss is 4, and try four learning rates:</p>
<ul>
<li><strong>η = 0.01:</strong> each step multiplies w by 0.98. After ten steps, w has only reached 1.634 and the loss is still 2.67. It crawls.</li>
<li><strong>η = 0.4:</strong> each step multiplies w by 0.2. Two becomes 0.4, then 0.08, then 0.016. Fast.</li>
<li><strong>η = 0.9:</strong> each step multiplies w by −0.8. The weight jumps to the other side of the bowl each time (−1.6, 1.28, −1.024, 0.819), but the jumps shrink, so it still lands.</li>
<li><strong>η = 1.1:</strong> each step multiplies w by −1.2. The jumps grow: −2.4, 2.88, −3.456, 4.147. The loss climbs 5.76, 8.29, 11.94, 17.20. It explodes.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/70kyq7bw435awadh3if1.png" alt="One weight on the bowl L = w², four learning rates: crawls, fast, zig-zags, explodes." /></p>
<p><em>One weight on the bowl L = w², four learning rates: crawls, fast, zig-zags, explodes.</em></p>
<h2>Why the edge is at one</h2>
<p>Everything depends on the factor 1 − 2η. After t steps, w_t = (1 − 2η)^t · w₀.</p>
<ul>
<li>Below η = 0.5, the factor is between 0 and 1: the weight slides in without overshooting.</li>
<li>At η = 0.5, the factor is 0: one jump lands exactly on the bottom.</li>
<li>Between 0.5 and 1, the factor is between −1 and 0: the weight flips sides but shrinks.</li>
<li>At η = 1, the factor is −1: it bounces between 2 and −2 forever.</li>
<li>Past 1, the factor is below −1: it grows every step.</li>
</ul>
<p>Nothing here is special to w². For any bowl L = (λ/2) w², with curvature λ (the second derivative), the factor is 1 − ηλ. So training is stable only if 0 &lt; η &lt; 2/λ, and one jump lands at η = 1/λ. For L = w², λ = 2: the edge is 1 and the one jump is 0.5.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qmp5namabvxoij2ljcyn.png" alt="The factor 1 − 2η decides everything: slides in below 0.5, one jump at 0.5, zig-zags up to 1, explodes past 1." /></p>
<p><em>The factor 1 − 2η decides everything: slides in below 0.5, one jump at 0.5, zig-zags up to 1, explodes past 1.</em></p>
<h2>Millions of directions</h2>
<p>A real network has millions of weights, so the loss has millions of directions, and they are not equally steep. Take two weights and L = ½(w₁² + 10 w₂²): one direction with curvature 1 (flat), one with curvature 10 (steep). Each step multiplies w₁ by (1 − η) and w₂ by (1 − 10η).</p>
<p>Starting from (4, 1):</p>
<ul>
<li><strong>η = 0.02:</strong> the steep direction shrinks fast (×0.8 per step) but the flat one barely moves (×0.98). After ten steps the weights are at (3.27, 0.11): done on the steep side, stuck on the flat side.</li>
<li><strong>η = 0.21:</strong> 0.21 is past 2/10, the edge of the steep direction. w₂ is multiplied by −1.1 each step: 1, −1.1, 1.21, −1.331, 1.464. It explodes.</li>
<li><strong>η = 2/11 ≈ 0.18:</strong> the best single fixed rate, 2/(λ_min + λ_max). Both directions shrink by 9/11 per step, and after ten steps the weights are at (0.54, 0.13).</li>
</ul>
<p>The steep direction sets the speed limit, η &lt; 2/λ_max, and the flat direction then crawls. Even the best fixed rate shrinks the error by only (κ − 1)/(κ + 1) = 9/11 per step, where κ = λ_max/λ_min = 10 is how uneven the valley is.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/6ertqowbcy3h6syw9kgt.png" alt="The valley L = ½(w₁² + 10w₂²): the steep side sets the speed limit; the best fixed step is 2/11." /></p>
<p><em>The valley L = ½(w₁² + 10w₂²): the steep side sets the speed limit; the best fixed step is 2/11.</em></p>
<h2>Picking a starting rate</h2>
<p>Two quick checks get you most of the way:</p>
<ol>
<li>Before training, look at the first loss. For softmax with C classes and small random weights, it should be about ln C, which is 2.30 for ten classes. If it isn't, something else is wrong.</li>
<li>Try a few learning rates a factor of ten apart: 1e-1, 1e-2, 1e-3, 1e-4. Run each for about a hundred iterations and keep the one whose loss drops fast without blowing up. Too small barely moves; too big turns into NaN.</li>
</ol>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/w1vluvc1ox9dy2xthu6u.png" alt="Trying 1e-4 to 1e-1 from the same starting loss of 2.30: keep the one that drops fast." /></p>
<p><em>Trying 1e-4 to 1e-1 from the same starting loss of 2.30: keep the one that drops fast.</em></p>
<h2>Try it in PyTorch</h2>
<p>The bowl example, at the learning rate that explodes:</p>
<pre><code class="language-python">w, lr = 2.0, 1.1
for t in range(4):
    w = w - lr * 2 * w
    print(round(w, 3))
</code></pre>
<p>It prints −2.4, 2.88, −3.456, 4.147. Change <code>lr</code> to 0.5 and w lands on 0 in one step.</p>
<p>In a real training loop the learning rate is one argument of the optimizer. A quick sweep tries a few values a factor of ten apart:</p>
<pre><code class="language-python">import torch
# model = your network
for lr in (1e-1, 1e-2, 1e-3, 1e-4):
    opt = torch.optim.SGD(model.parameters(), lr=lr)
    ...            # train ~100 iterations, keep the lr whose loss drops fast
</code></pre>
<p>Shrinking the rate during training (decay schedules) is its own episode.</p>
<h2>Key takeaways</h2>
<ul>
<li>The learning rate sets how far each gradient step goes; it is chosen, not learned.</li>
<li>On a bowl with curvature λ, each step multiplies the error by 1 − ηλ: stable only if η &lt; 2/λ, one jump at η = 1/λ.</li>
<li>In a real network the steepest direction sets the speed limit, so the flat directions crawl.</li>
<li>Start by checking the first loss (ln C), then try rates a factor of ten apart and keep the one that drops fast.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[How does PyTorch find every gradient by itself?]]></title><description><![CDATA[Autograd, step by step: PyTorch records the forward pass as a graph, then replays it backward]]></description><link>https://logitsolutionsai.hashnode.dev/how-does-pytorch-find-every-gradient-by-itself</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/how-does-pytorch-find-every-gradient-by-itself</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:57:37 GMT</pubDate><content:encoded><![CDATA[<p>In the last article we did backpropagation by hand. We sent the error back through a tiny network, one node at a time, and found each weight's gradient. It worked, but nobody does that for a real network with millions of weights.</p>
<p>In PyTorch you write the forward pass, call <code>L.backward()</code>, and every gradient is there. This article shows what happens in between. The engine that does it is called <strong>autograd</strong>, short for automatic differentiation.</p>
<p>We'll use the same network and the same numbers as last time, from the CS878 Deep Learning slides.</p>
<h2>The forward pass builds the graph</h2>
<p>Here is the network in PyTorch:</p>
<pre><code class="language-python">x  = torch.tensor(1.0)
w1 = torch.tensor(1/3, requires_grad=True)
w2 = torch.tensor(2.0, requires_grad=True)
y  = torch.tensor(0.0)
z  = x * w1
s  = z.relu()
yh = s * w2
L  = (yh - y)**2
</code></pre>
<p>The first four lines make the leaves of the graph: the input x, the target y and the two weights. Only the weights have <code>requires_grad=True</code>. That flag tells autograd we will want their gradients.</p>
<p>Every line after that runs one operation, and autograd records it. <code>x * w1</code> makes a multiply node that holds 1/3. <code>z.relu()</code> adds a ReLU node, still 1/3. Multiplying by w2 gives 2/3, and the squared error gives the loss, 4/9.</p>
<p>You can see the record in what each result prints. <code>z</code> prints as <code>tensor(0.3333, grad_fn=&lt;MulBackward0&gt;)</code>. The value is the number we expected. The <code>grad_fn</code> part is new: it is the function that made <code>z</code>.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/br6lnt5on9vq73qzgeda.png" alt="Typing the forward pass builds the graph, one node per line: the loss is 4/9." /></p>
<p><em>Typing the forward pass builds the graph, one node per line: the loss is 4/9.</em></p>
<h2>grad_fn links make the backward graph</h2>
<p>Every result keeps its <code>grad_fn</code>, and every <code>grad_fn</code> keeps links to the functions that made its inputs. Follow those links from the loss and you have the whole graph, pointing backward.</p>
<p>One line of code can hold more than one operation. <code>L = (yh - y)**2</code> is a subtraction and then a power, so PyTorch records two nodes: <code>L.grad_fn</code> is <code>PowBackward0</code>, and its input comes from <code>SubBackward0</code>.</p>
<p>The weights asked for gradients, so each gets an extra node at the very end of the backward graph, called <code>AccumulateGrad</code>. Think of it as a bucket: it is where the weight's <code>.grad</code> will be stored. The input x and the target y did not ask for gradients, so autograd skips them.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/07n3dmuaiflvf2yiylw7.png" alt="The backward graph: every operation is now its backward function, the arrows point down, and each weight has an AccumulateGrad bucket." /></p>
<p><em>The backward graph: every operation is now its backward function, the arrows point down, and each weight has an AccumulateGrad bucket.</em></p>
<h2>L.backward() walks the graph in reverse</h2>
<p>Calling <code>L.backward()</code> starts with a gradient of 1 at the loss and visits the nodes in reverse order. Each node knows one small rule: take the gradient from above and multiply it by its own local derivative.</p>
<ul>
<li>Pow: the local derivative of d² is 2d, so d gets 1 × 2d = 4/3.</li>
<li>Sub: the local derivative of ŷ − y with respect to ŷ is 1, so ŷ gets 4/3. The path to y is skipped.</li>
<li>Mul: a multiply node swaps values. w2 gets 4/3 × s = 4/9, and s gets 4/3 × w2 = 8/3.</li>
<li>ReLU: z was positive, so the gate is open and passes 8/3 through.</li>
<li>Mul: w1 gets 8/3 × x = 8/3. The path to x is skipped.</li>
</ul>
<p>The buckets fill: <code>w1.grad</code> is <code>tensor(2.6667)</code>, or 8/3, and <code>w2.grad</code> is <code>tensor(0.4444)</code>, or 4/9. Those are the numbers we found by hand.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cwwb55368uelizoo8vqv.png" alt="After L.backward(): the buckets hold 8/3 and 4/9, the same numbers as the hand-worked backprop." /></p>
<p><em>After L.backward(): the buckets hold 8/3 and 4/9, the same numbers as the hand-worked backprop.</em></p>
<h2>When a value is used twice, gradients add</h2>
<p>Take <code>y = w * w</code> with w = 3. The multiply node has two inputs, and both are w. On the way back, each path sends 3 (the other input's value). The two contributions meet in w's bucket and add up to 6, which is the derivative of w², 2w. This is the multivariable chain rule: when a value feeds several paths, its gradient is the sum over the paths.</p>
<p>The bucket keeps adding across calls too. Run the forward and backward pass again and <code>w.grad</code> goes from 6 to 12. That's why every training loop calls <code>optimizer.zero_grad()</code> (or sets the gradients to None) before each backward pass.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/a05m2ltwoaq9thez4vdc.png" alt="Running backward twice: the bucket jumps from 6 to 12, so training loops call zero_grad." /></p>
<p><em>Running backward twice: the bucket jumps from 6 to 12, so training loops call zero_grad.</em></p>
<h2>Why go backward?</h2>
<p>A network has one loss and millions of weights. You could also push derivatives forward, from one weight up to the loss. That's forward-mode differentiation, and it needs one pass per weight: a million weights, a million passes.</p>
<p>Reverse mode, the one autograd uses, starts from the single loss and reaches every weight in one sweep. It costs about as much as a couple of forward passes, whatever the number of weights. With one output and many inputs, going backward is the only sensible direction.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/yxkmxjxlc4kf02ohg4vj.png" alt="Reverse mode: one wavefront sweeps down from the loss through every layer and reaches all 36 weights in a single pass." /></p>
<p><em>Reverse mode: one wavefront sweeps down from the loss through every layer and reaches all 36 weights in a single pass.</em></p>
<h2>The graph is rebuilt every step</h2>
<p>After <code>backward()</code>, PyTorch frees the graph. The next forward pass records a new one. Because the graph is built while your code runs, ordinary Python works inside a model: an <code>if</code> can add a node on one step and leave it out on the next, and a loop can run a different number of times.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qrze1e3mo23rxi4fry0j.png" alt="The same code records two different graphs: with s &gt; 0 the if adds a ×2 node." /></p>
<p><em>The same code records two different graphs: with s &gt; 0 the <code>if</code> adds a ×2 node.</em></p>
<h2>Try it in PyTorch</h2>
<pre><code class="language-python">import torch
w = torch.tensor(3.,
  requires_grad=True)
(w * w).backward()
(w * w).backward()
print(w.grad)
</code></pre>
<p>It prints <code>tensor(12.)</code>, not 6, because the second backward pass adds to the first. Add <code>w.grad = None</code> between the two backward calls and it prints <code>tensor(6.)</code>.</p>
<h2>Key takeaways</h2>
<ul>
<li>Autograd records every operation of the forward pass as a graph. Each result's <code>grad_fn</code> is the function that made it.</li>
<li><code>L.backward()</code> walks that graph in reverse. Each node multiplies the gradient from above by its local derivative.</li>
<li>Weights with <code>requires_grad=True</code> collect their gradient in <code>.grad</code> through <code>AccumulateGrad</code>. Tensors without the flag are skipped.</li>
<li>Gradients from several paths add up, and <code>.grad</code> keeps adding across steps, so training loops call <code>zero_grad()</code>.</li>
<li>Reverse mode gets every gradient in one sweep, and the graph is rebuilt on every forward pass, so plain Python control flow works.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Who's to blame when a network gets it wrong?]]></title><description><![CDATA[Backpropagation, step by step: how one backward pass hands every weight its share of the blame]]></description><link>https://logitsolutionsai.hashnode.dev/who-s-to-blame-when-a-network-gets-it-wrong</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/who-s-to-blame-when-a-network-gets-it-wrong</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:56:35 GMT</pubDate><content:encoded><![CDATA[<p>When a neural network makes a wrong prediction, training has to fix it. But a network has many weights, and each one played some part in the mistake. Before we can change anything, we need to know how much each weight is to blame.</p>
<p>That's what backpropagation does. After the network makes its prediction (the forward pass), it sends the error back through the network (the backward pass). On the way, every weight picks up a number, its gradient, that says how much the loss would change if that weight moved a little. That number is the weight's share of the blame.</p>
<p>The examples below are the ones from the CS878 Deep Learning lecture slides, with the same numbers.</p>
<h2>A tiny graph, forward</h2>
<p>Start with something small enough to see all at once:</p>
<p><code>f(x, y, z) = (x + y) · z</code></p>
<p>Draw it as a graph with two steps. An add node computes d = x + y, and a multiply node computes f = d · z.</p>
<p>Put in x = 1, y = −3 and z = 4. The add node gives d = 1 + (−3) = −2. The multiply node gives f = −2 · 4 = −8. That's the forward pass: values flow from the inputs to the output.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/hx62wla96grqmqset9el.png" alt="The forward pass. The add node turns 1 and −3 into −2, and the multiply node turns −2 and 4 into −8." /></p>
<p><em>The forward pass. The add node turns 1 and −3 into −2, and the multiply node turns −2 and 4 into −8.</em></p>
<h2>The same graph, backward</h2>
<p>Now go the other way. We want to know how f changes when each input changes: ∂f/∂x, ∂f/∂y and ∂f/∂z.</p>
<p>Start at the output with a gradient of 1, because f changes exactly as much as f does.</p>
<p>A <strong>multiply node swaps</strong>: each input gets the upstream gradient times the <em>other</em> input's value. For f = d · z, the local gradients are ∂f/∂z = d and ∂f/∂d = z. So z gets 1 × (−2) = −2 (the value of d) and the sum gets 1 × 4 = 4 (the value of z).</p>
<p>An <strong>add node copies</strong>: it passes the gradient straight through to every input. For d = x + y, both local gradients are 1, so x and y each get 4 × 1 = 4.</p>
<p>Check one of them with the chain rule: ∂f/∂x = ∂f/∂d · ∂d/∂x = 4 · 1 = 4.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/b260i1r8e4g29yszfk9l.png" alt="The backward pass. Starting from 1 at the output, the multiply node sends −2 to z and 4 to the sum; the add node passes 4 to both x and y." /></p>
<p><em>The backward pass. Starting from 1 at the output, the multiply node sends −2 to z and 4 to the sum; the add node passes 4 to both x and y.</em></p>
<h2>Every node does the same job</h2>
<p>Look at what each node did. It took the gradient arriving from above, the upstream gradient, and multiplied it by its own local gradient. For any node, with <em>in</em> one of its inputs and <em>out</em> its output:</p>
<p><code>∂L/∂in = ∂L/∂out · ∂out/∂in (upstream times local)</code></p>
<p>That's the whole algorithm. Each node only needs to know its own small derivative. It never has to see the rest of the network. The chain rule from the last episode, applied node by node, from the output back to the inputs.</p>
<h2>A real network</h2>
<p>Now a tiny network: one input x = 1, a first weight w₁ = 1/3, a ReLU, a second weight w₂ = 2, and a squared-error loss against a target of y = 0.</p>
<p>Forward:</p>
<ul>
<li>z = x · w₁ = 1/3</li>
<li>s = max(0, z) = 1/3 (the ReLU's output; σ usually means the sigmoid)</li>
<li>ŷ = s · w₂ = 2/3</li>
<li>L = (ŷ − y)² = 4/9</li>
</ul>
<p>The network predicts 2/3 when the answer is 0. That's the mistake, and the loss of 4/9 measures it.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/clqdplmqk60vrmyoymtt.png" alt="The network after its forward pass. It predicts 2/3 instead of 0, a loss of 4/9." /></p>
<p><em>The network after its forward pass. It predicts 2/3 instead of 0, a loss of 4/9.</em></p>
<h2>Handing out the blame</h2>
<p>Backward, from the loss, one node at a time:</p>
<ul>
<li>At the output: ∂L/∂ŷ = 2(ŷ − y) = 4/3.</li>
<li>The multiply node ŷ = s · w₂ swaps. The second weight gets 4/3 × s = 4/3 × 1/3 = <strong>4/9</strong>, and the other branch gets 4/3 × w₂ = 4/3 × 2 = 8/3.</li>
<li>The ReLU's input was positive (z = 1/3), so the gate is open and its local gradient is 1: z gets 8/3 × 1 = 8/3. Had z been negative, the gate would be shut and pass back nothing.</li>
<li>The multiply node z = x · w₁ swaps too. The first weight gets 8/3 × x = 8/3 × 1 = <strong>8/3</strong>.</li>
</ul>
<p>Multiply the factors along the path and you get the slides' chain rule, ∂L/∂w₁ = ∂L/∂ŷ · ∂ŷ/∂s · ∂s/∂z · ∂z/∂w₁ = 4/3 · 2 · 1 · 1.</p>
<p>So the first weight gets six times as much of the blame as the second: 8/3 = 6 × 4/9. One backward pass found both numbers.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vutjq1npuzfzx6sztayz.png" alt="The backward pass through the network. The first weight's gradient is 8/3, the second weight's is 4/9." /></p>
<p><em>The backward pass through the network. The first weight's gradient is 8/3, the second weight's is 4/9.</em></p>
<h2>One step downhill</h2>
<p>Gradient descent then moves every weight at once, each against its own gradient, scaled by a learning rate α:</p>
<p><code>w ← w − α · ∂L/∂w</code></p>
<p>With α = 0.05, the first weight moves six times as far as the second: w₁ goes from 1/3 to 1/3 − 0.05 · 8/3 = 0.20, and w₂ from 2 to 2 − 0.05 · 4/9 ≈ 1.98. Run the forward pass again with the new weights: z = 0.20, s = 0.20, ŷ ≈ 0.40, and the loss drops from 0.44 to about 0.16.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/1gbb7pphvvdbmroa7g1q.png" alt="One gradient step with α = 0.05. The weights become 0.20 and 1.98, and the loss falls from 0.44 to 0.16." /></p>
<p><em>One gradient step with α = 0.05. The weights become 0.20 and 1.98, and the loss falls from 0.44 to 0.16.</em></p>
<h2>Why it matters</h2>
<p>A real network has millions of weights. Nudging them one at a time would take millions of forward passes per step. Backpropagation gets all of them in one backward pass that costs about as much as the forward pass. It does this by reusing work: the 4/3 at the output was computed once and shared by both weights.</p>
<p>It became the standard way to train neural networks after Rumelhart, Hinton and Williams showed in 1986 that it lets hidden layers learn useful features.</p>
<h2>Try it in PyTorch</h2>
<p>PyTorch runs backpropagation for you. Build the loss, call <code>backward()</code>, and every weight's gradient appears in <code>.grad</code>:</p>
<pre><code class="language-python">import torch
w = torch.tensor([1/3, 2.],
  requires_grad=True)
L = (w[1] * w[0].relu())**2
L.backward()
print(w.grad)
</code></pre>
<p>It prints <code>tensor([2.6667, 0.4444])</code>: 8/3 and 4/9, the same numbers we found by hand. (Here x = 1 and y = 0, so they drop out of the formula.) Change 1/3 to −1/3 and both gradients become zero: the ReLU outputs 0, so no blame gets through.</p>
<h2>Key takeaways</h2>
<ul>
<li>The forward pass computes values; the backward pass sends gradients from the loss back to every weight.</li>
<li>Each node multiplies the gradient from above by its own local gradient.</li>
<li>A multiply node swaps: each input gets the gradient times the other's value. An add node copies: it passes the gradient straight through. An open ReLU multiplies by 1.</li>
<li>In the slides' network, one backward pass gives the first weight 8/3 and the second 4/9, and one step with α = 0.05 takes the loss from 0.44 to 0.16.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[How does one nudge ripple through ten layers?]]></title><description><![CDATA[The chain rule, the idea that lets a network see through every one of its layers, with real numbers]]></description><link>https://logitsolutionsai.hashnode.dev/how-does-one-nudge-ripple-through-ten-layers</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/how-does-one-nudge-ripple-through-ten-layers</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:55:30 GMT</pubDate><content:encoded><![CDATA[<p>A neural network is a chain. The input goes into the first layer, the first layer's output goes into the second, and so on, until the last layer produces an answer and a loss, one number that says how wrong that answer is.</p>
<p>Training keeps asking one question: if I nudge this weight a little, how much does the loss move? For a weight ten layers down, the nudge has to travel through every layer above it before it reaches the loss. The chain rule tells you how big it is when it arrives.</p>
<h2>A network is a chain</h2>
<p>Written as math, a ten-layer network is a function inside a function inside a function:</p>
<p><code>f₁₀( ⋯ f₂( f₁(x) ) ⋯ )</code></p>
<p>Picture it as a tower of dials on one shaft: the input at the bottom, a dial for each layer above it. Turn the bottom dial a little and every dial above it turns too, but not by the same amount. Each layer has its own ratio, which can make the turn bigger or smaller.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qyv7esvbpnw70y27ucwf.png" alt="A network as a tower of dials. Turn the input at the bottom and every layer above it turns; the blue wedges show how far each one moved." /></p>
<p><em>A network as a tower of dials. Turn the input at the bottom and every layer above it turns; the blue wedges show how far each one moved.</em></p>
<h2>Two links, real numbers</h2>
<p>Take just two layers. The first triples its input, y = 3x, and the second squares that, z = y². Start the input at x = 1: then y = 3 and z = 9.</p>
<p>Now raise the input by 0.01, to x = 1.01. Then y = 3.03: it moved 0.03, three times the nudge, so its ratio is 3.</p>
<p>And z = 3.03² = 9.1809: it moved about 0.18, six times y's own move of 0.03, so its ratio is 6.</p>
<p>Why six? Write out (3 + h)² = 9 + 6h + h². For a tiny h, the h² hardly matters, so the change is about 6h. That ratio is the slope of y² at y = 3: its derivative.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/wmfdfc2di5h08a58ibgk.png" alt="Close on two links. Raising the input by 0.01 moves the first layer by 0.03 (ratio 3) and the second by 0.18 (ratio 6)." /></p>
<p><em>Close on two links. Raising the input by 0.01 moves the first layer by 0.03 (ratio 3) and the second by 0.18 (ratio 6).</em></p>
<h2>The ratios multiply</h2>
<p>The first link multiplies the nudge by 3 and the second by 6, so together it's 3 × 6 = 18. The real numbers agree: z moved 0.18 for a nudge of 0.01, which is 18 times.</p>
<p>That's the chain rule. Written with derivatives:</p>
<p><code>dz/dx = dz/dy · dy/dx = 6 × 3 = 18</code></p>
<p>Each layer only needs to know its own slope: find each link's ratio where it stands, then multiply.</p>
<h2>You've seen this on a clock</h2>
<p>A clock is a chain too. The minute hand turns 12 times as fast as the hour hand, and the second hand turns 60 times as fast as the minute hand. So the second hand turns 12 × 60 = 720 times as fast as the hour hand: you multiply the ratios along the chain, exactly as with the layers.</p>
<h2>Ten layers, ten ratios</h2>
<p>A network with ten layers multiplies ten ratios, and a product of many numbers moves fast in both directions.</p>
<ul>
<li>If every layer halves the turn, the top moves 0.5¹⁰ ≈ 0.001 as far as the input. The nudge has all but vanished.</li>
<li>If every layer doubles it, the top moves 2¹⁰ = 1024 times as far. The nudge has exploded.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cak3kx3rwr92mcxked92.png" alt="Ten layers that each double the turn. The bottom barely moves, and the top spins 1024 times as far." /></p>
<p><em>Ten layers that each double the turn. The bottom barely moves, and the top spins 1024 times as far.</em></p>
<p>This is why very deep networks were once so hard to train: the signal about how a deep weight affects the loss either faded away or grew out of control.</p>
<h2>The same rule in a real network</h2>
<p>Here is a small three-layer network: two inputs, three hidden neurons with ReLU, and one output, scored by squared error against a target of 1. With inputs 2 and 1, the hidden neurons come out at 1, 0.5 and 0.2, the output is 3, and the loss is (3 − 1)² = 4.</p>
<p>Pick one weight, w = 0.5, from the first input to the top hidden neuron, and follow its path to the loss. It has three links:</p>
<ul>
<li>the hidden neuron moves 2 times as much as w (the input it multiplies is 2);</li>
<li>the output moves 3 times as much as that hidden neuron (its weight is 3);</li>
<li>the loss moves 4 times as much as the output (the slope of (y − 1)² at y = 3).</li>
</ul>
<p>So ∂L/∂w = 2 × 3 × 4 = 24. Check it: raise w from 0.50 to 0.51, and the loss goes from 4.00 to 4.24.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ol7ue7hun4g548sg2ais.png" alt="A three-layer network. One weight's path to the loss has ratios 2, 3 and 4, so the loss moves 24 times as much as the weight." /></p>
<p><em>A three-layer network. One weight's path to the loss has ratios 2, 3 and 4, so the loss moves 24 times as much as the weight.</em></p>
<h2>Try it in PyTorch</h2>
<p>PyTorch applies the chain rule for you. <code>torch.func.grad</code> returns a function's derivative, even when the function is a chain of other functions:</p>
<pre><code class="language-python">from torch import tensor, func
y = lambda x: 3 * x
z = lambda y: y ** 2
f = lambda x: z(y(x))
x = tensor(1.)
print(func.grad(f)(x))
</code></pre>
<p>It prints <code>tensor(18.)</code>, the same 3 × 6 we found by hand. Add a third layer that halves its input, and the gradient of the longer chain should come out at 9.</p>
<h2>Key takeaways</h2>
<ul>
<li>A network is a chain of layers, and a nudge at the bottom ripples through every layer above it.</li>
<li>Each layer scales the nudge by its own ratio: its local slope, the derivative.</li>
<li>The chain rule: multiply the ratios along the chain. dz/dx = dz/dy · dy/dx.</li>
<li>With many layers the product can shrink to almost nothing (0.5¹⁰ ≈ 0.001) or blow up (2¹⁰ = 1024).</li>
<li>In a real network, a weight's effect on the loss is the product of the ratios along its path: 2 × 3 × 4 = 24.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[How do you find a valley blindfolded?]]></title><description><![CDATA[Gradient descent, the walk every neural network takes while it learns, step by step with real numbers]]></description><link>https://logitsolutionsai.hashnode.dev/how-do-you-find-a-valley-blindfolded</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/how-do-you-find-a-valley-blindfolded</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:54:06 GMT</pubDate><content:encoded><![CDATA[<p>Imagine standing in the hills with a blindfold on, trying to reach the bottom of the valley. You can't see the valley or the path. All you have is what you feel under your feet: which way the ground tilts, and how steeply.</p>
<p>That's enough. If the ground tilts down to your left, step left, then feel again. Repeat until the ground is flat. That loop is gradient descent, and it's how almost every neural network is trained.</p>
<h2>The ground is the loss</h2>
<p>A network in training is in the same position. Its loss, one number that says how wrong its answers are, depends on its weights: change a weight and the loss goes up or down.</p>
<p>Picture a network with just two weights. For every pair of weights you can compute the loss, so you can draw it as a height over a flat map of the two weights. That gives a landscape. Where you stand on the map is the current weights. The height under your feet is the current loss. Training means finding the lowest point.</p>
<p>In our example the landscape is a long valley:</p>
<p><code>loss = (w₁ − 3)² + 3 · (w₂ − 1)²</code></p>
<p>The bottom is at w₁ = 3 and w₂ = 1, where the loss is 0. We start at w₁ = 0 and w₂ = 3, where the loss is 21.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5eu4ckpqhjxvsqy3cnv0.png" alt="The valley the network can't see. The ball is the current weights; the blue patch is the only ground it can feel." /></p>
<p><em>The valley the network can't see. The ball is the current weights; the blue patch is the only ground it can feel.</em></p>
<p>The network never sees this picture. A real network has millions of weights, so its landscape can't be drawn at all. Like the hiker, it only knows the ground where it stands.</p>
<h2>The slope is the gradient</h2>
<p>The thing you feel under your feet has a name: the gradient. It's the slope of the loss in every weight's direction at once, and it points uphill, the way the loss grows fastest.</p>
<p>At our starting point the gradient is (−6, 12). The 12 says the loss climbs steeply if weight 2 grows. The −6 says it also climbs if weight 1 shrinks. Going uphill would be a bad idea, so gradient descent does the opposite and steps against the gradient, downhill.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/iumf92xobto63o6swm6c.png" alt="The gradient points uphill (grey arrow). The step goes the other way, downhill (blue)." /></p>
<p><em>The gradient points uphill (grey arrow). The step goes the other way, downhill (blue).</em></p>
<h2>How big is a step?</h2>
<p>Each step is the gradient times a small number called the learning rate, written η (eta):</p>
<p><code>w ← w − η · ∇L</code></p>
<p>With a learning rate of 0.1, the first step moves the weights by 0.1 × (−6, 12) = (−0.6, 1.2) the other way. They go from (0, 3) to (0.6, 1.8), and the loss drops from 21 to 7.7.</p>
<p>Then you feel the slope again and take the next step. Here is what twenty steps do:</p>
<ul>
<li>after 1 step, the loss is 7.7;</li>
<li>after 2 steps, 4.0;</li>
<li>after 5 steps, 0.97;</li>
<li>after 10 steps, 0.10;</li>
<li>after 20 steps, 0.001, almost zero.</li>
</ul>
<p>Notice that the steps get shorter by themselves. Near the bottom the ground flattens, so the gradient shrinks, and a small gradient times the learning rate makes a small step. Nobody tells the network to slow down near the minimum. The slope does it.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cs1hdyc32usjpwm804vn.png" alt="Twenty real steps at learning rate 0.1. The loss goes from 21 to almost zero, and the steps shrink as the ground flattens." /></p>
<p><em>Twenty real steps at learning rate 0.1. The loss goes from 21 to almost zero, and the steps shrink as the ground flattens.</em></p>
<h2>The learning rate is your stride</h2>
<p>The gradient gives the direction, and the learning rate decides how far you go each time. To see why that matters, look along the valley floor from the side, where it's a simple curve, loss = (w − 3)², starting at w = 0 with a loss of 9.</p>
<ul>
<li><strong>Learning rate 0.1:</strong> the hops shrink as the curve flattens, and the ball settles at the bottom.</li>
<li><strong>Learning rate 0.01:</strong> tiny steps. After twenty of them the loss is still 4.0. You're walking the right way, just very slowly.</li>
<li><strong>Learning rate 1.1:</strong> giant leaps. Every step overshoots the bottom and lands higher on the other side. Four steps later the loss has grown from 9 to 38.7, and it keeps growing.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/wqr40j3ljc0n8zc442kt.png" alt="The valley floor from the side. At 1.1 every step leaps over the bottom and climbs (grey arcs); at 0.1 the hops shrink and the ball settles at the bottom (blue)." /></p>
<p><em>The valley floor from the side. At 1.1 every step leaps over the bottom and climbs (grey arcs); at 0.1 the hops shrink and the ball settles at the bottom (blue).</em></p>
<p>There's a simple reason for the blow-up. On this curve, each step multiplies your distance from the bottom by (1 − 2η). With η = 0.1 that's 0.8, so you get 20% closer every step. With η = 1.1 it's −1.2: you land on the other side, 20% farther away than before.</p>
<p>That's why the learning rate is usually the first setting people tune. A common recipe from the lecture notes: try 0.1, 0.01, 0.001 and 0.0001, and keep the one that makes the loss drop fast within the first hundred steps.</p>
<h2>Try it in PyTorch</h2>
<p>PyTorch can compute the gradient for you. <code>torch.func.grad</code> turns a function into its slope:</p>
<pre><code class="language-python">from torch import tensor, func
L = lambda w: (w - 3) ** 2
w = tensor(0.)
for _ in range(20):
  w -= 0.1 * func.grad(L)(w)
print(w)
</code></pre>
<p>It prints <code>tensor(2.9654)</code>: twenty steps from 0 get within 0.035 of the bottom at 3. Change 0.1 to 1.1 and run it again to watch the steps leap out of the valley.</p>
<h2>Key takeaways</h2>
<ul>
<li>Training is a walk down the loss landscape, where your position is the weights and your height is the loss.</li>
<li>The gradient is the slope under your feet. It points uphill, so each step goes against it: w ← w − η · ∇L.</li>
<li>Steps shrink by themselves near the bottom, because the slope flattens.</li>
<li>The learning rate is your stride. Too small and you crawl, too big and you leap out of the valley.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[This is what a confident mistake looks like]]></title><description><![CDATA[Cross-entropy, the loss behind almost every classifier, as a landscape and as a bet]]></description><link>https://logitsolutionsai.hashnode.dev/this-is-what-a-confident-mistake-looks-like</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/this-is-what-a-confident-mistake-looks-like</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:53:01 GMT</pubDate><content:encoded><![CDATA[<p>A classifier doesn't just pick an answer. It gives every class a score, and softmax turns those scores into probabilities: cat, dog, bird. To learn, the model needs one number that says how good those probabilities were. For classifiers, that number is almost always the cross-entropy loss.</p>
<h2>One number, one log</h2>
<p>Cross-entropy looks at a single probability: the one the model gave to the right answer. Call it p. The loss is minus its log:</p>
<p><code>loss = −log(p)</code></p>
<p>If the picture is a cat and the model said 67% cat, the loss is −log(0.67) ≈ 0.4. If it said 1% cat, the loss is −log(0.01) ≈ 4.6.</p>
<h2>The loss as a landscape</h2>
<p>Take a classifier with three classes, and hold the bird score still. Two numbers are left: the cat score and the dog score. For every pair, cross-entropy gives a loss. Make that loss the height, and you get a landscape.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3v4w4ul43h6ew5115u5g.png" alt="The loss over the cat and dog scores: a landscape. The ball is a confidently wrong model: cat 1%, loss 4.61." /></p>
<p><em>The loss over the cat and dog scores: a landscape. The ball is a confidently wrong model: cat 1%, loss 4.61.</em></p>
<p>Where the cat score wins, the ground is flat and close to zero. A model that's confident and correct sits there at almost no cost: at 98% cat, the loss is 0.02.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/fqjr8v1sbhzru3ddgahf.png" alt="Where the cat score wins, the ground is flat and close to zero." /></p>
<p><em>Where the cat score wins, the ground is flat and close to zero.</em></p>
<p>Where the dog score wins, the ground climbs, and it never levels off. Far enough up that ramp the loss grows by one for every extra point of dog score. There is no flat spot for a confident mistake.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cfqie1n4hxw7lcxlmyqk.png" alt="Where the dog score wins, the ground keeps climbing." /></p>
<p><em>Where the dog score wins, the ground keeps climbing.</em></p>
<h2>Training rolls downhill</h2>
<p>Training follows the slope. For softmax with cross-entropy, the slope with respect to each score is just the prediction minus the truth, where the truth is 1 for the right class and 0 for the others. So each training step moves the scores by that amount, times a learning rate:</p>
<p><code>z ← z − (p − y)</code></p>
<p>Start at the confident mistake, cat 1% and dog 99%, with a learning rate of 1. Three steps take the cat from 1% to 7%, to 32%, to 64%, and the loss falls from 4.61 to 0.44. The ramp is steep exactly where the model is most wrong, so the push back is strongest there.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/7qjwtlce87mc6ra9vr4l.png" alt="Three real gradient-descent steps from the top of the ramp: cat 1% → 64%." /></p>
<p><em>Three real gradient-descent steps from the top of the ramp: cat 1% → 64%.</em></p>
<h2>The same loss as a bet</h2>
<p>Here's another way to see it. Think of the model's probabilities as a bet: 100 chips spread across the answers, 67 on cat, 24 on dog and 9 on bird. When the answer turns out to be a cat, cross-entropy sends a bill: minus the log of the cat's share, −log(67/100) ≈ 0.4.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/a44bycmo5i6v0u84poa3.png" alt="The model bets 100 chips. It's a cat, so the bill is −log(0.67) ≈ 0.4." /></p>
<p><em>The model bets 100 chips. It's a cat, so the bill is −log(0.67) ≈ 0.4.</em></p>
<p>Halve the chips on the truth, and the bill grows by the same amount every time, log(2) ≈ 0.69:</p>
<ul>
<li>67 chips cost 0.40;</li>
<li>34 chips cost 1.09;</li>
<li>17 chips cost 1.79;</li>
<li>one chip costs 4.61;</li>
<li>zero chips: there is no limit.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gpx469xmxjqta0nb3xvk.png" alt="Every halving adds 0.69. One chip costs 4.6; at zero, the bill has no limit." /></p>
<p><em>Every halving adds 0.69. One chip costs 4.6; at zero, the bill has no limit.</em></p>
<p>That's why a confident mistake costs so much: being sure of the wrong answer means betting almost nothing on the truth, and every halving of that bet keeps adding to the bill.</p>
<h2>Try it in PyTorch</h2>
<p>You never write the log yourself. <code>nn.CrossEntropyLoss</code> takes the raw scores and the right class and does the softmax and the log for you:</p>
<pre><code class="language-python">import torch
from torch import nn
z = torch.tensor([[2., 1, 0]])
y = torch.tensor([0])  # cat
print(nn.CrossEntropyLoss()(z, y))
</code></pre>
<p>It prints <code>tensor(0.4076)</code>, the 0.4 from above. Pass it probabilities instead of raw scores and it applies softmax a second time, and the loss comes out wrong.</p>
<h2>Key takeaways</h2>
<ul>
<li>Cross-entropy = −log(probability of the right answer).</li>
<li>Over the scores it is a landscape: flat where the model is right, a ramp with no top where it is confidently wrong.</li>
<li>Its slope is prediction minus truth, so training pushes hardest where the model is most wrong.</li>
<li>As a bet: every halving of the chips on the truth adds 0.69, and zero chips has no limit.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[How does a network know it's wrong?]]></title><description><![CDATA[Loss functions turn every mistake into one number. Here's how squared and absolute loss score the same misses very differently]]></description><link>https://logitsolutionsai.hashnode.dev/how-does-a-network-know-it-s-wrong</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/how-does-a-network-know-it-s-wrong</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:52:00 GMT</pubDate><content:encoded><![CDATA[<p>A neural network can't feel embarrassed when it's wrong. It needs a way to measure its mistakes, so it knows which direction to improve.</p>
<p>That measurement is the loss function. It turns all of a network's errors into a single number, and training is the process of pushing that number down.</p>
<h2>One number for "how wrong"</h2>
<p>A loss function compares the network's predictions with the right answers and returns one number:</p>
<ul>
<li>Zero means every prediction is perfect.</li>
<li>Bigger means worse.</li>
</ul>
<p>That's it. Everything a network learns, it learns by trying to make this number smaller.</p>
<h2>A simple example</h2>
<p>Say a network draws a line to predict three data points. It doesn't fit perfectly. Its predictions miss the three points by 3, 2, and 1.</p>
<p>How bad is that, in one number? It depends on which loss function you pick.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/1fpzb4kcib44qmukacv2.png" alt="The network's line misses three points by 3, 2 and 1." /></p>
<p><em>The network's line misses three points by 3, 2 and 1.</em></p>
<h2>Squared loss</h2>
<p>The squared loss squares each miss, then adds them up:</p>
<ul>
<li>3² = 9</li>
<li>2² = 4</li>
<li>1² = 1</li>
</ul>
<p>The total is 9 + 4 + 1 = 14. When you average it over all the data points, this is called the <strong>mean squared error</strong>, or MSE. Here, that's 14 ÷ 3 ≈ 4.7.</p>
<p>Squaring makes every miss positive, and it punishes big misses much more than small ones. A miss of 3 costs nine times as much as a miss of 1.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/e912ixl6gv9mdr583xre.png" alt="Squared loss: the misses 3, 2 and 1 cost 9, 4 and 1, for a total of 14." /></p>
<p><em>Squared loss: the misses 3, 2 and 1 cost 9, 4 and 1, for a total of 14.</em></p>
<h2>Absolute loss</h2>
<p>The absolute loss is simpler. It just adds up the size of each miss, ignoring whether the prediction was too high or too low:</p>
<p>3 + 2 + 1 = 6.</p>
<p>Averaged over the data points, this is called the <strong>mean absolute error</strong>, or MAE. Here, that's 6 ÷ 3 = 2.</p>
<h2>What one outlier does</h2>
<p>Now a new point arrives, and the network misses it by 5.</p>
<ul>
<li>Squared, that one miss costs 5² = 25. That's more than all the other misses combined (14).</li>
<li>The absolute loss only adds 5.</li>
</ul>
<p>The totals jump from 14 to 39 for squared loss, but only from 6 to 11 for absolute loss.</p>
<p>So one unusual point can dominate the squared loss. If your data has a few wild outliers, a network trained with squared loss will bend over backwards to reduce them, sometimes at the expense of fitting everything else.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/b109euopudzuvlxs3y5a.png" alt="One new point misses by 5. Squared, it costs 25, more than the other three misses combined. Absolute only adds 5." /></p>
<p><em>One new point misses by 5. Squared, it costs 25, more than the other three misses combined. Absolute only adds 5.</em></p>
<h2>How training uses the loss</h2>
<p>Training adjusts the network's weights to push the loss down, one small step at a time. Each step asks: "If I nudge this weight a little, does the loss go up or down?" Then it moves the weight in the direction that lowers the loss.</p>
<p>Which loss you choose shapes what the network cares about. Squared loss cares a lot about big mistakes. Absolute loss treats every unit of error the same.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/97gb9rqxwqtexgsmeehh.png" alt="Training nudges the weights to push the loss down, one small step at a time." /></p>
<p><em>Training nudges the weights to push the loss down, one small step at a time.</em></p>
<h2>Quick check</h2>
<p>Your data has a few wild outliers. Which loss stays calmer: squared, or absolute?</p>
<p><strong>Answer: absolute.</strong> It grows in step with each miss, while squaring blows big misses up.</p>
<h2>Key takeaways</h2>
<ul>
<li>A loss function turns all of a network's errors into one number. Zero is perfect.</li>
<li>Squared loss squares each miss (3, 2, 1 → 9 + 4 + 1 = 14). Averaged, it's the mean squared error.</li>
<li>Absolute loss adds the miss sizes (3 + 2 + 1 = 6). Averaged, it's the mean absolute error.</li>
<li>Squared loss is very sensitive to outliers: a single miss of 5 costs 25.</li>
<li>Training adjusts the weights, step by step, to push the loss down.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[How do raw scores become probabilities?]]></title><description><![CDATA[Softmax, the last step of almost every classifier, explained with three numbers]]></description><link>https://logitsolutionsai.hashnode.dev/how-do-raw-scores-become-probabilities</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/how-do-raw-scores-become-probabilities</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:50:59 GMT</pubDate><content:encoded><![CDATA[<p>When a neural network looks at a photo and decides "cat," it doesn't jump straight to an answer. Its last layer produces a raw score for every possible class. Those scores are useful, but they aren't probabilities yet.</p>
<p>Softmax is the small function that turns them into probabilities. It takes two steps, and you can do both by hand.</p>
<h2>Raw scores are not probabilities</h2>
<p>Say a network has three classes: cat, dog, and bird. For one image, its last layer gives these raw scores (often called logits):</p>
<ul>
<li>Cat: 2</li>
<li>Dog: 1</li>
<li>Bird: 0</li>
</ul>
<p>Cat clearly scores highest. But these numbers have two problems if you want to read them as probabilities. They can be negative, and they don't add up to 1. A score of 2 doesn't tell you "how sure" the network is.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/bw22azvdvrhtwj5txkhb.png" alt="The network's last layer gives one raw score per class: cat 2, dog 1, bird 0." /></p>
<p><em>The network's last layer gives one raw score per class: cat 2, dog 1, bird 0.</em></p>
<h2>Step 1: take the exponential</h2>
<p>First, softmax raises the number e (about 2.718) to the power of each score. This is called taking the exponential.</p>
<ul>
<li>Cat: e² ≈ 7.4</li>
<li>Dog: e¹ ≈ 2.7</li>
<li>Bird: e⁰ = 1</li>
</ul>
<p>The exponential has a useful property: its result is always positive, even when the score is negative. A score of −3 becomes e⁻³ ≈ 0.05, small but still positive. It also stretches the gaps, so higher scores pull further ahead.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/onvv0fsn356p7p9d07zx.png" alt="Step 1: the exponential turns 2, 1 and 0 into about 7.4, 2.7 and 1. Every result is positive." /></p>
<p><em>Step 1: the exponential turns 2, 1 and 0 into about 7.4, 2.7 and 1. Every result is positive.</em></p>
<h2>Step 2: divide by the total</h2>
<p>Next, add the three results: 7.4 + 2.7 + 1 ≈ 11.1. Then divide each one by that total.</p>
<ul>
<li>Cat: 7.4 ÷ 11.1 ≈ 67%</li>
<li>Dog: 2.7 ÷ 11.1 ≈ 24%</li>
<li>Bird: 1 ÷ 11.1 ≈ 9%</li>
</ul>
<p>Together, they add up to 100%. Now the numbers are real probabilities: all positive, and they sum to one.</p>
<p>As a formula, for a class with score zᵢ: softmax(zᵢ) = e^(zᵢ) ÷ (the sum of e^(z) over all classes).</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/wrgs6uw6we5loim1ww3j.png" alt="Step 2: divide each exponential by the total. Cat gets 67%, dog 24%, and bird 9%, adding up to 100%." /></p>
<p><em>Step 2: divide each exponential by the total. Cat gets 67%, dog 24%, and bird 9%, adding up to 100%.</em></p>
<h2>Why "soft" max?</h2>
<p>A hard max would simply pick the winner and give it everything: cat 100%, dog 0%, bird 0%. That throws away information about how close the race was.</p>
<p>Softmax is the soft version. The winner gets the biggest share, but the others keep a share too. That matters during training, because the network can learn from how confident it was, not just whether it was right.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/850vza7v2ce4sqitzqar.png" alt="A hard max would give cat everything and the others nothing. Softmax keeps a share for every class." /></p>
<p><em>A hard max would give cat everything and the others nothing. Softmax keeps a share for every class.</em></p>
<h2>Quick check</h2>
<p>Add 10 to every score: cat 12, dog 11, bird 10. Do the probabilities stay the same, or does cat's share grow?</p>
<p><strong>Answer: they stay the same.</strong> Adding 10 multiplies every exponential by the same amount, e¹⁰. That common factor cancels when you divide by the total. Only the differences between scores matter.</p>
<h2>Key takeaways</h2>
<ul>
<li>A classifier's last layer outputs raw scores, which can be negative and don't sum to one.</li>
<li>Softmax takes the exponential of each score, which makes every value positive.</li>
<li>Then it divides by the total, so the results add up to 100%.</li>
<li>Scores of 2, 1 and 0 become about 67%, 24% and 9%.</li>
<li>Shifting every score by the same amount doesn't change the result. Only the gaps matter.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Without one bend, 100 layers act like one]]></title><description><![CDATA[Why every neural network needs an activation function, explained with two simple equations]]></description><link>https://logitsolutionsai.hashnode.dev/without-one-bend-100-layers-act-like-one</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/without-one-bend-100-layers-act-like-one</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:49:49 GMT</pubDate><content:encoded><![CDATA[<p>Deep networks stack layer after layer. But there's a catch hiding in that stack. Without one small ingredient, a hundred layers can do exactly what one layer does, and nothing more.</p>
<p>That ingredient is the activation function. Here's why it matters, in a few lines of algebra.</p>
<h2>What a layer does</h2>
<p>Every layer in a neural network does two things:</p>
<ol>
<li>It multiplies and adds: each input is multiplied by a weight, the results are summed, and a bias is added.</li>
<li>It passes the result through an activation function: a small bend.</li>
</ol>
<p>The first step is simple arithmetic. The second step is the one that's easy to overlook, and it's the one that makes depth worth having.</p>
<h2>What happens without the bend</h2>
<p>Let's remove the activation function and watch what happens with two tiny layers.</p>
<ul>
<li>Layer one doubles its input and adds one: <code>h = 2x + 1</code>.</li>
<li>Layer two triples that and subtracts four: <code>y = 3h − 4</code>.</li>
</ul>
<p>Now put them together. Substitute the first into the second: <code>y = 3(2x + 1) − 4 = 6x − 1</code>.</p>
<p>Two layers, and the result is a single multiply-and-add. It's still just one straight line.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/006fuly20gs5m00ukypl.png" alt="Two layers without a bend collapse into a single line, 6x − 1" />
<em>Two layers without a bend: 2x + 1, then 3h − 4. Combined, they collapse into a single line, 6x − 1.</em></p>
<h2>Stacking doesn't help</h2>
<p>This isn't a coincidence of the numbers we picked. Any multiply-and-add fed into another multiply-and-add is still a multiply-and-add.</p>
<p>So stack a hundred of these layers, and they still collapse into one. All that depth, and the network can only draw a straight line.</p>
<h2>Add the bend: ReLU</h2>
<p>Now add an activation function. The most popular one is called ReLU (rectified linear unit). Its rule is:</p>
<ul>
<li>Negative numbers become zero.</li>
<li>Positive numbers pass straight through.</li>
</ul>
<p>So −3 becomes 0, and 2 stays 2. That's it. It looks almost too simple to matter.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/t1hzjnnkmlxengv6oh9h.png" alt="ReLU: negative inputs become zero, positive inputs pass straight through" />
<em>ReLU: negative inputs become zero, positive inputs pass straight through.</em></p>
<h2>From bends to curves</h2>
<p>That one kink at zero is enough to stop the collapse. The layers can't be merged into a single line anymore.</p>
<p>Better still, bends can be combined. Add two ReLUs, one of them flipped, and you get a peak. Combine many of them, with different positions and slopes, and they trace a curve, one straight piece at a time.</p>
<p>This is how networks build complicated functions out of very simple parts.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vkr6crgjzzjm6iyvuhga.png" alt="Many ReLU pieces combine to trace a curve" />
<em>Many ReLU pieces, each a straight segment with one bend, combine to trace a curve.</em></p>
<h2>Sigmoid vs ReLU</h2>
<p>Older networks often used a smooth S-shaped curve called sigmoid, which squashes any number into the range between 0 and 1.</p>
<p>Today, ReLU is the standard choice in most networks, because it's simple and fast to compute. It also tends to make deep networks easier to train.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/z3hhdvvtjk7gg08o4xa5.png" alt="Sigmoid, a smooth S-curve, compared with ReLU" />
<em>Sigmoid, a smooth S-curve, was common in older networks. ReLU is the standard choice today.</em></p>
<h2>Quick check</h2>
<p>A network has ten layers, with no activation functions. Can it draw a curve, or only a straight line?</p>
<p><strong>Answer: only a straight line.</strong> Without a bend, all ten layers collapse into one multiply-and-add.</p>
<h2>Key takeaways</h2>
<ul>
<li>Every layer multiplies, adds, and then applies an activation function.</li>
<li>Without the activation, any stack of layers collapses into a single straight line, like 2x + 1 and 3h − 4 becoming 6x − 1.</li>
<li>ReLU turns negatives into zero and passes positives through. That one bend stops the collapse.</li>
<li>Many bends combined can trace curves.</li>
<li>Sigmoid was common in older networks. ReLU is the default today.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[How do hidden layers bend a straight line?]]></title><description><![CDATA[They don't. They join many straight lines into shapes, and that's where a network's power comes from]]></description><link>https://logitsolutionsai.hashnode.dev/how-do-hidden-layers-bend-a-straight-line</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/how-do-hidden-layers-bend-a-straight-line</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:48:51 GMT</pubDate><content:encoded><![CDATA[<p>A single neuron can only draw one straight line. Yet neural networks recognize faces, read handwriting, and trace curved boundaries everywhere. So where do the curves come from?</p>
<p>The surprising answer: from straight lines, lots of them, combined by a hidden layer.</p>
<h2>One line can't fence in a cluster</h2>
<p>A neuron splits the plane in two with a straight line. Points on one side get a "yes," and points on the other side get a "no."</p>
<p>That works when the classes sit on opposite sides of the page. But look at this case: the blue points form a cluster in the middle, surrounded by the others. Wherever you draw one straight line, it can't fence the cluster in.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/2xtp5j5uv5kuasmiqhzx.png" alt="Blue points sit in the middle, surrounded by others; no single straight line separates them" />
<em>The blue points sit in the middle. A single straight line can't separate them from the points around them.</em></p>
<h2>Add a hidden layer</h2>
<p>So add a layer of neurons between the inputs and the output. This is a hidden layer. Give it three neurons.</p>
<p>Each hidden neuron draws its own line, and each one asks a single question about any point: "Is this point on my side?" The answer is yes (1) or no (0).</p>
<h2>The output neuron asks for agreement</h2>
<p>The output neuron adds up the three answers. It only fires when the sum passes 2.5, so it needs all three to say yes.</p>
<p>The only place where all three lines agree is the region between them. Together, the three lines fence off a triangle.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/acz5byhr45h8hixf37uj.png" alt="Three hidden neurons draw three lines that fence off a triangle" />
<em>Three hidden neurons draw three lines. The output fires only where all three agree, which is a triangle.</em></p>
<h2>A worked example</h2>
<p>Take a point inside the triangle. All three neurons say yes, so the sum is 1 + 1 + 1 = 3. That passes 2.5, so the output fires.</p>
<p>Now take a point just past one of the lines. Only two neurons say yes, so the sum is 0 + 1 + 1 = 2. That's below 2.5, so the output stays off.</p>
<p>That threshold turns the output neuron into an "AND" of all the hidden neurons.</p>
<h2>More neurons, rounder shapes</h2>
<p>The triangle is a rough fit, and its corners catch a few outsiders. The fix is simple: use more hidden neurons.</p>
<ul>
<li>With six neurons, you get six lines and a tighter hexagon.</li>
<li>With twenty, the edge looks almost like a circle.</li>
</ul>
<p>The edge is never truly curved. It's made of many short straight pieces, like a polygon with lots of sides. Add enough of them, and the difference stops mattering.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/27b9v7yxqk8uhusss5tk.png" alt="With twenty hidden neurons, the boundary looks almost like a circle" />
<em>More hidden neurons means more straight edges. With twenty, the boundary looks almost like a circle.</em></p>
<h2>Go deeper</h2>
<p>A second hidden layer can combine the shapes from the first one. That lets a network fence off things a single shape can't, like two separate islands, or a ring with a hole in the middle.</p>
<p>This is why depth matters. Each layer builds on the shapes from the layer before it.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cjzd7tw2xdmu47u2ymxz.png" alt="A second hidden layer combines shapes into two islands or a ring with a hole" />
<em>A second hidden layer combines shapes: two islands, or a ring with a hole.</em></p>
<h2>Quick check</h2>
<p>A hidden layer has five neurons. Does the output fence off a pentagon, or a perfect circle?</p>
<p><strong>Answer: a pentagon.</strong> Five neurons draw five straight lines, so the edge has (at most) five sides. A perfect circle would need infinitely many.</p>
<h2>Key takeaways</h2>
<ul>
<li>One neuron draws one straight line.</li>
<li>A hidden layer draws many lines, and the output neuron fires only where they agree.</li>
<li>Three lines make a triangle, six a hexagon, and twenty something close to a circle.</li>
<li>More layers combine shapes into islands, rings, and other complex regions.</li>
<li>Hidden layers never bend a line. They join many straight ones into a curve.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[Why can't one neuron learn XOR?]]></title><description><![CDATA[The tiny logic puzzle that stalled neural networks, and the hidden layer that solved it]]></description><link>https://logitsolutionsai.hashnode.dev/why-can-t-one-neuron-learn-xor</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/why-can-t-one-neuron-learn-xor</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:47:46 GMT</pubDate><content:encoded><![CDATA[<p>XOR looks like the easiest problem in the world. It has two inputs, four cases, and a one-line rule. Yet a single artificial neuron cannot learn it, no matter how long you train it.</p>
<p>Understanding why takes one picture. And the fix explains why every modern neural network has hidden layers.</p>
<h2>What XOR does</h2>
<p>XOR stands for "exclusive or." It outputs 1 when exactly one of its two inputs is 1.</p>
<ul>
<li>0 and 0 give 0.</li>
<li>0 and 1 give 1.</li>
<li>1 and 0 give 1.</li>
<li>1 and 1 give 0.</li>
</ul>
<p>So it's "one or the other, but not both."</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/rkhipbl2ei6qrfyj3nrh.png" alt="The XOR truth table: the output is 1 only when the two inputs differ" />
<em>The XOR truth table. The output is 1 only when the two inputs differ.</em></p>
<h2>Put the four cases on a grid</h2>
<p>Now plot each case as a point, with the first input across and the second input up. Color the points by their answer: blue for 1, hollow for 0.</p>
<p>The two 1s sit on one diagonal. The two 0s sit on the other diagonal.</p>
<h2>One neuron, one straight line</h2>
<p>A single neuron (a perceptron) decides by drawing one straight line. Everything on one side gets a 1, and everything on the other side gets a 0.</p>
<p>Try to place that line for XOR:</p>
<ul>
<li>A vertical line catches one of the 1s, but also a 0.</li>
<li>Turn it into a diagonal, and it catches both 1s, but still grabs a 0.</li>
</ul>
<p>Wherever you put it, the line mixes the classes. No straight line can separate them. Problems where one line does work are called "linearly separable." XOR is not.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/l3vx28t92xvwbrmwd47t.png" alt="However you draw a single straight line, it puts a 0 on the same side as the 1s" />
<em>However you draw a single straight line, it puts a 0 on the same side as the 1s.</em></p>
<h2>The book that cooled the field</h2>
<p>In 1969, Marvin Minsky and Seymour Papert proved this limit in their book <em>Perceptrons</em>. They showed that single-layer perceptrons can't compute functions like XOR.</p>
<p>The result helped cool interest in neural networks for years. Research slowed until people had practical ways to train networks with more than one layer.</p>
<h2>The fix: a hidden layer</h2>
<p>The answer is to add a layer of neurons between the inputs and the output. It's called a hidden layer because you never see its values directly.</p>
<p>For XOR, two hidden neurons are enough:</p>
<ol>
<li>The first neuron asks: is at least one input on? That's OR.</li>
<li>The second neuron asks: are they not both on? That's NAND ("not and").</li>
<li>The output neuron fires only when both answers are yes. That's AND.</li>
</ol>
<p>Check it: for inputs 1 and 0, OR says yes and NAND says yes, so the output is 1. For 1 and 1, OR says yes but NAND says no, so the output is 0.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5goctu13ch5ckxfaol9o.png" alt="A hidden layer solves XOR: OR and NAND feed an AND output" />
<em>A hidden layer solves XOR: OR and NAND feed an AND output.</em></p>
<h2>Two lines make a band</h2>
<p>Here's what that looks like on the grid. Each hidden neuron draws its own straight line. Together, the two lines fence off a diagonal band, and both 1s land inside it.</p>
<p>One neuron gives you one line. A layer of neurons gives you many lines, and the next neuron can combine them into shapes. That's the core reason deep networks have layers.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/xr3u3nklk4m06e71z79i.png" alt="The OR line and the NAND line fence off a band that holds both 1s and neither 0" />
<em>The OR line and the NAND line fence off a band that holds both 1s and neither 0.</em></p>
<h2>Quick check</h2>
<p>One neuron draws one straight line. Which can it learn: the AND gate, or the XOR gate?</p>
<p><strong>Answer: AND.</strong> AND outputs 1 only for the input (1, 1). That single point sits in a corner, so one straight line can cut it off. XOR needs two.</p>
<h2>Key takeaways</h2>
<ul>
<li>XOR outputs 1 when exactly one input is 1.</li>
<li>A single neuron draws one straight line, and no straight line separates XOR's 1s from its 0s.</li>
<li>Minsky and Papert proved this limit in 1969, which slowed neural network research for years.</li>
<li>A hidden layer fixes it: OR and NAND draw two lines, and AND combines them into a band.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item><item><title><![CDATA[The perceptron: the 1958 machine that learned from its mistakes]]></title><description><![CDATA[How one artificial neuron makes a decision, and how it fixes itself when it's wrong]]></description><link>https://logitsolutionsai.hashnode.dev/the-perceptron-the-1958-machine-that-learned-from-its-mistakes</link><guid isPermaLink="true">https://logitsolutionsai.hashnode.dev/the-perceptron-the-1958-machine-that-learned-from-its-mistakes</guid><dc:creator><![CDATA[Logit Solutions AI]]></dc:creator><pubDate>Sat, 31 Oct 2026 19:45:29 GMT</pubDate><content:encoded><![CDATA[<p>In 1958, psychologist Frank Rosenblatt unveiled the perceptron at the Cornell Aeronautical Laboratory. The press got excited. The New York Times wrote that the Navy expected it to one day walk, talk, see, and write.</p>
<p>It couldn't do any of that. But it introduced an idea that every modern neural network still uses: a machine that improves by fixing its own mistakes.</p>
<h2>How a perceptron makes a decision</h2>
<p>Under the hood, a perceptron is a single artificial neuron. It does three things:</p>
<ol>
<li>It takes some inputs, like x₁ and x₂.</li>
<li>It multiplies each input by a weight (w₁ and w₂) and adds them up.</li>
<li>It compares the sum with a threshold, written θ (theta).</li>
</ol>
<p>If the sum clears the threshold, the perceptron outputs 1. Otherwise, it outputs 0. That's the whole decision: yes or no.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/bhq0y4fzao7h5w3cqjt4.png" alt="A perceptron: weighted inputs are summed, then a threshold step outputs 1 or 0" />
<em>A perceptron: weighted inputs are summed, then a threshold step turns the sum into a 1 or a 0.</em></p>
<h2>The rule draws a straight line</h2>
<p>Here's the useful way to picture it. With two inputs, every example is a point on a flat plane, at position (x₁, x₂).</p>
<p>The boundary where the sum exactly equals the threshold, <code>w₁x₁ + w₂x₂ = θ</code>, is a straight line. Points on one side get a 1. Points on the other side get a 0.</p>
<p>So a perceptron is a line-drawer. Its weights decide where the line sits and which way it tilts.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/41ybc9iv6mvmzfxj49yb.png" alt="A straight line splits the plane: one side outputs 1, the other outputs 0" />
<em>With two inputs, the perceptron's rule splits the plane with a straight line: one side outputs 1, the other outputs 0.</em></p>
<h2>Learning from mistakes</h2>
<p>A new perceptron starts with weights that put its line in the wrong place. So how does it get better?</p>
<ol>
<li>It looks at one example at a time and makes a guess.</li>
<li>If the guess is right, nothing changes.</li>
<li>If the guess is wrong, it nudges each weight by the input times the error.</li>
</ol>
<p>As a formula: <code>w ← w + x · error</code>, where the error is the target minus the output. In practice, the nudge is often scaled down by a small number called the learning rate, and the threshold is adjusted the same way.</p>
<h2>A worked example</h2>
<p>Say the perceptron outputs 0 for a point, but the right answer was 1.</p>
<ul>
<li>The error is 1 − 0 = +1.</li>
<li>So each weight moves by its own input. Weights for strong inputs move the most.</li>
<li>The line swings toward that point, until the point ends up on the correct side.</li>
</ul>
<p>If it had output 1 when the answer was 0, the error would be −1, and the weights would move the other way.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/d8z6o3e2omvo0l3vct4s.png" alt="Target 1, output 0: the error is +1, so the weights grow and the line swings toward the missed point" />
<em>Target 1, output 0: the error is +1, so the weights grow and the line swings toward the missed point.</em></p>
<h2>When it works, and when it doesn't</h2>
<p>Repeat this, example after example, and the line keeps moving until every point lands on the right side.</p>
<p>Rosenblatt proved something remarkable: if one straight line can separate the two classes, this rule always finds such a line, in a finite number of steps. This is known as the perceptron convergence theorem.</p>
<p>The catch is in that "if." Some problems have no single separating line at all. The classic example is XOR, and solving it takes more than one neuron.</p>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/i9yjnslo3d6zsx9d12fi.png" alt="The line settles where it separates every point" />
<em>Example after example, the line settles where it separates every point. This is guaranteed whenever a straight line can do it.</em></p>
<h2>Quick check</h2>
<p>The perceptron gets a point right. Do its weights change, or stay the same?</p>
<p><strong>Answer: they stay the same.</strong> When the output is correct, the error is zero, and input times zero changes nothing. A perceptron only learns from its mistakes.</p>
<h2>Key takeaways</h2>
<ul>
<li>A perceptron is one neuron: a weighted sum, then a threshold that outputs 1 or 0.</li>
<li>With two inputs, its decision boundary is a straight line.</li>
<li>It learns by nudging each weight by the input times the error, and only when it's wrong.</li>
<li>If a straight line can separate the classes, it is guaranteed to find one.</li>
</ul>
<hr />
<p><em>This article is part of Deep Learning, Visually by Logit Solutions AI: short animated explainers that cover one idea at a time, each with a written version like this one.</em></p>
]]></content:encoded></item></channel></rss>