<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Jien Weng</title>
    <subtitle>Research notes, essays, publications, and working papers by Jien Weng.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://jienweng.github.io/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://jienweng.github.io"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-01T00:00:00+00:00</updated>
    <id>https://jienweng.github.io/atom.xml</id>
    <entry xml:lang="en">
        <title>The Fokker-Planck Equation Explained, from an SDE to the Density it Evolves</title>
        <published>2026-07-01T00:00:00+00:00</published>
        <updated>2026-07-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/fokker-planck-sde/"/>
        <id>https://jienweng.github.io/notes/fokker-planck-sde/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/fokker-planck-sde/">&lt;p&gt;Most explanations of the Fokker-Planck equation start too late. They write down a partial differential equation for a density, call one term &quot;drift&quot; and the other &quot;diffusion&quot;, and move on. That is correct, but it hides where the equation comes from.&lt;&#x2F;p&gt;
&lt;p&gt;The cleaner way to think about it is to start one step earlier, with a single stochastic trajectory. A stochastic differential equation describes &lt;em&gt;one&lt;&#x2F;em&gt; random path. But one path tells you almost nothing. What we usually want is the distribution: given where the system started, where is it likely to be at time $t$? The Fokker-Planck equation is the deterministic law that this distribution obeys.&lt;&#x2F;p&gt;
&lt;p&gt;So the real chain is:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{SDE} \to \text{ensemble of paths} \to \text{density } p(x,t) \to \text{Fokker-Planck PDE} \to \text{stationary law}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Once that chain is visible, the equation stops looking like a definition to memorize. It becomes the obvious bookkeeping for how probability mass flows: drift transports it, diffusion spreads it.&lt;&#x2F;p&gt;
&lt;p&gt;This note walks the full path. We start from a concrete SDE and simulate it, watch a cloud of paths turn into a density, derive the Fokker-Planck equation from Ito&#x27;s lemma, and then solve it from scratch in Python. We finish with an Ornstein-Uhlenbeck example whose answer we know in closed form, so we can check three independent routes against each other.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Run it yourself.&lt;&#x2F;strong&gt; Every code block below is collected in a runnable Jupyter notebook: &lt;a href=&quot;&#x2F;notebooks&#x2F;fokker-planck-sde.ipynb&quot;&gt;&lt;strong&gt;download &lt;code&gt;fokker-planck-sde.ipynb&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;. It only needs &lt;code&gt;numpy&lt;&#x2F;code&gt; and &lt;code&gt;matplotlib&lt;&#x2F;code&gt; (&lt;code&gt;pip install numpy matplotlib&lt;&#x2F;code&gt;), and runs top to bottom.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;start-with-a-single-trajectory&quot;&gt;Start with a Single Trajectory&lt;&#x2F;h2&gt;
&lt;p&gt;A stochastic differential equation in one dimension has the form&lt;&#x2F;p&gt;
&lt;p&gt;$$
dX_t = \mu(X_t, t), dt + \sigma(X_t, t), dW_t,
$$&lt;&#x2F;p&gt;
&lt;p&gt;where $W_t$ is a Brownian motion. Read it as a rule for taking a small step: over an interval $dt$, the state moves by a deterministic amount $\mu, dt$ (the &lt;strong&gt;drift&lt;&#x2F;strong&gt;) plus a random kick $\sigma, dW_t$ (the &lt;strong&gt;diffusion&lt;&#x2F;strong&gt;), where $dW_t$ is a Gaussian increment with mean zero and variance $dt$.&lt;&#x2F;p&gt;
&lt;p&gt;The running example for this note is the &lt;strong&gt;Ornstein-Uhlenbeck&lt;&#x2F;strong&gt; (OU) process:&lt;&#x2F;p&gt;
&lt;p&gt;$$
dX_t = -\theta X_t, dt + \sigma, dW_t.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The drift $-\theta X_t$ pulls the state back toward zero, more strongly the further out it is. The diffusion $\sigma, dW_t$ keeps kicking it away. This tug-of-war between mean reversion and noise is exactly the structure that shows up in interest-rate models, in the velocity of a particle under friction, and in many stationary time series. We will use $\theta = 1$, $\sigma = 1$, and start every path at $X_0 = 2$.&lt;&#x2F;p&gt;
&lt;p&gt;The simplest way to simulate an SDE is the &lt;strong&gt;Euler-Maruyama&lt;&#x2F;strong&gt; scheme. Discretize time into steps of size $\Delta t$ and replace the differentials by increments:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_{k+1} = X_k + \mu(X_k, t_k), \Delta t + \sigma(X_k, t_k), \sqrt{\Delta t}; Z_k,
\qquad Z_k \sim \mathcal{N}(0, 1).
$$&lt;&#x2F;p&gt;
&lt;p&gt;The only subtlety is that the random increment scales as $\sqrt{\Delta t}$, not $\Delta t$. That is the signature of Brownian motion: over a window of length $\Delta t$ the displacement has standard deviation $\sqrt{\Delta t}$, so variance grows linearly in time.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; euler_maruyama&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;mu&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; sigma&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; x0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; T&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; dt&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; n_paths&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; rng&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    n_steps&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;T&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; dt&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    X&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;full&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_paths&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; float&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    traj&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;empty&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_steps&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; n_paths&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    traj&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_steps&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        t&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; rng&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;standard_normal&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_paths&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        X&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; mu&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dt&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; sigma&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqrt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;dt&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; Z&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        traj&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; traj&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;theta&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; sigma_ou&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; x0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mu&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; lambda&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; t&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;theta&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sig&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; lambda&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; t&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; sigma_ou&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rng&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;random&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;default_rng&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;traj&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; euler_maruyama&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;mu&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; sig&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; x0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; T&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;4.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; dt&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1e-3&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; n_paths&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;20000&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; rng&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;rng&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A handful of these paths, plotted over time, all start at $2$ and get dragged toward zero while jittering:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fokker-planck-sde&amp;#x2F;ou_paths.png&quot; alt=&quot;Several simulated Ornstein-Uhlenbeck sample paths starting at x=2 and relaxing toward zero&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Twelve OU sample paths. Each starts at X_0 = 2, is pulled toward zero by the drift, and jitters under the diffusion. No single path is predictable; the ensemble is.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;No individual path is predictable. But look at all $20{,}000$ of them at once and a pattern appears.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-one-path-to-a-density&quot;&gt;From One Path to a Density&lt;&#x2F;h2&gt;
&lt;p&gt;Fix a time $t$ and forget the ordering of the paths. What we have is a cloud of values $X_t^{(1)}, X_t^{(2)}, \ldots$, one per path. Their histogram approximates a probability density $p(x, t)$: the probability of finding the process near $x$ at time $t$.&lt;&#x2F;p&gt;
&lt;p&gt;At $t = 0$ that density is a spike at $x = 2$, because every path starts there. As time runs, two things happen at once. The whole cloud drifts left toward zero, following the mean reversion. And it spreads out, because the noise accumulates. Eventually the inward pull and the outward spreading balance, and the density stops changing. That limiting shape is the &lt;strong&gt;stationary distribution&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fokker-planck-sde&amp;#x2F;density_evolution.png&quot; alt=&quot;Histogram of the OU ensemble at several times, shifting from a spike at x=2 toward a fixed bell curve centered at zero&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The ensemble density at four times. It starts concentrated at X_0 = 2, then drifts toward zero and broadens, settling into a fixed bell curve. The Fokker-Planck equation is the law governing this whole movie.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;The question this note answers is: &lt;em&gt;can we describe the movie of $p(x, t)$ directly, without simulating a single path?&lt;&#x2F;em&gt; Yes. The density obeys a deterministic PDE, and that PDE is the Fokker-Planck equation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deriving-the-fokker-planck-equation-from-ito-s-lemma&quot;&gt;Deriving the Fokker-Planck Equation from Ito&#x27;s Lemma&lt;&#x2F;h2&gt;
&lt;p&gt;The cleanest derivation uses a &lt;strong&gt;test function&lt;&#x2F;strong&gt;. Let $f$ be any smooth function that vanishes outside a bounded region. We track how the expectation $\mathbb{E}[f(X_t)]$ evolves, in two different ways, and match them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Route one: apply Ito&#x27;s lemma.&lt;&#x2F;strong&gt; For $dX_t = \mu, dt + \sigma, dW_t$, Ito&#x27;s lemma says that the differential of $f(X_t)$ carries an extra second-order term that ordinary calculus would miss:&lt;&#x2F;p&gt;
&lt;p&gt;$$
df(X_t) = f&#x27;(X_t), dX_t + \tfrac{1}{2} f&#x27;&#x27;(X_t), (dX_t)^2.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Using $(dX_t)^2 = \sigma^2, dt$ (the defining rule of Ito calculus, since $(dW_t)^2 = dt$),&lt;&#x2F;p&gt;
&lt;p&gt;$$
df(X_t) = \left[\mu, f&#x27;(X_t) + \tfrac{1}{2}\sigma^2 f&#x27;&#x27;(X_t)\right] dt + \sigma f&#x27;(X_t), dW_t.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Take expectations. The $dW_t$ term has mean zero, so it drops out:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\frac{d}{dt}, \mathbb{E}[f(X_t)] = \mathbb{E}!\left[\mu(X_t), f&#x27;(X_t) + \tfrac{1}{2}\sigma^2(X_t), f&#x27;&#x27;(X_t)\right]
= \int \left[\mu(x) f&#x27;(x) + \tfrac{1}{2}\sigma^2(x) f&#x27;&#x27;(x)\right] p(x, t), dx.
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Route two: differentiate under the integral.&lt;&#x2F;strong&gt; By definition $\mathbb{E}[f(X_t)] = \int f(x), p(x, t), dx$, so&lt;&#x2F;p&gt;
&lt;p&gt;$$
\frac{d}{dt}, \mathbb{E}[f(X_t)] = \int f(x), \frac{\partial p}{\partial t}, dx.
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Match them.&lt;&#x2F;strong&gt; Set the two expressions equal and move all derivatives off $f$ using integration by parts. Because $f$ vanishes at the boundaries, every boundary term is zero:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\int \mu f&#x27; p, dx = -\int f, \frac{\partial}{\partial x}(\mu p), dx,
\qquad
\int \tfrac{1}{2}\sigma^2 f&#x27;&#x27; p, dx = \int f, \frac{\partial^2}{\partial x^2}!\left(\tfrac{1}{2}\sigma^2 p\right) dx.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Substituting back, we get an identity that must hold for &lt;em&gt;every&lt;&#x2F;em&gt; test function $f$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\int f(x), \frac{\partial p}{\partial t}, dx
= \int f(x)\left[-\frac{\partial}{\partial x}(\mu p) + \frac{\partial^2}{\partial x^2}!\left(\tfrac{1}{2}\sigma^2 p\right)\right] dx.
$$&lt;&#x2F;p&gt;
&lt;p&gt;If two integrals agree against all test functions, their integrands agree. This gives the &lt;strong&gt;Fokker-Planck equation&lt;&#x2F;strong&gt; (also called the forward Kolmogorov equation):&lt;&#x2F;p&gt;
&lt;p&gt;$$
\boxed{;\frac{\partial p}{\partial t} = -\frac{\partial}{\partial x}\big[\mu(x, t), p\big] + \frac{\partial^2}{\partial x^2}!\left[\tfrac{1}{2}\sigma^2(x, t), p\right].;}
$$&lt;&#x2F;p&gt;
&lt;p&gt;The derivation is worth pausing on. The first term came from the drift $\mu$ and is first order; the second came from the diffusion $\sigma$ and is second order, and it is there only because of the extra Ito term $\tfrac12 f&#x27;&#x27;(dX)^2$. Without Ito calculus, the diffusion would vanish and the density would never spread. The whole &quot;randomness&quot; of the process lives in that one second-order term.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-the-equation-actually-says-transport-plus-spreading&quot;&gt;What the Equation Actually Says: Transport Plus Spreading&lt;&#x2F;h2&gt;
&lt;p&gt;The Fokker-Planck equation is easier to read as a &lt;strong&gt;conservation law&lt;&#x2F;strong&gt;. Write it as&lt;&#x2F;p&gt;
&lt;p&gt;$$
\frac{\partial p}{\partial t} = -\frac{\partial J}{\partial x},
\qquad
J(x, t) = \mu(x, t), p - \frac{\partial}{\partial x}!\left[\tfrac{1}{2}\sigma^2(x, t), p\right].
$$&lt;&#x2F;p&gt;
&lt;p&gt;Here $J$ is the &lt;strong&gt;probability current&lt;&#x2F;strong&gt;: the rate at which probability mass flows past the point $x$. The equation $\partial_t p = -\partial_x J$ is just the statement that probability is conserved. If more flows in than out at a point, the density there rises.&lt;&#x2F;p&gt;
&lt;p&gt;The current has two pieces, and they correspond exactly to the two forces in the SDE.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transport (drift).&lt;&#x2F;strong&gt; The term $\mu(x), p$ carries mass in the direction of the drift, like a wind blowing the density downstream. For the OU process $\mu = -\theta x$ blows everything toward the origin.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Spreading (diffusion).&lt;&#x2F;strong&gt; The term $-\partial_x[\tfrac12\sigma^2 p]$ pushes mass from high-density regions to low-density ones, exactly like heat flowing from hot to cold. It is what turns a spike into a bell curve.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A &lt;strong&gt;stationary distribution&lt;&#x2F;strong&gt; $p_\infty$ is one where the movie stops: $\partial_t p = 0$, which means the current $J$ is constant. On the whole line, with density decaying to zero at infinity, that constant must be zero. Setting $J = 0$ for the OU process,&lt;&#x2F;p&gt;
&lt;p&gt;$$
-\theta x, p_\infty - \frac{\sigma^2}{2}, p_\infty&#x27; = 0
\quad\Longrightarrow\quad
\frac{p_\infty&#x27;}{p_\infty} = -\frac{2\theta}{\sigma^2}, x
\quad\Longrightarrow\quad
p_\infty(x) \propto \exp!\left(-\frac{\theta x^2}{\sigma^2}\right).
$$&lt;&#x2F;p&gt;
&lt;p&gt;That is a Gaussian with mean $0$ and variance $\sigma^2 &#x2F; (2\theta)$. Drift and diffusion reach a truce, and the truce is a bell curve. With our numbers $\theta = \sigma = 1$, the stationary law is $\mathcal{N}(0, \tfrac12)$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-ornstein-uhlenbeck-answer-in-closed-form&quot;&gt;The Ornstein-Uhlenbeck Answer in Closed Form&lt;&#x2F;h2&gt;
&lt;p&gt;The OU process is special: we can solve it exactly, which makes it the perfect test case. Because the drift is linear and the noise is additive, the transition density stays Gaussian for all time. Starting from a fixed $X_0 = x_0$, the process at time $t$ is normally distributed:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_t \sim \mathcal{N}\big(m(t),, v(t)\big),
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
m(t) = x_0, e^{-\theta t},
\qquad
v(t) = \frac{\sigma^2}{2\theta}\left(1 - e^{-2\theta t}\right).
$$&lt;&#x2F;p&gt;
&lt;p&gt;The mean decays exponentially toward zero at rate $\theta$. The variance grows from $0$ up to the stationary value $\sigma^2 &#x2F; (2\theta)$. Both limits match the picture: the cloud drifts to the origin and its width saturates.&lt;&#x2F;p&gt;
&lt;p&gt;You can verify these solve the Fokker-Planck equation by substitution, but it is more convincing to check them numerically, which is what the rest of the note does.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hand-calculation-with-real-numbers&quot;&gt;Hand Calculation with Real Numbers&lt;&#x2F;h2&gt;
&lt;p&gt;Before writing any solver, pin down what the answer &lt;em&gt;should&lt;&#x2F;em&gt; be at a specific time, so we have something concrete to test against. Take $t = 1$ with $\theta = \sigma = 1$ and $x_0 = 2$.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Mean.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;$$
m(1) = 2, e^{-1} = 2 \times 0.367879 = 0.735759.
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Variance.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;$$
v(1) = \frac{1}{2}\left(1 - e^{-2}\right) = \frac{1}{2}\left(1 - 0.135335\right) = \frac{1}{2}\times 0.864665 = 0.432332,
$$&lt;&#x2F;p&gt;
&lt;p&gt;so the standard deviation is $\sqrt{0.432332} = 0.657520$.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Stationary check.&lt;&#x2F;strong&gt; As $t \to \infty$, $m(t) \to 0$ and $v(t) \to \tfrac12$, matching the $\mathcal{N}(0, \tfrac12)$ we derived from setting the current to zero. Its standard deviation is $\sqrt{0.5} = 0.707107$.&lt;&#x2F;p&gt;
&lt;p&gt;So at $t = 1$ the density should be a Gaussian centered at about $0.736$ with standard deviation about $0.658$, and by $t = 4$ it should be almost exactly $\mathcal{N}(0, \tfrac12)$. Keep these three numbers in mind; every method below has to reproduce them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;solving-the-fokker-planck-equation-from-scratch&quot;&gt;Solving the Fokker-Planck Equation from Scratch&lt;&#x2F;h2&gt;
&lt;p&gt;We now solve the PDE numerically, without simulating any paths. Discretize $x$ on a grid and integrate the conservation law forward in time. The safe way to do this is to keep the &lt;strong&gt;flux form&lt;&#x2F;strong&gt;, so that the scheme conserves total probability by construction: compute the current $J$ on the cell faces (the midpoints between grid points), then update each cell by the difference of the fluxes on its two faces.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; solve_fokker_planck&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;mu&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; D&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; p0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; T&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; dt&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Explicit finite-volume solver for  dp&#x2F;dt = -d&#x2F;dx(mu p) + d^2&#x2F;dx^2(D p).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    mu(x): drift, D: constant diffusion sigma^2&#x2F;2, x: grid, p0: initial density.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    dx&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    xf&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;          #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; cell faces (midpoints)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    muf&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mu&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;xf&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; p0&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;copy&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    n_steps&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;T&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; dt&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_steps&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;        #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; probability current on the faces: transport minus diffusion&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        J&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; muf&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;p&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; D&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;p&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; dx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        dpdt&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;zeros_like&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;p&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        dpdt&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;J&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; J&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; dx&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;   #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; divergence of the current&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; dt&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dpdt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        p&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;                     #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; absorbing far-field boundary&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; grid and a narrow-Gaussian approximation to the initial spike at x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;linspace&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 801&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dx&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;p0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; x0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; **&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.01&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqrt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.01&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; sigma_ou&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; **&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dt_pde&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dx&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; **&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; D&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;                 #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; diffusion stability limit (CFL)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;p_pde&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; solve_fokker_planck&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;lambda&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; z&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;theta&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; D&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; p0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; T&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; dt&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;dt_pde&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two numerical points matter. First, the time step is bounded by a &lt;strong&gt;stability condition&lt;&#x2F;strong&gt;: for an explicit diffusion scheme, $\Delta t \lesssim \Delta x^2 &#x2F; \sigma^2$. Push past it and the solution blows up into oscillations. Second, the initial spike at $x_0$ is approximated by a narrow Gaussian, because a true delta on a grid is a single spike that the scheme struggles with; the diffusion smooths this out almost immediately, so it does not affect the answer at $t = 1$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;three-routes-one-answer&quot;&gt;Three Routes, One Answer&lt;&#x2F;h2&gt;
&lt;p&gt;Now the payoff. We have three completely independent ways to get the density at $t = 1$:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Monte Carlo.&lt;&#x2F;strong&gt; Simulate many SDE paths with Euler-Maruyama and histogram their values at $t = 1$.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PDE.&lt;&#x2F;strong&gt; Solve the Fokker-Planck equation on a grid and read off $p(x, 1)$.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Closed form.&lt;&#x2F;strong&gt; Evaluate the exact Gaussian $\mathcal{N}(m(1), v(1))$.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; math&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; erf&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; only for the analytic pdf below&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 1. Monte Carlo: reuse the trajectories, take the slice at t = 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;t_idx&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1e-3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mc_samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; traj&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;t_idx&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 3. Closed-form transition density at t = 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;theta&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;v1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;sigma_ou&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; **&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; theta&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; theta&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;analytic&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; m1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; **&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; v1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqrt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; v1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;analytic   mean=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;m1&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.6f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  std=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqrt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;v1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.6f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;monte carlo mean=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;mc_samples&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mean&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.6f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  std=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;mc_samples&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;std&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.6f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; PDE mean&#x2F;std from the grid density p_pde&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;pde grid   mean=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; p_pde&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dx&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.6f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;      f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;std=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqrt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; p_pde&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dx&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; **&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; p_pde&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dx&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.6f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All three report the same numbers we computed by hand, up to discretization error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;analytic    mean=0.735759  std=0.657520&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;monte carlo mean=0.738001  std=0.656903&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pde grid    mean=0.735715  std=0.658482&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Overlaid on the same axes, the Monte Carlo histogram, the PDE solution, and the analytic Gaussian sit on top of each other:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fokker-planck-sde&amp;#x2F;three_way_overlay.png&quot; alt=&quot;At t=1, the Monte Carlo histogram, the finite-volume PDE solution, and the exact Gaussian all coincide&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The density at t = 1, computed three independent ways. The Monte Carlo histogram (bars), the Fokker-Planck PDE solution (solid), and the closed-form Gaussian (dashed) agree because they describe the same object from different directions.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;This is the moment where the theory becomes tangible. The Monte Carlo route never writes down a PDE; it just pushes paths forward and counts. The PDE route never simulates a path; it evolves a density on a grid. The closed form does neither; it solves the equation exactly. Three different arithmetic routes, one distribution, because the Fokker-Planck equation is precisely the deterministic law that the random ensemble obeys.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-stationary-distribution&quot;&gt;The Stationary Distribution&lt;&#x2F;h2&gt;
&lt;p&gt;Let the same three methods run out to $t = 4$ and the density settles onto the stationary law $\mathcal{N}(0, \tfrac12)$ we derived by setting the probability current to zero:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fokker-planck-sde&amp;#x2F;stationary_distribution.png&quot; alt=&quot;At t=4 the density matches the stationary Gaussian N(0, 1&amp;#x2F;2)&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;By t = 4 the ensemble has forgotten where it started. The long-time density (bars and solid) matches the stationary Gaussian N(0, 1&amp;#x2F;2) (dashed), the balance point where drift and diffusion cancel.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;The process has forgotten its initial condition. Whether it started at $x_0 = 2$ or anywhere else, it relaxes to the same bell curve. That loss of memory, at rate $\theta$, is the defining property of a mean-reverting process, and the Fokker-Planck equation makes it quantitative.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;practical-points-worth-making-explicit&quot;&gt;Practical Points Worth Making Explicit&lt;&#x2F;h2&gt;
&lt;p&gt;A few things trip people up when they take this beyond the OU example.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Stability of the explicit scheme.&lt;&#x2F;strong&gt; The time step must respect $\Delta t \lesssim \Delta x^2 &#x2F; \sigma^2$. Halving the grid spacing quarters the allowed step. When this becomes expensive, implicit schemes (Crank-Nicolson) remove the restriction at the cost of solving a linear system each step.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Boundary conditions carry modelling meaning.&lt;&#x2F;strong&gt; We used an absorbing far-field boundary ($p = 0$), which is fine when the density decays anyway. But boundaries are not just numerics: a &lt;strong&gt;reflecting&lt;&#x2F;strong&gt; boundary (zero current, $J = 0$) models a wall the process cannot cross, while an &lt;strong&gt;absorbing&lt;&#x2F;strong&gt; boundary models a level at which the process is killed. In finance the absorbing case is exactly a barrier option or a default threshold.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;State-dependent diffusion.&lt;&#x2F;strong&gt; When $\sigma$ depends on $x$, the second term is $\partial_x^2[\tfrac12 \sigma^2(x), p]$, and the $\sigma^2$ stays &lt;em&gt;inside&lt;&#x2F;em&gt; both derivatives. Geometric Brownian motion, $dX = \mu X, dt + \sigma X, dW$, is the standard example and underlies Black-Scholes. Getting that placement wrong is the most common error in writing down a Fokker-Planck equation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Higher dimensions.&lt;&#x2F;strong&gt; In $d$ dimensions the drift becomes a vector and the diffusion a matrix $D = \tfrac12 \sigma \sigma^\top$, and the equation reads $\partial_t p = -\nabla!\cdot(\boldsymbol\mu p) + \nabla!\cdot(\nabla!\cdot(D, p))$. The grid solver stops scaling, and Monte Carlo becomes the practical method. That crossover is why simulation dominates high-dimensional problems.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-big-picture&quot;&gt;The Big Picture&lt;&#x2F;h2&gt;
&lt;p&gt;At this point the pieces should feel like one continuous argument rather than a definition dropped from the sky.&lt;&#x2F;p&gt;
&lt;p&gt;We started with an SDE, a rule for one random trajectory, and simulated it with Euler-Maruyama. One path is unpredictable, but the ensemble of paths forms a density $p(x, t)$. Applying Ito&#x27;s lemma to a test function and integrating by parts turned the randomness of the SDE into a deterministic PDE for that density, the Fokker-Planck equation:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\frac{\partial p}{\partial t} = -\frac{\partial}{\partial x}\big[\mu, p\big] + \frac{\partial^2}{\partial x^2}!\left[\tfrac{1}{2}\sigma^2, p\right].
$$&lt;&#x2F;p&gt;
&lt;p&gt;Reading it as a conservation law $\partial_t p = -\partial_x J$ exposed its two forces: drift transports probability, diffusion spreads it. Setting the current to zero gave the stationary distribution. And an Ornstein-Uhlenbeck example, solvable in closed form, let us check three independent routes, Monte Carlo, a from-scratch PDE solver, and the exact Gaussian, against the same hand-computed numbers.&lt;&#x2F;p&gt;
&lt;p&gt;So the SDE and the Fokker-Planck equation are two views of one process. The SDE is the microscopic rule for a single sample; the Fokker-Planck equation is the macroscopic law for the whole probability cloud. This is the same duality that runs through the rest of the stochastic toolkit: the &lt;em&gt;forward&lt;&#x2F;em&gt; Kolmogorov equation here evolves densities in time, while the &lt;em&gt;backward&lt;&#x2F;em&gt; Kolmogorov equation evolves expectations and is the object behind Feynman-Kac and option pricing.&lt;&#x2F;p&gt;
&lt;p&gt;I think this topic reads better when the SDE is taken as the starting point and the PDE is derived as its consequence, rather than the other way around. The mathematics carries the weight, the OU example makes the abstraction concrete, and the three-way agreement closes the loop.&lt;&#x2F;p&gt;
&lt;p&gt;If you think there is a better example, a cleaner derivation, or a natural extension, feel free to suggest it. From here the obvious next steps are the backward Kolmogorov equation and Feynman-Kac, Langevin dynamics as a sampler, and the jump from these scalar equations to their high-dimensional, matrix-diffusion form.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>An Introduction to MLOps: A Complete Tutorial with a Hands-On</title>
        <published>2026-06-29T00:00:00+00:00</published>
        <updated>2026-06-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/intro-to-mlops-tutorial/"/>
        <id>https://jienweng.github.io/notes/intro-to-mlops-tutorial/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/intro-to-mlops-tutorial/">&lt;p&gt;This is the written companion to the &lt;a href=&quot;&#x2F;slides&#x2F;2026&#x2F;intro-to-mlops&#x2F;&quot;&gt;&lt;strong&gt;Introduction to MLOps&lt;&#x2F;strong&gt; slides&lt;&#x2F;a&gt;. It follows the deck section by section: the same chain of ideas, the same figures, and then the hands-on the deck builds toward. The slides give the picture in a room; this note lets you read it at your own pace and, more importantly, &lt;strong&gt;run&lt;&#x2F;strong&gt; it. By the end you will have tracked a real machine-learning experiment, compared two runs, and inspected them in a web UI.&lt;&#x2F;p&gt;
&lt;p&gt;The chain the deck walks, and so will we:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;reproducibility → the software cycle → DevOps → the ML lifecycle → MLOps → MLflow → a hands-on.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In a hurry?&lt;&#x2F;strong&gt; Skip straight to the &lt;a href=&quot;https:&#x2F;&#x2F;jienweng.github.io&#x2F;notes&#x2F;intro-to-mlops-tutorial&#x2F;#hands-on-track-your-first-experiment&quot;&gt;&lt;strong&gt;hands-on&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;, or just &lt;code&gt;git clone&lt;&#x2F;code&gt; the companion repo, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JienWeng&#x2F;mlops-tutorial&quot;&gt;&lt;strong&gt;github.com&#x2F;JienWeng&#x2F;mlops-tutorial&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;, and run it. The sections above the hands-on are the &lt;em&gt;why&lt;&#x2F;em&gt;; the hands-on is the &lt;em&gt;how&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;why-reproducibility-and-why-ml-makes-it-hard&quot;&gt;Why reproducibility, and why ML makes it hard&lt;&#x2F;h2&gt;
&lt;p&gt;The starting motivation is research integrity. A result you cannot reproduce is hard to trust, hard to build on, and hard to defend. In software-heavy science this is why people publish code alongside papers (the &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;paperswithcode.com&quot;&gt;Papers with Code&lt;&#x2F;a&gt; culture). But &quot;publish the code&quot; is not enough on its own.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;paper-with-code.webp&quot; alt=&quot;Papers with Code listing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Sharing code with papers is a start. Credit: paperswithcode.com&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Reproducibility really means being able to recreate a result from &lt;strong&gt;all&lt;&#x2F;strong&gt; of its ingredients:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;code&lt;&#x2F;strong&gt; (which commit?),&lt;&#x2F;li&gt;
&lt;li&gt;the &lt;strong&gt;data&lt;&#x2F;strong&gt; (which version, which split?),&lt;&#x2F;li&gt;
&lt;li&gt;the &lt;strong&gt;environment&lt;&#x2F;strong&gt; (which library versions?),&lt;&#x2F;li&gt;
&lt;li&gt;and the &lt;strong&gt;parameters&lt;&#x2F;strong&gt; that produced the run.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlops-trojan.webp&quot; alt=&quot;What reproducibility implies&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;What reproducibility really implies: far more than just the code.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Machine learning makes this harder than ordinary software because &lt;strong&gt;three things change over time&lt;&#x2F;strong&gt;, not one:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Artifact&lt;&#x2F;th&gt;&lt;th&gt;Changes because…&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Code&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;the usual reasons: refactors, bug fixes, new features&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Data&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;new data arrives, distributions drift, labels get corrected&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Model&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;it is retrained, fine-tuned, or replaced&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Track only the code and you will still fail to reproduce a model, because the data and the trained model moved underneath you. MLOps is the practice of bringing software-engineering discipline (automation, reproducibility, monitoring) to all three. The one-line definition worth memorising:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;MLOps = DevOps principles applied to machine-learning systems.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;To get there, we first need the software-engineering half of that sentence.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-software-development-cycle&quot;&gt;The software development cycle&lt;&#x2F;h2&gt;
&lt;p&gt;Classic software development moves through repeating stages:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Development&lt;&#x2F;strong&gt;: planning and coding.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;&#x2F;strong&gt;: quick tests and a build.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Testing&lt;&#x2F;strong&gt;: deeper tests, validation, and a release.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Delivery&lt;&#x2F;strong&gt;: final packaging and deployment to a running server.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring&lt;&#x2F;strong&gt;: collecting data, watching each component, gathering user feedback.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Run those once, by hand, and you have traditional software delivery: slow, batched, and error-prone at the hand-offs.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;dev-cycle.webp&quot; alt=&quot;The software development cycle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The stages of the software development cycle.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;automating-it-devops-and-ci-cd&quot;&gt;Automating it: DevOps and CI&#x2F;CD&lt;&#x2F;h2&gt;
&lt;p&gt;DevOps is what you get when you &lt;strong&gt;automate that cycle&lt;&#x2F;strong&gt; so every change flows through it continuously instead of in big manual batches:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Continuous Integration (CI)&lt;&#x2F;strong&gt;: every commit is automatically built and tested.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Delivery (CD)&lt;&#x2F;strong&gt;: passing builds are automatically packaged and deployed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Monitoring&lt;&#x2F;strong&gt;: the running system is watched for errors and feedback, which flows back into planning.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;ci-cd-github.png&quot; alt=&quot;CI&amp;#x2F;CD pipeline&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;CI&amp;#x2F;CD automates the loop: every change is built, tested, and shipped continuously. Credit: resources.github.com&amp;#x2F;ci-cd&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;DevOps is usually described as resting on three legs, often drawn as a trident:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;People&lt;&#x2F;strong&gt;: a culture where development and operations share ownership.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Process&lt;&#x2F;strong&gt;: the automated CI&#x2F;CD pipeline itself.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;&#x2F;strong&gt;: the machinery that runs it (GitHub Actions, GitLab CI, containers, and so on).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The tools get the attention, but the culture and the process are what make the automation stick.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;devops-trident.webp&quot; alt=&quot;DevOps trident: people, process, tools&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;DevOps rests on three legs: people, process, and tools. Credit: aws.amazon.com&amp;#x2F;devops&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;the-machine-learning-lifecycle&quot;&gt;The machine-learning lifecycle&lt;&#x2F;h2&gt;
&lt;p&gt;Machine learning adds its own stages on top of the software cycle. A typical ML lifecycle runs:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Data extraction&lt;&#x2F;strong&gt;: fetch the data.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Data analysis&lt;&#x2F;strong&gt;: understand its nature and quirks.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Data preparation&lt;&#x2F;strong&gt;: clean it, engineer features, split into train&#x2F;validation&#x2F;test.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Model training&lt;&#x2F;strong&gt;: fit the model, tune hyperparameters.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Model evaluation&lt;&#x2F;strong&gt;: measure quality on held-out data.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Model validation&lt;&#x2F;strong&gt;: confirm it beats a baseline and is fit to deploy.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Model serving&lt;&#x2F;strong&gt;: package and deploy it to make predictions.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Model monitoring&lt;&#x2F;strong&gt;: watch performance and decide when to retrain.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlcycle.webp&quot; alt=&quot;The ML cycle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The machine-learning cycle. Credit: ml-ops.org&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlops-end-to-end.png&quot; alt=&quot;End-to-end ML lifecycle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The same lifecycle, end to end: data stages feed model stages, and monitoring loops back to the start.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;The loop at the end is the important part: monitoring feeds back into data and training. An ML system in production is a living thing, not a shipped artifact.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mlops-devops-for-ml&quot;&gt;MLOps = DevOps for ML&lt;&#x2F;h2&gt;
&lt;p&gt;Now overlay the DevOps loop onto the ML lifecycle. Each &quot;continuous&quot; practice gains an ML twist:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlops-org.png&quot; alt=&quot;MLOps as DevOps applied to ML&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;MLOps = DevOps principles applied to ML systems. Credit: ml-ops.org&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Continuous Integration&lt;&#x2F;strong&gt; is no longer only about testing code: it now also &lt;strong&gt;tests and validates data, schemas, and models&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Delivery&lt;&#x2F;strong&gt; ships not a single package but &lt;strong&gt;a whole pipeline&lt;&#x2F;strong&gt; that can deploy a model-serving service.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Training (CT)&lt;&#x2F;strong&gt; is unique to ML: the system can &lt;strong&gt;automatically retrain&lt;&#x2F;strong&gt; and redeploy as new data arrives.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Monitoring&lt;&#x2F;strong&gt; tracks &lt;strong&gt;model decay&lt;&#x2F;strong&gt; and can trigger retraining when quality drops.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;CT and model&#x2F;data monitoring are what make MLOps its own discipline rather than &quot;DevOps with notebooks&quot;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-an-mlops-workflow-looks-like-in-practice&quot;&gt;What an MLOps workflow looks like in practice&lt;&#x2F;h2&gt;
&lt;p&gt;Put concretely, a working MLOps setup wires together a handful of pieces:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;code versioning&lt;&#x2F;strong&gt; (git) and &lt;strong&gt;data versioning&lt;&#x2F;strong&gt; (DVC): the two moving inputs, tracked like any other asset;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;experimentation&lt;&#x2F;strong&gt; in notebooks (Jupyter), feeding an &lt;strong&gt;experiment-tracking&lt;&#x2F;strong&gt; server (MLflow) that records every run&#x27;s parameters and metrics;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;artifact tracking&lt;&#x2F;strong&gt; (also MLflow): every &lt;em&gt;file&lt;&#x2F;em&gt; a run produces (the trained model, a confusion-matrix plot, a preprocessing pipeline) gets stored and linked to the run that made it;&lt;&#x2F;li&gt;
&lt;li&gt;a &lt;strong&gt;model registry&lt;&#x2F;strong&gt; (MLflow) that versions the best models and stages them (staging → production);&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;pipeline orchestration&lt;&#x2F;strong&gt; (Airflow) that wires the above into a scheduled, repeatable flow;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;model serving&lt;&#x2F;strong&gt; (BentoML) that wraps a registered model in an API; and&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;model monitoring&lt;&#x2F;strong&gt; (Prometheus&#x2F;Grafana) that watches the served model and closes the loop back to data.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mymlops-workflow.png&quot; alt=&quot;A concrete MLOps workflow&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;A concrete MLOps workflow wiring the pieces together. Credit: mymlops.com&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Experiment tracking and artifact tracking are easy to conflate; MLflow happens to do both, which is why it appears three times in the diagram. The distinction matters: &lt;strong&gt;experiment tracking&lt;&#x2F;strong&gt; logs the &lt;em&gt;numbers&lt;&#x2F;em&gt; (&lt;code&gt;mlflow.log_metric(&quot;accuracy&quot;, acc)&lt;&#x2F;code&gt;), while &lt;strong&gt;artifact tracking&lt;&#x2F;strong&gt; stores the &lt;em&gt;files&lt;&#x2F;em&gt; a run produced (&lt;code&gt;mlflow.log_artifact(&quot;confusion_matrix.png&quot;)&lt;&#x2F;code&gt;). Same tool, two different jobs. You will use both in the hands-on below.&lt;&#x2F;p&gt;
&lt;p&gt;You do not build all of this on day one. The single highest-leverage habit, the one that pays off immediately even in a solo project, is &lt;strong&gt;experiment tracking&lt;&#x2F;strong&gt;. That is where we will get hands-on, before bridging to model serving at the end.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mlflow-the-tool-we-will-use&quot;&gt;MLflow: the tool we will use&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mlflow.org&quot;&gt;&lt;strong&gt;MLflow&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; is the open-source standard for experiment tracking. The mental model: an ML experiment is &lt;em&gt;environment + data + code&lt;&#x2F;em&gt;, and MLflow is the logbook that records, for each run, its &lt;strong&gt;hyperparameters + results + plots&lt;&#x2F;strong&gt; so you can compare runs later in a web UI. It is framework-agnostic (scikit-learn, PyTorch, TensorFlow, XGBoost…) and works for &lt;em&gt;any&lt;&#x2F;em&gt; computational experiment, not just ML.&lt;&#x2F;p&gt;
&lt;p&gt;MLflow has four components:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlflow-components.png&quot; alt=&quot;MLflow components&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;MLflow&amp;#x27;s components. Credit: mlflow.org&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlflow-overview.png&quot; alt=&quot;MLflow overview&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;How the pieces fit together when you track an experiment. Credit: mlflow.org&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tracking&lt;&#x2F;strong&gt;: log parameters, metrics, and artifacts per run (our focus).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Projects&lt;&#x2F;strong&gt;: package code so a run is reproducible.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Models&lt;&#x2F;strong&gt;: a standard packaging format so a trained model can be served anywhere.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Model Registry&lt;&#x2F;strong&gt;: version models and move them through stages (staging → production).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-mlflow-tracking-api&quot;&gt;The MLflow tracking API&lt;&#x2F;h2&gt;
&lt;p&gt;Tracking comes down to a few calls. First, name an experiment to group related runs, then wrap each run in a context manager:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;set_experiment&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;intro-to-mlops&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;with&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;start_run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;run_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;my-first-run&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_param&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.01&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;       #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; one hyperparameter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_params&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;epochs&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;batch&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 32&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt; #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; several at once&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_metric&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;accuracy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.92&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;            #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; a result&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_model&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;model&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;model&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; the model itself&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The three verbs you will use constantly:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;log_param&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;log_params&lt;&#x2F;code&gt;: key-value settings (hyperparameters, config).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;log_metric&lt;&#x2F;code&gt;: numeric results (loss, accuracy); these become plots in the UI so you can compare runs.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;log_model&lt;&#x2F;code&gt;: the trained model, in MLflow&#x27;s portable format.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Too lazy to log by hand?&lt;&#x2F;strong&gt; For supported libraries, a single &lt;code&gt;autolog()&lt;&#x2F;code&gt; call before &lt;code&gt;fit()&lt;&#x2F;code&gt; captures parameters, metrics, and the model automatically:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;autolog&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;      #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; or mlflow.tensorflow.autolog(), mlflow.pytorch.autolog()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;model&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fit&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X_train&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y_train&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;   #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; params, metrics, and model are logged for you&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Manual logging gives you the most control; &lt;code&gt;autolog&lt;&#x2F;code&gt; gives you most of the value for one line. And the feature people quietly love most: &lt;strong&gt;logging plots as artifacts&lt;&#x2F;strong&gt;, so a figure is stored with the run and viewable in the UI:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;savefig&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;loss.png&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_artifact&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;loss.png&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;where-mlflow-stores-runs-three-setup-options&quot;&gt;Where MLflow stores runs: three setup options&lt;&#x2F;h2&gt;
&lt;p&gt;Before logging anything, MLflow needs to know &lt;em&gt;where&lt;&#x2F;em&gt; runs go. There are three options, in increasing order of scale:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlflow-tracking-setup-overview.png&quot; alt=&quot;MLflow tracking setup overview&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;MLflow tracking setup. We use the local-database option below. Credit: mlflow.org&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Option 1, local filesystem&lt;&#x2F;strong&gt; (&lt;code&gt;.&#x2F;mlruns&lt;&#x2F;code&gt;): the simplest default.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Option 2, local database&lt;&#x2F;strong&gt; (&lt;code&gt;sqlite:&#x2F;&#x2F;&#x2F;mlflow.db&lt;&#x2F;code&gt;): still on your laptop, but the backend MLflow now recommends. &lt;strong&gt;This is what we use below.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Option 3, remote server&lt;&#x2F;strong&gt; (&lt;code&gt;http:&#x2F;&#x2F;server:5000&lt;&#x2F;code&gt;): a shared tracking server for a team.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;hands-on-track-your-first-experiment&quot;&gt;Hands-on: track your first experiment&lt;&#x2F;h2&gt;
&lt;p&gt;Everything below is self-contained and runs on a laptop in under ten minutes. We will train two small classifiers, log them to MLflow, and compare them in the UI.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;&#x2F;strong&gt; Python 3.9+ and &lt;code&gt;pip&lt;&#x2F;code&gt;. No GPU, no cloud account, no prior MLflow setup. Five steps: install → write the script → run it → run it again → open the UI.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want to just clone and run?&lt;&#x2F;strong&gt; The full source (script &lt;strong&gt;and&lt;&#x2F;strong&gt; notebook) lives in the companion repo: &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JienWeng&#x2F;mlops-tutorial&quot;&gt;&lt;strong&gt;github.com&#x2F;JienWeng&#x2F;mlops-tutorial&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;. &lt;code&gt;git clone&lt;&#x2F;code&gt; it, &lt;code&gt;pip install -r requirements.txt&lt;&#x2F;code&gt;, then &lt;code&gt;python train.py&lt;&#x2F;code&gt; (or open &lt;code&gt;notebook.ipynb&lt;&#x2F;code&gt;). The steps below explain what that code does.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;1-install&quot;&gt;1. Install&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;python&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; pip&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; install&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; mlflow&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; scikit-learn&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; matplotlib&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;2-choose-a-store-option-2-local-sqlite&quot;&gt;2. Choose a store (Option 2: local SQLite)&lt;&#x2F;h3&gt;
&lt;p&gt;We use a local SQLite database, Option 2 above. It keeps everything on your laptop with no server to run.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads-up (MLflow 3.x):&lt;&#x2F;strong&gt; the plain-folder store (&lt;code&gt;.&#x2F;mlruns&lt;&#x2F;code&gt; on its own) is now in maintenance mode and will raise an error unless you opt in. A SQLite URI like &lt;code&gt;sqlite:&#x2F;&#x2F;&#x2F;mlflow.db&lt;&#x2F;code&gt; is the friction-free local setup, so that is what we use. (On MLflow 2.x this same code still works.)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;We set the store directly in the script, so there is nothing to configure separately.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-the-experiment-script&quot;&gt;3. The experiment script&lt;&#x2F;h3&gt;
&lt;p&gt;Save this as &lt;code&gt;train.py&lt;&#x2F;code&gt;. It trains a logistic-regression classifier on the classic breast-cancer dataset, then logs the parameters, the metrics, the trained model, and a confusion-matrix plot.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; train.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; matplotlib&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pyplot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; plt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sklearn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;datasets&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; load_breast_cancer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;linear_model&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; LogisticRegression&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;metrics&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; ConfusionMatrixDisplay&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; accuracy_score&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; f1_score&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;model_selection&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; train_test_split&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Where to store runs: a local SQLite db (metadata) + .&#x2F;mlruns (artifacts)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;set_tracking_uri&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sqlite:&#x2F;&#x2F;&#x2F;mlflow.db&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Group related runs under a named experiment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;set_experiment&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;intro-to-mlops&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; --- data ---&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; load_breast_cancer&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;return_X_y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X_train&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; X_test&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y_train&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y_test&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; train_test_split&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; test_size&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; random_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;42&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; A hyperparameter we want to track and vary later&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;C&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; inverse regularisation strength&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;with&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;start_run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;run_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;logreg-C=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;C&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; --- train ---&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    model&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; LogisticRegression&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;C&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;C&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; max_iter&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;10_000&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    model&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fit&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X_train&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y_train&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; --- evaluate ---&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    preds&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; model&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;predict&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X_test&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    acc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; accuracy_score&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_test&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; preds&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    f1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; f1_score&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_test&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; preds&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; --- log: parameters, metrics, the model itself ---&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_param&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;model&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;LogisticRegression&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_param&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;C&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; C&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_metric&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;accuracy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; acc&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_metric&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;f1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; f1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_model&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;model&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;model&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; --- log: a plot as an artifact ---&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ConfusionMatrixDisplay&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;from_predictions&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_test&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; preds&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;title&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Confusion matrix (C=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;C&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;savefig&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;confusion_matrix.png&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; bbox_inches&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;tight&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log_artifact&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;confusion_matrix.png&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Logged run: accuracy=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;acc&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.4f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;, f1=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;f1&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;:.4f&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;python&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; train.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You just produced your first tracked run. The parameters, metrics, the serialised model, and the plot are all saved.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-make-a-second-run-to-compare-against&quot;&gt;4. Make a second run to compare against&lt;&#x2F;h3&gt;
&lt;p&gt;Change one line, &lt;code&gt;C = 0.01&lt;&#x2F;code&gt;, and run &lt;code&gt;python train.py&lt;&#x2F;code&gt; again. (Stronger regularisation; the score should move.) Now you have &lt;strong&gt;two runs to compare&lt;&#x2F;strong&gt;, which is the whole point of tracking.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-inspect-and-compare-in-the-ui&quot;&gt;5. Inspect and compare in the UI&lt;&#x2F;h3&gt;
&lt;p&gt;From the same folder, start the UI, pointing it at the same SQLite store:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;mlflow&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ui&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-backend-store-uri&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sqlite:&#x2F;&#x2F;&#x2F;mlflow.db&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Open &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;127.0.0.1:5000&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:5000&lt;&#x2F;a&gt;. You will see the &lt;code&gt;intro-to-mlops&lt;&#x2F;code&gt; experiment with both runs. It looks like this:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;slides&amp;#x2F;2026&amp;#x2F;intro-to-mlops&amp;#x2F;img&amp;#x2F;mlflow-ui.jpeg&quot; alt=&quot;The MLflow tracking UI&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The MLflow UI: every run with its parameters and metrics, ready to sort and compare. Credit: mlflow.org&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;From here you can:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;sort and filter runs by &lt;code&gt;accuracy&lt;&#x2F;code&gt; or &lt;code&gt;f1&lt;&#x2F;code&gt;,&lt;&#x2F;li&gt;
&lt;li&gt;tick two runs and click &lt;strong&gt;Compare&lt;&#x2F;strong&gt; to see parameters and metrics side by side,&lt;&#x2F;li&gt;
&lt;li&gt;open a run to view its &lt;strong&gt;confusion-matrix plot&lt;&#x2F;strong&gt; and download the saved model.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That comparison view answers the question MLOps exists to answer: which settings gave the best score, and exactly how were they produced? You now have a reproducible record of it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-serve-it-mlflow-bentoml-minimal&quot;&gt;6. Serve it: MLflow → BentoML (minimal)&lt;&#x2F;h3&gt;
&lt;p&gt;Tracking answers &quot;what&#x27;s the best run?&quot; Serving answers &quot;how do users call it?&quot; This is the &lt;strong&gt;model serving&lt;&#x2F;strong&gt; box from the workflow diagram above, and &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.bentoml.com&quot;&gt;&lt;strong&gt;BentoML&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; is the tool. The bridge is one call: load the model MLflow already tracked and stored, hand it to a Bento service.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;python&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; pip&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; install&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; bentoml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Save this as &lt;code&gt;service.py&lt;&#x2F;code&gt;, next to &lt;code&gt;mlflow.db&lt;&#x2F;code&gt; and &lt;code&gt;mlruns&#x2F;&lt;&#x2F;code&gt; (also in the companion repo, alongside &lt;code&gt;train.py&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; service.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; bentoml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;set_tracking_uri&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sqlite:&#x2F;&#x2F;&#x2F;mlflow.db&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Grab the most recent run of the experiment, no run ID to copy by hand&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;experiment&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;get_experiment_by_name&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;intro-to-mlops&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;latest_run&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;search_runs&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    experiment&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;experiment_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; order_by&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;start_time DESC&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; max_results&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;iloc&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;model&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mlflow&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;load_model&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;runs:&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;latest_run&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;run_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;model&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;bentoml&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;class&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; MLopsService&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;bentoml&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;api&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; predict&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; features&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; list&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;list&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;float&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; list&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; model&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;predict&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;features&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tolist&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;bentoml&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; serve&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; service:MLopsService&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That starts an HTTP API on &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;127.0.0.1:3000&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:3000&lt;&#x2F;a&gt; with interactive docs. Call it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;X&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; POST&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; http:&#x2F;&#x2F;127.0.0.1:3000&#x2F;predict&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;H&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type: application&#x2F;json&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;d&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&amp;quot;features&amp;quot;: [[14.0, 20.0, 90.0, 600.0, 0.1, 0.1, 0.1, 0.05, 0.2, 0.06, 0.4, 1.0, 3.0, 40.0, 0.006, 0.02, 0.03, 0.01, 0.02, 0.003, 16.0, 25.0, 105.0, 900.0, 0.14, 0.25, 0.3, 0.12, 0.3, 0.08]]}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s the whole bridge: &lt;strong&gt;MLflow tracks and stores the model; BentoML loads it by URI and serves it.&lt;&#x2F;strong&gt; Everything past this (batching, Docker packaging with &lt;code&gt;bentoml build&lt;&#x2F;code&gt;, autoscaling) is the same idea scaled up.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-you-just-built-and-what-comes-next&quot;&gt;What you just built (and what comes next)&lt;&#x2F;h3&gt;
&lt;p&gt;You have implemented the smallest real MLOps loop: &lt;strong&gt;parameterise → run → log → compare → serve&lt;&#x2F;strong&gt;. The natural extensions, in rough order of payoff:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Commit &lt;code&gt;train.py&lt;&#x2F;code&gt; to git&lt;&#x2F;strong&gt; so each run is tied to a code version.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Register the best model&lt;&#x2F;strong&gt; in MLflow&#x27;s Model Registry to give it a name and version, instead of pasting a run ID.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Automate the run in CI&lt;&#x2F;strong&gt; so a push retrains and re-logs.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Monitor&lt;&#x2F;strong&gt; the served model&#x27;s predictions, closing the loop back to data.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That list is exactly the MLOps workflow from earlier. You have now built its first two links.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;take-home-message&quot;&gt;Take-home message&lt;&#x2F;h2&gt;
&lt;p&gt;Research, and production work too, should be &lt;strong&gt;reproducible&lt;&#x2F;strong&gt; and, where possible, &lt;strong&gt;open&lt;&#x2F;strong&gt;. If your work involves machine learning, that means using dedicated tools to make experiments reproducible rather than relying on memory and scattered notebooks.&lt;&#x2F;p&gt;
&lt;p&gt;Adopting these habits in your daily workflow pays off quickly: time saved in the long run, engineering skill gained, and trust earned among collaborators. And MLflow in particular is &lt;strong&gt;not just for machine learning&lt;&#x2F;strong&gt;: it is a capable logbook for tracking &lt;em&gt;any&lt;&#x2F;em&gt; computational experiment and keeping your results reproducible over time.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;.small[This tutorial follows and is adapted from the lecture &lt;strong&gt;&quot;An introduction to MLOps&quot;&lt;&#x2F;strong&gt; by &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;aboucaud.github.io&#x2F;slides&#x2F;2023&#x2F;lsst-france-mlops&quot;&gt;Alexandre Boucaud&lt;&#x2F;a&gt; (LSST France, Lyon, December 2023), licensed under &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;creativecommons.org&#x2F;licenses&#x2F;by-sa&#x2F;4.0&quot;&gt;CC BY-SA 4.0&lt;&#x2F;a&gt;. The figures are taken from the accompanying &lt;a href=&quot;&#x2F;slides&#x2F;2026&#x2F;intro-to-mlops&#x2F;&quot;&gt;slides&lt;&#x2F;a&gt;, which are re-hosted under the same license; individual figures credit their original sources (ml-ops.org, mlflow.org, mymlops.com, GitHub, AWS) in their captions. This written tutorial and hands-on are an original adaptation, likewise shared under CC BY-SA 4.0.]&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Tree-Based Models Explained: From Decision Trees to Random Forest and XGBoost</title>
        <published>2026-05-11T00:00:00+00:00</published>
        <updated>2026-05-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/tree-based-models/"/>
        <id>https://jienweng.github.io/notes/tree-based-models/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/tree-based-models/">&lt;p&gt;A single decision tree is powerful but unstable. Nearly everything about Random Forests and XGBoost follows from that one sentence, so that is where this note starts, not at the formulas. Forests and boosting are just two different answers to the instability problem.&lt;&#x2F;p&gt;
&lt;p&gt;The path is:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{decision rules} \to \text{decision trees} \to \text{bagging} \to \text{Random Forest} \to \text{boosting} \to \text{XGBoost}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Intuition first, but the math is written out in full. I dislike formulas that appear from nowhere.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Run it yourself.&lt;&#x2F;strong&gt; The worked example is collected in a runnable notebook: &lt;a href=&quot;&#x2F;notebooks&#x2F;tree-based-models.ipynb&quot;&gt;&lt;strong&gt;download &lt;code&gt;tree-based-models.ipynb&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;. It uses &lt;code&gt;numpy&lt;&#x2F;code&gt;, &lt;code&gt;scikit-learn&lt;&#x2F;code&gt;, and (optionally) &lt;code&gt;xgboost&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;why-people-reach-for-trees-random-forests-and-xgboost&quot;&gt;Why People Reach for Trees, Random Forests, and XGBoost&lt;&#x2F;h2&gt;
&lt;p&gt;The motivation usually looks like this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linear models miss nonlinear structure.&lt;&#x2F;strong&gt; Trees capture nonlinear, rule-like boundaries without feature engineering.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Single trees are unstable.&lt;&#x2F;strong&gt; They are accurate but high-variance.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Random Forests stabilize trees.&lt;&#x2F;strong&gt; Bagging reduces variance by averaging many noisy trees.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Boosting repairs bias.&lt;&#x2F;strong&gt; XGBoost builds trees sequentially to correct systematic mistakes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So the choice is not &quot;trees vs. XGBoost&quot;. It is more like this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;single tree&lt;&#x2F;strong&gt; to explain a small, interpretable rule set.&lt;&#x2F;li&gt;
&lt;li&gt;Use a &lt;strong&gt;Random Forest&lt;&#x2F;strong&gt; when you want strong performance with low tuning risk.&lt;&#x2F;li&gt;
&lt;li&gt;Use &lt;strong&gt;XGBoost&lt;&#x2F;strong&gt; when you want maximum accuracy and can tune regularization.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;start-with-the-simplest-useful-model-a-decision-rule&quot;&gt;Start with the Simplest Useful Model: A Decision Rule&lt;&#x2F;h2&gt;
&lt;p&gt;Imagine a classifier that uses a single rule:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{if } x_1 \le 3.5, \text{ predict class 0, else class 1}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is a decision stump. It is weak, but it is easy to interpret. The point is not accuracy. The point is that rules like this are already nonlinear in the raw feature space.&lt;&#x2F;p&gt;
&lt;p&gt;Now chain many rules in sequence, and you get a decision tree.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;single_split_2d.png&quot; alt=&quot;Single split in 2D&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;A single decision rule creates a nonlinear boundary from a linear threshold.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;decision-trees-as-greedy-partitioning&quot;&gt;Decision Trees as Greedy Partitioning&lt;&#x2F;h2&gt;
&lt;p&gt;A decision tree repeatedly splits the feature space into regions. Each split chooses a feature and a threshold. The goal is to make the resulting child nodes more &quot;pure&quot; than the parent node.&lt;&#x2F;p&gt;
&lt;p&gt;For classification, the standard impurity measures are &lt;strong&gt;entropy&lt;&#x2F;strong&gt; and &lt;strong&gt;Gini&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Entropy for a node with class proportions $p_k$ is&lt;&#x2F;p&gt;
&lt;p&gt;$$
H(S) = -\sum_{k=1}^{K} p_k \log p_k.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Gini impurity is&lt;&#x2F;p&gt;
&lt;p&gt;$$
G(S) = 1 - \sum_{k=1}^{K} p_k^2.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Both measures are $0$ when a node is perfectly pure and larger when it is mixed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;information-gain&quot;&gt;Information Gain&lt;&#x2F;h3&gt;
&lt;p&gt;Suppose a candidate split divides a node $S$ into children $S_L$ and $S_R$. Let $n$ be the total samples in $S$, with $n_L$ and $n_R$ in the children. The &lt;strong&gt;information gain&lt;&#x2F;strong&gt; is&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{IG}(S, \text{split}) = H(S) - \frac{n_L}{n} H(S_L) - \frac{n_R}{n} H(S_R).
$$&lt;&#x2F;p&gt;
&lt;p&gt;Using Gini, replace $H$ with $G$ in the same formula.&lt;&#x2F;p&gt;
&lt;p&gt;The tree algorithm is greedy: at each node it chooses the split that maximizes this gain.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-small-example&quot;&gt;A Small Example&lt;&#x2F;h3&gt;
&lt;p&gt;Assume a node has $10$ samples: $6$ of class 1 and $4$ of class 0. The entropy is&lt;&#x2F;p&gt;
&lt;p&gt;$$
H(S) = -\left(\frac{6}{10}\log\frac{6}{10} + \frac{4}{10}\log\frac{4}{10}\right).
$$&lt;&#x2F;p&gt;
&lt;p&gt;Now imagine a split that creates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Left child: $4$ class 1, $1$ class 0 ($n_L=5$)&lt;&#x2F;li&gt;
&lt;li&gt;Right child: $2$ class 1, $3$ class 0 ($n_R=5$)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Compute $H(S_L)$ and $H(S_R)$, and you get a positive information gain. The split is not perfect, but it is better than the parent. That is enough to move the algorithm forward.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;tree_impurity_reduction.png&quot; alt=&quot;Impurity reduction example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;A split is chosen because it reduces impurity, not because it is globally optimal.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h3 id=&quot;leaf-predictions&quot;&gt;Leaf Predictions&lt;&#x2F;h3&gt;
&lt;p&gt;Once a node stops splitting, it becomes a leaf. For classification, the leaf prediction is&lt;&#x2F;p&gt;
&lt;p&gt;$$
\hat{p}_k = \frac{\text{count of class } k \text{ in leaf}}{\text{samples in leaf}},
$$&lt;&#x2F;p&gt;
&lt;p&gt;and the predicted class is the argmax of $\hat{p}_k$.&lt;&#x2F;p&gt;
&lt;p&gt;For regression, the leaf prediction is typically the mean:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\hat{y} = \frac{1}{|S|} \sum_{i \in S} y_i.
$$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-trees-are-unstable&quot;&gt;Why Trees Are Unstable&lt;&#x2F;h3&gt;
&lt;p&gt;A tree is a high-variance model. A small change in the dataset can change an early split, which changes every downstream region. Trees can overfit without strong regularization such as max depth, minimum samples per leaf, or pruning.&lt;&#x2F;p&gt;
&lt;p&gt;This is where ensembles enter the story.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bagging-reduce-variance-by-averaging&quot;&gt;Bagging: Reduce Variance by Averaging&lt;&#x2F;h2&gt;
&lt;p&gt;Bagging (bootstrap aggregating) trains many trees on different bootstrap samples of the data and averages their predictions.&lt;&#x2F;p&gt;
&lt;p&gt;Let $\hat{f}_1, \dots, \hat{f}_M$ be $M$ trees. The bagged predictor is&lt;&#x2F;p&gt;
&lt;p&gt;$$
\hat{f}_{\text{bag}}(x) = \frac{1}{M} \sum^M \hat{f}_m(x).
$$&lt;&#x2F;p&gt;
&lt;p&gt;If each tree has variance $\sigma^2$ and pairwise correlation $\rho$, then&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{Var}(\hat{f}_{\text{bag}}) = \frac{1}{M}(1-\rho)\sigma^2 + \rho\sigma^2.
$$&lt;&#x2F;p&gt;
&lt;p&gt;So averaging reduces variance, but correlation limits how much we gain.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;random-forest-bagging-feature-randomness&quot;&gt;Random Forest: Bagging + Feature Randomness&lt;&#x2F;h2&gt;
&lt;p&gt;Random Forests go one step further. They reduce correlation by forcing each split to consider only a random subset of features.&lt;&#x2F;p&gt;
&lt;p&gt;This does two things:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;It decorrelates the trees, which reduces the $\rho$ term above.&lt;&#x2F;li&gt;
&lt;li&gt;It prevents strong predictors from dominating every split.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The forest prediction is still an average (regression) or a majority vote (classification). The math is the same as bagging, but the diversity is higher.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;random_forest_bagging.png&quot; alt=&quot;Bootstrap aggregation&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Random Forests reduce variance by averaging many decorrelated trees.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;boosting-reduce-bias-by-building-sequentially&quot;&gt;Boosting: Reduce Bias by Building Sequentially&lt;&#x2F;h2&gt;
&lt;p&gt;Bagging attacks variance. Boosting attacks bias.&lt;&#x2F;p&gt;
&lt;p&gt;The idea is to build an additive model that corrects its own mistakes:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\hat{y}^{(t)}(x) = \hat{y}^{(t-1)}(x) + f_t(x),
$$&lt;&#x2F;p&gt;
&lt;p&gt;where each $f_t$ is a small tree and the sequence is trained to reduce a loss function.&lt;&#x2F;p&gt;
&lt;p&gt;Boosting is best understood as &lt;strong&gt;functional gradient descent&lt;&#x2F;strong&gt;: we are minimizing a loss by moving in function space, one tree at a time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xgboost-boosting-with-a-second-order-objective&quot;&gt;XGBoost: Boosting with a Second-Order Objective&lt;&#x2F;h2&gt;
&lt;p&gt;XGBoost formalizes this with an objective that includes regularization. At step $t$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\mathcal{L}^{(t)} = \sum_{i=1}^n l\left(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)\right) + \Omega(f_t),
$$&lt;&#x2F;p&gt;
&lt;p&gt;where $l$ is the training loss and the regularization term is&lt;&#x2F;p&gt;
&lt;p&gt;$$
\Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^{T} w_j^2.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Here $T$ is the number of leaves and $w_j$ is the score assigned to leaf $j$.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;second-order-approximation&quot;&gt;Second-Order Approximation&lt;&#x2F;h3&gt;
&lt;p&gt;Let&lt;&#x2F;p&gt;
&lt;p&gt;$$
g_i = \partial_{\hat{y}} l(y_i, \hat{y}_i^{(t-1)}),
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
h_i = \partial_{\hat{y}}^2 l(y_i, \hat{y}_i^{(t-1)}).
$$&lt;&#x2F;p&gt;
&lt;p&gt;Using a second-order Taylor expansion around $\hat{y}_i^{(t-1)}$, the objective becomes&lt;&#x2F;p&gt;
&lt;p&gt;$$
\tilde{\mathcal{L}}^{(t)} = \sum_{i=1}^n \left[g_i f_t(x_i) + \frac{1}{2} h_i f_t(x_i)^2\right] + \Omega(f_t).
$$&lt;&#x2F;p&gt;
&lt;p&gt;Because a tree predicts a constant weight $w_j$ for all samples in leaf $j$, we can group terms per leaf. Let $I_j$ be the indices in leaf $j$ and define&lt;&#x2F;p&gt;
&lt;p&gt;$$
G_j = \sum_{i \in I_j} g_i, \qquad H_j = \sum_{i \in I_j} h_i.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Then the optimal leaf weight is&lt;&#x2F;p&gt;
&lt;p&gt;$$
w_j^* = -\frac{G_j}{H_j + \lambda},
$$&lt;&#x2F;p&gt;
&lt;p&gt;and the best value of the objective for that leaf is&lt;&#x2F;p&gt;
&lt;p&gt;$$
-\frac{1}{2} \frac{G_j^2}{H_j + \lambda}.
$$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;split-gain&quot;&gt;Split Gain&lt;&#x2F;h3&gt;
&lt;p&gt;A split is good if it improves the objective. The gain of splitting a node into left and right children is&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{Gain} = \frac{1}{2}\left(\frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{G^2}{H + \lambda}\right) - \gamma.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is the exact reason XGBoost splits where it does. The split is not only about purity. It is about how much the second-order objective improves after regularization.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;xgboost_gain_split.png&quot; alt=&quot;XGBoost split gain&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;XGBoost chooses splits by maximizing second-order gain with regularization.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;why-xgboost-works-so-well&quot;&gt;Why XGBoost Works So Well&lt;&#x2F;h2&gt;
&lt;p&gt;The success of XGBoost is not a mystery. It is the combination of:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sequential correction:&lt;&#x2F;strong&gt; each tree targets the remaining error.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Second-order curvature:&lt;&#x2F;strong&gt; Hessians stabilize the optimization.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Regularization:&lt;&#x2F;strong&gt; the $\gamma$ and $\lambda$ terms actively penalize complexity.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Shrinkage:&lt;&#x2F;strong&gt; a learning rate scales each tree so the model does not overreact.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In practice, these ideas mean the model stays expressive without exploding in variance.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;boosting_stages.png&quot; alt=&quot;Boosting stages&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Boosting builds an additive model one small tree at a time.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;a-few-practical-tradeoffs&quot;&gt;A Few Practical Tradeoffs&lt;&#x2F;h2&gt;
&lt;p&gt;Tree ensembles are strong, but their strengths are specific:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Interpretability:&lt;&#x2F;strong&gt; single trees are readable, forests and boosted trees are not.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Bias vs variance:&lt;&#x2F;strong&gt; Random Forests reduce variance, boosting reduces bias.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Overfitting:&lt;&#x2F;strong&gt; deep trees can overfit, especially in boosting without regularization.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Data leakage:&lt;&#x2F;strong&gt; trees can memorize target leakage quickly, so features must be clean.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Class imbalance:&lt;&#x2F;strong&gt; impurity is dominated by the majority class unless weighted.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Compute cost:&lt;&#x2F;strong&gt; XGBoost is fast for trees, but still heavier than linear models.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;a-worked-dataset-example-synthetic-regression&quot;&gt;A Worked Dataset Example (Synthetic Regression)&lt;&#x2F;h2&gt;
&lt;p&gt;To make this concrete, we use a small synthetic regression dataset from scikit-learn. It is numeric, quick to run, and avoids any external downloads. The idea is to compare a single tree, a Random Forest, and XGBoost on the same train&#x2F;test split.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;regression_overview.png&quot; alt=&quot;Regression dataset overview&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;A synthetic regression dataset with noise and nonlinear structure.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Below is a compact code snippet. The &lt;a href=&quot;&#x2F;notebooks&#x2F;tree-based-models.ipynb&quot;&gt;full notebook&lt;&#x2F;a&gt; is provided separately so you can run it end-to-end.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;datasets&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; make_regression&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;model_selection&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; train_test_split&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;metrics&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; mean_absolute_error&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; mean_squared_error&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tree&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; DecisionTreeRegressor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sklearn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;ensemble&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; RandomForestRegressor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;	from&lt;&#x2F;span&gt;&lt;span&gt; xgboost&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; XGBRegressor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;except&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; ImportError&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	XGBRegressor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; make_regression&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;n_samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2000&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; n_features&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;12&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; noise&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;15.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; random_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;42&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X_train&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; X_test&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y_train&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y_test&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; train_test_split&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; test_size&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; random_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;42&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tree&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; DecisionTreeRegressor&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;max_depth&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; random_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;42&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rf&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; RandomForestRegressor&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;n_estimators&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;300&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; random_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;42&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;models&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;tree&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;rf&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; rf&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; XGBRegressor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; is&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; not&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; None&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	xgb&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; XGBRegressor&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;		n_estimators&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;400&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;		max_depth&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;		learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.05&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;		subsample&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.8&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;		colsample_bytree&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.8&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;		reg_lambda&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;		random_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;42&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	models&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;xgb&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; xgb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; model&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; models&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;items&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	model&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fit&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X_train&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y_train&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	preds&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; model&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;predict&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X_test&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	rmse&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqrt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;mean_squared_error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_test&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; preds&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	mae&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mean_absolute_error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_test&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; preds&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;	print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;rmse&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; round&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;rmse&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;mae&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; round&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;mae&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;model_comparison_bars.png&quot; alt=&quot;Model comparison&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;RMSE and MAE comparison across a single tree, Random Forest, and XGBoost.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;tree-based-models&amp;#x2F;feature_importance.png&quot; alt=&quot;Feature importances&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Feature importance comparison for Random Forest and XGBoost.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;what-code-to-look-at&quot;&gt;What Code to Look At&lt;&#x2F;h2&gt;
&lt;p&gt;If you want to read real implementations, start here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sklearn.tree.DecisionTreeClassifier&lt;&#x2F;code&gt; for tree splits, impurity, and pruning logic.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;sklearn.ensemble.RandomForestClassifier&lt;&#x2F;code&gt; for bagging and feature subsampling.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;xgboost.XGBClassifier&lt;&#x2F;code&gt; for second-order boosting with regularization.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-big-picture&quot;&gt;The Big Picture&lt;&#x2F;h2&gt;
&lt;p&gt;We started with simple decision rules. That gave us decision trees.&lt;&#x2F;p&gt;
&lt;p&gt;Decision trees are powerful but unstable, so we average them. That gives us bagging, and then Random Forests.&lt;&#x2F;p&gt;
&lt;p&gt;Random Forests reduce variance but still leave bias on the table, so we build trees sequentially to correct errors. That gives us boosting, and XGBoost.&lt;&#x2F;p&gt;
&lt;p&gt;Trees define nonlinear partitions, bagging stabilizes them, boosting makes them accurate. That is the whole story, and every formula above is one of those three moves written out.&lt;&#x2F;p&gt;
&lt;p&gt;At some point I may extend the notebook with hyperparameter tuning and a multi-class example. If you want that sooner, leave a comment.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Fast Fourier Transform Explained from the DFT, with a Sound-Wave Example</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/fft-sound-wave/"/>
        <id>https://jienweng.github.io/notes/fft-sound-wave/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/fft-sound-wave/">&lt;p&gt;The butterfly diagram is usually the first thing people show you about the FFT. I think it should be the last. The FFT is nothing more than a fast way to compute the Discrete Fourier Transform, so none of it makes sense until the DFT does. And the DFT itself is easiest to see if you start from a sampled sound wave.&lt;&#x2F;p&gt;
&lt;p&gt;So this note goes:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{sound wave} \to \text{samples} \to \text{DFT} \to \text{FFT} \to \text{spectrum}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;We build up the picture of sound as a sum of sinusoids, get the DFT as its discrete version, and then let the structure of the DFT matrix hand us the Cooley-Tukey FFT almost for free. At the end we run the whole thing on a real sound wave, implemented from scratch in Python.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Run it yourself.&lt;&#x2F;strong&gt; Every code block below is collected in a runnable Jupyter notebook: &lt;a href=&quot;&#x2F;notebooks&#x2F;fft-sound-wave.ipynb&quot;&gt;&lt;strong&gt;download &lt;code&gt;fft-sound-wave.ipynb&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;. It only needs &lt;code&gt;numpy&lt;&#x2F;code&gt; and &lt;code&gt;matplotlib&lt;&#x2F;code&gt; (&lt;code&gt;pip install numpy matplotlib&lt;&#x2F;code&gt;), and runs top to bottom.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;start-with-a-sound-wave&quot;&gt;Start with a Sound Wave&lt;&#x2F;h2&gt;
&lt;p&gt;Think of a pure musical tone. When a tuning fork vibrates at $440$ Hz, it displaces air molecules in a nearly sinusoidal way. The pressure at a fixed point in space looks like&lt;&#x2F;p&gt;
&lt;p&gt;$$
x(t) = A \cos(2\pi f t + \varphi),
$$&lt;&#x2F;p&gt;
&lt;p&gt;with frequency $f = 440$ Hz, some amplitude $A$, and some phase $\varphi$.&lt;&#x2F;p&gt;
&lt;p&gt;A real sound is almost never a single sinusoid. A piano note at A4 contains the fundamental at $440$ Hz plus overtones at roughly $880$ Hz, $1320$ Hz, and so on, each with its own amplitude. More generally, any reasonable sound can be written as a sum of sinusoids:&lt;&#x2F;p&gt;
&lt;p&gt;$$
x(t) = \sum_{k} A_k \cos(2\pi f_k t + \varphi_k).
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is the Fourier picture. The time-domain view tells you what the waveform looks like at each instant. The frequency-domain view tells you which sinusoids, at which amplitudes and phases, combine to produce that waveform. The two views carry the same information but answer different questions.&lt;&#x2F;p&gt;
&lt;p&gt;On a computer, we never see $x(t)$ directly. We see samples. An analogue-to-digital converter records the pressure at a fixed sample rate $f_s$ (for example $44{,}100$ samples per second for CD audio), producing a finite sequence&lt;&#x2F;p&gt;
&lt;p&gt;$$
x_n = x(n &#x2F; f_s), \qquad n = 0, 1, \ldots, N-1.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The question we really want to answer is: given these $N$ samples, which frequencies are present and how strong are they?&lt;&#x2F;p&gt;
&lt;p&gt;That question has a precise answer called the Discrete Fourier Transform.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-fourier-series-to-the-dft&quot;&gt;From Fourier Series to the DFT&lt;&#x2F;h2&gt;
&lt;p&gt;The continuous Fourier series says that a periodic signal can be written as a sum of complex exponentials:&lt;&#x2F;p&gt;
&lt;p&gt;$$
x(t) = \sum_{k=-\infty}^{\infty} c_k , e^{i 2\pi k t &#x2F; T},
$$&lt;&#x2F;p&gt;
&lt;p&gt;where $T$ is the period. The coefficients $c_k$ tell us how much of each frequency $k&#x2F;T$ is present. Complex exponentials show up here for a reason. Using Euler&#x27;s formula,&lt;&#x2F;p&gt;
&lt;p&gt;$$
e^{i\theta} = \cos\theta + i\sin\theta,
$$&lt;&#x2F;p&gt;
&lt;p&gt;a single complex exponential carries both a cosine and a sine at the same frequency. That packages amplitude and phase into one object and lets the math stay linear and clean.&lt;&#x2F;p&gt;
&lt;p&gt;Now discretize. Suppose we sample $x(t)$ at $N$ evenly spaced points over one period $T$, with spacing $\Delta t = T&#x2F;N$. The samples are&lt;&#x2F;p&gt;
&lt;p&gt;$$
x_n = x(n \Delta t), \qquad n = 0, 1, \ldots, N-1.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The natural discrete analogue of the Fourier series uses $N$ complex exponentials,&lt;&#x2F;p&gt;
&lt;p&gt;$$
e^{i 2\pi k n &#x2F; N}, \qquad k = 0, 1, \ldots, N-1,
$$&lt;&#x2F;p&gt;
&lt;p&gt;as a basis for length-$N$ sequences. These discrete exponentials are orthogonal:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\sum_{n=0}^{N-1} e^{i 2\pi k n &#x2F; N} , e^{-i 2\pi \ell n &#x2F; N}
= \begin{cases} N &amp;amp; \text{if } k = \ell, \ 0 &amp;amp; \text{otherwise.} \end{cases}
$$&lt;&#x2F;p&gt;
&lt;p&gt;Orthogonality is what makes the transform invertible. It also tells us the right formula for the coefficients, by projecting $x_n$ onto each basis vector.&lt;&#x2F;p&gt;
&lt;p&gt;This gives the Discrete Fourier Transform. For a sequence $x_0, x_1, \ldots, x_{N-1}$, define&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_k = \sum_{n=0}^{N-1} x_n , e^{-i 2\pi k n &#x2F; N}, \qquad k = 0, 1, \ldots, N-1.
$$&lt;&#x2F;p&gt;
&lt;p&gt;And the inverse:&lt;&#x2F;p&gt;
&lt;p&gt;$$
x_n = \frac{1}{N} \sum_{k=0}^{N-1} X_k , e^{i 2\pi k n &#x2F; N}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Each $X_k$ is a complex number. Its magnitude $|X_k|$ tells us how strong the frequency component at bin $k$ is, and its phase $\arg X_k$ tells us where that sinusoid starts.&lt;&#x2F;p&gt;
&lt;p&gt;The frequency associated with bin $k$ is&lt;&#x2F;p&gt;
&lt;p&gt;$$
f_k = \frac{k}{N} f_s,
$$&lt;&#x2F;p&gt;
&lt;p&gt;for $k = 0, 1, \ldots, N&#x2F;2$, after which the bins fold by symmetry for real inputs. The highest frequency we can resolve is $f_s &#x2F; 2$, the Nyquist frequency. Anything above that aliases down into lower bins, which is why sample rate matters.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-the-transform-actually-does-the-winding-view&quot;&gt;What the Transform Actually Does: The Winding View&lt;&#x2F;h2&gt;
&lt;p&gt;The formula for $X_k$ is easy to write down but easy to stare at without seeing anything. Before we optimize &lt;em&gt;how&lt;&#x2F;em&gt; to compute it, here is a different perspective on &lt;em&gt;what&lt;&#x2F;em&gt; it computes, one that makes the whole transform intuitive.&lt;&#x2F;p&gt;
&lt;p&gt;Look again at a single term. The factor&lt;&#x2F;p&gt;
&lt;p&gt;$$
e^{-i 2\pi k n &#x2F; N}
$$&lt;&#x2F;p&gt;
&lt;p&gt;is a unit vector in the complex plane that rotates clockwise as $n$ advances. Multiplying the signal $x_n$ by it &lt;strong&gt;winds the signal around the origin&lt;&#x2F;strong&gt; at a winding rate set by $k$: small $k$ winds slowly, large $k$ winds fast. The sum over $n$ then just adds up all those wound points, and dividing by $N$ gives their &lt;strong&gt;center of mass&lt;&#x2F;strong&gt; (their average position).&lt;&#x2F;p&gt;
&lt;p&gt;So $X_k &#x2F; N$ is literally: &lt;em&gt;take the signal, wrap it around a circle at rate $k$, and find the center of mass of the resulting curve.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now the key effect. When the winding rate &lt;strong&gt;matches&lt;&#x2F;strong&gt; a frequency that is actually present in the signal, the signal&#x27;s peaks land on the same side of the circle on every turn. The wound points pile up on one side, and their center of mass sits far from the origin: a large $|X_k|$. When the winding rate &lt;strong&gt;does not match&lt;&#x2F;strong&gt;, the peaks land at different angles each turn, spread evenly around the circle, and cancel. The center of mass collapses to near zero.&lt;&#x2F;p&gt;
&lt;p&gt;To see it, here is a simple two-component signal (a $2$ Hz plus a $3$ Hz cosine, chosen at low frequencies purely so the winding is visible) wound at four different rates:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fft-sound-wave&amp;#x2F;fft_winding_frequencies.png&quot; alt=&quot;The signal wound around the origin at four winding frequencies, with the center of mass marked&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Winding a 2 Hz + 3 Hz signal. At 2 Hz and 3 Hz the center of mass (red) pulls away from the origin; at 1 Hz and 2.5 Hz it cancels back to the center.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Sweep the winding rate continuously and record how far the center of mass sits from the origin at each rate, and you trace out the Fourier transform magnitude directly: flat and near zero everywhere except sharp bumps at exactly the frequencies the signal contains.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fft-sound-wave&amp;#x2F;fft_winding_spectrum.png&quot; alt=&quot;Magnitude of the center of mass as a function of winding frequency, peaking at 2 and 3 Hz&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Sweeping the winding frequency reproduces the spectrum: peaks at 2 and 3 Hz, near-zero elsewhere. This curve *is* the Fourier transform.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;The same picture is clearer in three dimensions. Stack each wound copy of the signal along a third axis given by its winding frequency. Most slices are balanced loops centered on the axis; only at the true frequencies does the center of mass bulge outward.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fft-sound-wave&amp;#x2F;fft_3d_winding.png&quot; alt=&quot;3D plot stacking the wound signal along a winding-frequency axis, with centers of mass bulging at 2 and 3 Hz&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The same winding viewed in 3D. Each slice is the signal wound at one frequency; the red centers of mass bulge out only at 2 and 3 Hz.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;That is the one-sentence summary of the entire transform: &lt;strong&gt;the DFT measures how well the signal lines up with a corkscrew of each frequency.&lt;&#x2F;strong&gt; The complex exponential $e^{-i 2\pi k n &#x2F; N}$ is that corkscrew, and the sum is the alignment score. Everything that follows (the matrix form, the even&#x2F;odd split, the butterflies) is just machinery for computing all $N$ of these alignment scores quickly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-dft-as-a-matrix&quot;&gt;The DFT as a Matrix&lt;&#x2F;h2&gt;
&lt;p&gt;Define the primitive root of unity&lt;&#x2F;p&gt;
&lt;p&gt;$$
\omega_N = e^{-i 2\pi &#x2F; N}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Then&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_k = \sum_{n=0}^{N-1} x_n , \omega_N^{kn}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Let $F_N$ be the $N \times N$ matrix with entries $(F_N)_{k,n} = \omega_N^{kn}$. Then the DFT is just a matrix-vector product:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X = F_N x.
$$&lt;&#x2F;p&gt;
&lt;p&gt;A matrix-vector product with a dense $N \times N$ matrix takes $O(N^2)$ multiplications and additions. For a one-second CD-quality audio frame with $N = 44{,}100$ samples that is about $2 \times 10^9$ operations per frame. Doable, but clearly wasteful if the matrix has structure.&lt;&#x2F;p&gt;
&lt;p&gt;It does. The matrix $F_N$ is extremely structured, because $\omega_N$ is a root of unity. The FFT is the algorithm that exploits that structure.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-fft-idea-split-into-even-and-odd&quot;&gt;The FFT Idea: Split Into Even and Odd&lt;&#x2F;h2&gt;
&lt;p&gt;The Cooley-Tukey FFT is a divide-and-conquer algorithm. Assume $N$ is a power of two (we can always zero-pad to get there). Split the input sequence by index parity:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{even part: } ; x_0, x_2, x_4, \ldots, x_{N-2},
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{odd part: } ; x_1, x_3, x_5, \ldots, x_{N-1}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Now rewrite $X_k$ by separating even and odd indices:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_k ;=; \sum_{m=0}^{N&#x2F;2 - 1} x_{2m} , e^{-i 2\pi k (2m) &#x2F; N} ;+; \sum_{m=0}^{N&#x2F;2 - 1} x_{2m+1} , e^{-i 2\pi k (2m+1) &#x2F; N}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Pull out the extra factor in the odd sum:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_k ;=; \sum_{m=0}^{N&#x2F;2 - 1} x_{2m} , e^{-i 2\pi k m &#x2F; (N&#x2F;2)} ;+; e^{-i 2\pi k &#x2F; N} \sum_{m=0}^{N&#x2F;2 - 1} x_{2m+1} , e^{-i 2\pi k m &#x2F; (N&#x2F;2)}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Now look carefully at each sum. Each one is itself a DFT, of length $N&#x2F;2$. Let&lt;&#x2F;p&gt;
&lt;p&gt;$$
E_k = \sum_{m=0}^{N&#x2F;2 - 1} x_{2m} , e^{-i 2\pi k m &#x2F; (N&#x2F;2)},
\qquad
O_k = \sum_{m=0}^{N&#x2F;2 - 1} x_{2m+1} , e^{-i 2\pi k m &#x2F; (N&#x2F;2)}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Then&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_k = E_k + e^{-i 2\pi k &#x2F; N} , O_k.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The quantity $e^{-i 2\pi k &#x2F; N}$ is usually written&lt;&#x2F;p&gt;
&lt;p&gt;$$
W_N^k = e^{-i 2\pi k &#x2F; N}
$$&lt;&#x2F;p&gt;
&lt;p&gt;and called a &lt;strong&gt;twiddle factor&lt;&#x2F;strong&gt;. It is the bookkeeping that corrects for the shift between even and odd indices.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-butterfly-two-outputs-for-the-price-of-one&quot;&gt;The Butterfly: Two Outputs for the Price of One&lt;&#x2F;h2&gt;
&lt;p&gt;We have computed $X_k$ in terms of $E_k$ and $O_k$ for $k = 0, 1, \ldots, N&#x2F;2 - 1$. What about $k = N&#x2F;2, N&#x2F;2+1, \ldots, N-1$?&lt;&#x2F;p&gt;
&lt;p&gt;Use the periodicity of $E_k$ and $O_k$. Both are length-$N&#x2F;2$ DFTs, so&lt;&#x2F;p&gt;
&lt;p&gt;$$
E_{k + N&#x2F;2} = E_k, \qquad O_{k + N&#x2F;2} = O_k.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The twiddle factor shifts by a sign:&lt;&#x2F;p&gt;
&lt;p&gt;$$
W_N^{k + N&#x2F;2} = e^{-i 2\pi (k + N&#x2F;2) &#x2F; N} = e^{-i 2\pi k &#x2F; N} e^{-i\pi} = -W_N^k.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Combining these,&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_k = E_k + W_N^k , O_k,
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_{k + N&#x2F;2} = E_k - W_N^k , O_k.
$$&lt;&#x2F;p&gt;
&lt;p&gt;That pair of equations is one &lt;strong&gt;butterfly&lt;&#x2F;strong&gt;. From two inputs $E_k$ and $O_k$ (and a precomputed twiddle), we produce two outputs $X_k$ and $X_{k+N&#x2F;2}$ using one complex multiplication and two complex additions. We reuse work instead of redoing it.&lt;&#x2F;p&gt;
&lt;p&gt;At this point the whole algorithm is already visible. Compute two $N&#x2F;2$-point DFTs. Combine them with $N&#x2F;2$ butterflies to produce the $N$-point DFT. Recurse on each half.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;complexity-why-it-is-o-n-log-n&quot;&gt;Complexity: Why It Is $O(N \log N)$&lt;&#x2F;h2&gt;
&lt;p&gt;Let $T(N)$ be the number of operations to compute an $N$-point DFT using this recursion. We do:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Two DFTs of size $N&#x2F;2$, each costing $T(N&#x2F;2)$.&lt;&#x2F;li&gt;
&lt;li&gt;$N&#x2F;2$ butterflies to combine them, each a constant amount of work. So $O(N)$ total for the combine step.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That gives the recurrence&lt;&#x2F;p&gt;
&lt;p&gt;$$
T(N) = 2 T(N&#x2F;2) + cN,
$$&lt;&#x2F;p&gt;
&lt;p&gt;with $T(1) = O(1)$, for some constant $c$.&lt;&#x2F;p&gt;
&lt;p&gt;Unroll the recurrence:&lt;&#x2F;p&gt;
&lt;p&gt;$$
T(N) ;=; 2T(N&#x2F;2) + cN ;=; 4 T(N&#x2F;4) + 2cN ;=; 8 T(N&#x2F;8) + 3cN ;=; \ldots
$$&lt;&#x2F;p&gt;
&lt;p&gt;After $\log_2 N$ levels of unrolling we reach $T(1)$, and each level contributes $cN$ work:&lt;&#x2F;p&gt;
&lt;p&gt;$$
T(N) = N \cdot T(1) + cN \log_2 N = O(N \log N).
$$&lt;&#x2F;p&gt;
&lt;p&gt;Compare that to the naive DFT. For $N = 2^{20} \approx 10^6$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
N^2 = 10^{12}, \qquad N \log_2 N \approx 2 \times 10^7.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Five orders of magnitude. That is the whole reason real-time audio processing, modern communications, and large-scale scientific computing are tractable.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-worked-example-n-4&quot;&gt;A Worked Example: N = 4&lt;&#x2F;h2&gt;
&lt;p&gt;To see the recursion concretely, take $N = 4$ with input $x = (x_0, x_1, x_2, x_3)$.&lt;&#x2F;p&gt;
&lt;p&gt;The even part is $(x_0, x_2)$, and the odd part is $(x_1, x_3)$. Both are length-2 DFTs.&lt;&#x2F;p&gt;
&lt;p&gt;For a length-2 DFT with input $(a, b)$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
Y_0 = a + b, \qquad Y_1 = a - b,
$$&lt;&#x2F;p&gt;
&lt;p&gt;which is itself one butterfly with twiddle $W_2^0 = 1$.&lt;&#x2F;p&gt;
&lt;p&gt;So the two half-DFTs give&lt;&#x2F;p&gt;
&lt;p&gt;$$
E_0 = x_0 + x_2, \quad E_1 = x_0 - x_2,
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
O_0 = x_1 + x_3, \quad O_1 = x_1 - x_3.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The twiddle factors at size $N=4$ are $W_4^0 = 1$ and $W_4^1 = e^{-i\pi&#x2F;2} = -i$. Combining:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_0 = E_0 + W_4^0 , O_0 = (x_0 + x_2) + (x_1 + x_3),
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_1 = E_1 + W_4^1 , O_1 = (x_0 - x_2) - i(x_1 - x_3),
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_2 = E_0 - W_4^0 , O_0 = (x_0 + x_2) - (x_1 + x_3),
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_3 = E_1 - W_4^1 , O_1 = (x_0 - x_2) + i(x_1 - x_3).
$$&lt;&#x2F;p&gt;
&lt;p&gt;Four outputs, built from two pairs of butterflies. You can check by hand that these match $X_k = \sum_n x_n \omega_4^{kn}$ directly. This is the full FFT, in miniature.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hand-calculation-with-real-numbers&quot;&gt;Hand Calculation with Real Numbers&lt;&#x2F;h2&gt;
&lt;p&gt;The symbolic form is useful, but it is more convincing to watch the recursion run on actual numbers. Take the concrete input&lt;&#x2F;p&gt;
&lt;p&gt;$$
x = (x_0, x_1, x_2, x_3) = (1, 2, 3, 4).
$$&lt;&#x2F;p&gt;
&lt;p&gt;Following the principle from the previous section step by step.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Step 1. Split by parity.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{even: } (x_0, x_2) = (1, 3), \qquad \text{odd: } (x_1, x_3) = (2, 4).
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Step 2. Length-2 DFT of each half.&lt;&#x2F;strong&gt; Using $Y_0 = a + b$, $Y_1 = a - b$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
E_0 = 1 + 3 = 4, \qquad E_1 = 1 - 3 = -2,
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
O_0 = 2 + 4 = 6, \qquad O_1 = 2 - 4 = -2.
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Step 3. Twiddle factors at $N = 4$.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;$$
W_4^0 = e^{0} = 1, \qquad W_4^1 = e^{-i\pi&#x2F;2} = -i.
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Step 4. Apply the butterfly equations.&lt;&#x2F;strong&gt; Using $X_k = E_k + W_4^k O_k$ and $X_{k+2} = E_k - W_4^k O_k$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_0 = E_0 + W_4^0 , O_0 = 4 + (1)(6) = 10,
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_1 = E_1 + W_4^1 , O_1 = -2 + (-i)(-2) = -2 + 2i,
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_2 = E_0 - W_4^0 , O_0 = 4 - 6 = -2,
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_3 = E_1 - W_4^1 , O_1 = -2 - (-i)(-2) = -2 - 2i.
$$&lt;&#x2F;p&gt;
&lt;p&gt;So by hand,&lt;&#x2F;p&gt;
&lt;p&gt;$$
X = (10,; -2 + 2i,; -2,; -2 - 2i).
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Step 5. Cross-check with code.&lt;&#x2F;strong&gt; Running the same input through &lt;code&gt;numpy&lt;&#x2F;code&gt; and our from-scratch implementations should produce exactly the same vector:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;array&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; dtype&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;complex&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;numpy  :&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fft&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fft&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;naive  :&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; naive_dft&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;fft_rec:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; fft_recursive&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Expected output (up to floating-point error):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;numpy  : [10.+0.j -2.+2.j -2.+0.j -2.-2.j]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;naive  : [10.+0.j -2.+2.j -2.+0.j -2.-2.j]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fft_rec: [10.+0.j -2.+2.j -2.+0.j -2.-2.j]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Three different routes, one answer. The naive DFT sums all $N^2 = 16$ terms directly. The recursive FFT computes two length-2 DFTs and combines them with two butterflies, using far fewer operations. Both recover the same spectrum because they are computing the same mathematical object, just through different arithmetic paths.&lt;&#x2F;p&gt;
&lt;p&gt;This is the moment where the principle becomes tangible. Even&#x2F;odd split, two half-DFTs, twiddle factor, butterfly. Four equations, one vector of outputs. Scale the same pattern recursively to $N = 8, 16, \ldots, 2^{20}$ and you have the full FFT.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-scratch-in-python-naive-dft-and-recursive-fft&quot;&gt;From Scratch in Python: Naive DFT and Recursive FFT&lt;&#x2F;h2&gt;
&lt;p&gt;The fastest way to internalize this is to implement both and watch them agree.&lt;&#x2F;p&gt;
&lt;p&gt;Start with the naive DFT, a direct translation of the definition:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; naive_dft&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;asarray&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; dtype&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;complex&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;shape&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    n&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;arange&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;reshape&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    M&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;j&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; M&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; @&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the recursive Cooley-Tukey FFT. It assumes $N$ is a power of two. When $N$ drops to a small base case we hand off to the naive DFT, which is fine for tiny sizes:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; fft_recursive&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;asarray&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; dtype&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;complex&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;shape&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        raise&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; ValueError&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Length of input must be a power of 2.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; naive_dft&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    X_even&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; fft_recursive&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    X_odd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; fft_recursive&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;arange&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    twiddle&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;j&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;concatenate&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;X_even&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; twiddle&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; X_odd&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                           X_even&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; twiddle&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; X_odd&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Check agreement with &lt;code&gt;numpy.fft.fft&lt;&#x2F;code&gt; on random input:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rng&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;random&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;default_rng&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; rng&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;standard_normal&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1024&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X_naive&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; naive_dft&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X_fft&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; fft_recursive&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X_np&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fft&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fft&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;allclose&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X_naive&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; X_np&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;allclose&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X_fft&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; X_np&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Both should print &lt;code&gt;True&lt;&#x2F;code&gt; up to floating-point error. The three implementations compute the same mathematical object. The FFT just computes it faster.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-real-sound-wave-example&quot;&gt;A Real Sound-Wave Example&lt;&#x2F;h2&gt;
&lt;p&gt;Now the payoff. We construct a synthetic sound wave made of three tones, sample it, and recover those tones from the samples using the FFT.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; matplotlib&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pyplot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; plt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sample_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 8000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;sample_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; duration&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;arange&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; sample_rate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sin&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 220&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;     +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.6&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sin&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 440&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;     +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.3&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sin&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 880&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.05&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;random&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;default_rng&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;standard_normal&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The signal &lt;code&gt;x&lt;&#x2F;code&gt; contains a strong $220$ Hz tone, a medium $440$ Hz tone, a weaker $880$ Hz tone, and a small amount of noise.&lt;&#x2F;p&gt;
&lt;p&gt;In the time domain the waveform looks periodic but visually messy. That is expected. Time domain is not the right view for this question.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fft-sound-wave&amp;#x2F;time_domain_signal.png&quot; alt=&quot;Synthetic sound wave in the time domain&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;A short window of the synthetic three-tone signal in the time domain.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Now apply the FFT. Because &lt;code&gt;N = 8000&lt;&#x2F;code&gt; is not a power of two, we either zero-pad up to the next power of two or use a mixed-radix FFT. For this note we will zero-pad, which is also a common practical trick to improve bin resolution:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; next_pow2&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;n&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;bit_length&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;N_pad&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; next_pow2&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x_pad&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;zeros&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N_pad&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x_pad&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;X&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; fft_recursive&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_pad&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;magnitude&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;abs&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;X&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; N&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;freqs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;arange&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N_pad&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; sample_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; N_pad&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;half&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; N_pad&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;plot&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;freqs&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;half&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; magnitude&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt;half&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;xlabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Frequency (Hz)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;ylabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Magnitude&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;title&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Magnitude spectrum of the synthetic sound wave&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;grid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;show&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The spectrum shows three clean peaks, at $220$, $440$, and $880$ Hz, with heights roughly proportional to the amplitudes we put in. The noise spreads thinly across all other bins and stays small.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fft-sound-wave&amp;#x2F;frequency_spectrum.png&quot; alt=&quot;Magnitude spectrum with three peaks&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The DFT magnitude spectrum. The three input sinusoids appear as isolated peaks at 220, 440, and 880 Hz.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;That is the central picture. The time-domain view showed an oscillating mess. The frequency-domain view recovers exactly the three tones we synthesized, because that is what the DFT does. The FFT just made the computation fast enough to be practical on a full second of audio.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;naive-dft-vs-fft-timing&quot;&gt;Naive DFT vs FFT: Timing&lt;&#x2F;h2&gt;
&lt;p&gt;To make the complexity story concrete, it helps to time both implementations on increasing input sizes. The naive DFT should grow quadratically, and the recursive FFT should grow close to linearly in $N \log N$:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sizes&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;**&lt;&#x2F;span&gt;&lt;span&gt;k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; k&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 13&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;naive_times&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fft_times&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; N&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; sizes&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;random&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;default_rng&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;standard_normal&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    t0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;perf_counter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    naive_dft&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    naive_times&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;time&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;perf_counter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; t0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    t0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;perf_counter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fft_recursive&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fft_times&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;time&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;perf_counter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; t0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;loglog&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;sizes&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; naive_times&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; marker&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; label&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;naive DFT&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;loglog&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;sizes&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; fft_times&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; marker&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; label&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;recursive FFT&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;xlabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;N&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;ylabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Time (s)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;title&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Runtime: naive DFT vs recursive FFT&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;legend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;grid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; which&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;both&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;show&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On a log-log plot, the naive DFT has slope close to $2$ and the recursive FFT has slope close to $1$. That is the $O(N^2)$ versus $O(N \log N)$ gap, visualized directly.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;fft-sound-wave&amp;#x2F;naive_vs_fft_timing.png&quot; alt=&quot;Naive DFT vs FFT runtime on log-log axes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Empirical runtime comparison. The naive DFT grows quadratically while the recursive FFT grows close to linearly in N log N.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;sampling-nyquist-and-aliasing&quot;&gt;Sampling, Nyquist, and Aliasing&lt;&#x2F;h2&gt;
&lt;p&gt;A few practical points are worth making explicit, because they trip people up in real audio code.&lt;&#x2F;p&gt;
&lt;p&gt;The highest frequency the DFT can represent is the Nyquist frequency $f_s &#x2F; 2$. At sample rate $8000$ Hz, that is $4000$ Hz. Any signal component above that frequency does not simply vanish. It aliases down into a lower bin and corrupts the spectrum. That is why audio is low-pass filtered before sampling.&lt;&#x2F;p&gt;
&lt;p&gt;For real-valued inputs, the spectrum is conjugate-symmetric: $X_{N-k} = \overline{X_k}$. So the second half of the DFT output carries no new information. In practice we only plot bins $0$ through $N&#x2F;2$.&lt;&#x2F;p&gt;
&lt;p&gt;The frequency resolution of the DFT is $f_s &#x2F; N$. Longer windows give finer frequency resolution but worse time resolution. This trade-off is exactly why short-time Fourier transforms and wavelets exist.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;windowing-and-spectral-leakage&quot;&gt;Windowing and Spectral Leakage&lt;&#x2F;h2&gt;
&lt;p&gt;If the signal frequency does not land exactly on a DFT bin, its energy spreads into neighboring bins. This is called spectral leakage, and it is a property of the DFT, not a bug. Leakage is reduced by multiplying the signal by a smooth &lt;strong&gt;window function&lt;&#x2F;strong&gt; before the transform:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;window&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;hanning&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;N&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x_windowed&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; window&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A Hann window tapers the signal smoothly to zero at both ends, which suppresses the discontinuity that causes leakage. The trade-off is a slight widening of each spectral peak. For most audio applications this is a good deal.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;real-input-a-slightly-faster-variant&quot;&gt;Real Input: A Slightly Faster Variant&lt;&#x2F;h2&gt;
&lt;p&gt;When the input is real, &lt;code&gt;numpy.fft.rfft&lt;&#x2F;code&gt; returns only the non-redundant half of the spectrum and is about twice as fast as the full complex FFT. In from-scratch code the same idea can be implemented by exploiting the conjugate symmetry directly. For production use, &lt;code&gt;numpy.fft.rfft&lt;&#x2F;code&gt; or any mature FFT library (FFTW, PocketFFT, cuFFT) is almost always the right choice. The recursive implementation above is for understanding, not for speed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-big-picture&quot;&gt;The Big Picture&lt;&#x2F;h2&gt;
&lt;p&gt;At this point the whole thing should feel like one continuous argument, not three unrelated algorithms.&lt;&#x2F;p&gt;
&lt;p&gt;We started with a sound wave in the time domain and asked which frequencies it contains. Sampling gave us a finite sequence $x_n$. Projecting that sequence onto a basis of complex exponentials gave the DFT:&lt;&#x2F;p&gt;
&lt;p&gt;$$
X_k = \sum_{n=0}^{N-1} x_n , e^{-i 2\pi k n &#x2F; N}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The DFT is a linear map, so it can be written as a matrix product $X = F_N x$. Computed naively, that is $O(N^2)$.&lt;&#x2F;p&gt;
&lt;p&gt;We then exploited the structure of $F_N$. The roots of unity satisfy a recursion, so the $N$-point DFT splits into two $N&#x2F;2$-point DFTs combined by $N&#x2F;2$ butterflies. That gives the recurrence $T(N) = 2T(N&#x2F;2) + O(N)$ and the complexity $O(N \log N)$.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, we tested the whole pipeline on a synthetic sound wave and recovered the three input tones as three clean peaks in the magnitude spectrum.&lt;&#x2F;p&gt;
&lt;p&gt;So in the end, the DFT is the definition and the FFT is the algorithm. The DFT says what we want. The FFT says how to get it quickly. The exponential keeps showing up for the same reason it does in logistic and softmax regression: it is the right basis when the underlying object has multiplicative structure. Roots of unity are multiplicative. Splitting them in half, again and again, is exactly what turns $N^2$ into $N \log N$.&lt;&#x2F;p&gt;
&lt;p&gt;If I keep going with this, the next stops are the short-time Fourier transform and spectrograms, and after that the jump from Fourier bases to wavelets. Corrections and better examples are welcome in the comments.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Softmax Regression Explained from Logistic Regression</title>
        <published>2026-04-19T00:00:00+00:00</published>
        <updated>2026-04-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/softmax-regression/"/>
        <id>https://jienweng.github.io/notes/softmax-regression/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/softmax-regression/">&lt;p&gt;Softmax regression is what logistic regression grows into when there are more than two classes, and logistic regression itself only really makes sense once you understand odds. So instead of starting at the softmax formula, this note starts all the way back at odds and walks forward:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{probability} \to \text{odds} \to \text{log-odds} \to \text{logistic regression} \to \text{softmax regression}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;By the end, the exponential should not feel like a trick anymore. It is just the machine that turns unrestricted linear scores into positive ratios.&lt;&#x2F;p&gt;
&lt;p&gt;If you want the full runnable walkthrough, the companion notebook lives on Kaggle:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.kaggle.com&#x2F;code&#x2F;lyhatt&#x2F;softmax-from-logistic-regression&quot;&gt;https:&#x2F;&#x2F;www.kaggle.com&#x2F;code&#x2F;lyhatt&#x2F;softmax-from-logistic-regression&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That notebook was assisted by AI tools. This note is the written version.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;start-with-probability-then-ask-for-more-structure&quot;&gt;Start with Probability, Then Ask for More Structure&lt;&#x2F;h2&gt;
&lt;p&gt;Suppose we have a binary outcome. Let&lt;&#x2F;p&gt;
&lt;p&gt;$$
p = P(y=1 \mid x)
$$&lt;&#x2F;p&gt;
&lt;p&gt;be the probability that the positive class occurs given the input $x$.&lt;&#x2F;p&gt;
&lt;p&gt;Probability is easy to interpret, but it is not yet the best thing to model. A slightly more structured object is the &lt;strong&gt;odds&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\text{odds} = \frac{p}{1-p}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;The odds tell us how much more likely the positive event is than the negative one.&lt;&#x2F;p&gt;
&lt;p&gt;Some quick examples help. If $p = 0.5$, then the odds are $1$. If $p = 0.8$, then the odds are $\frac{0.8}{0.2} = 4$. If $p = 0.2$, then the odds are $\frac{0.2}{0.8} = 0.25$. So odds greater than $1$ favor the positive class, odds less than $1$ favor the negative class, and odds equal to $1$ mean the two classes are balanced.&lt;&#x2F;p&gt;
&lt;p&gt;This is already useful, but there is a problem. Odds are always positive, so they live on $(0,\infty)$. A plain linear model&lt;&#x2F;p&gt;
&lt;p&gt;$$
w^T x + b
$$&lt;&#x2F;p&gt;
&lt;p&gt;lives on the whole real line, from $-\infty$ to $\infty$. Those two spaces do not match well. If we want a linear model, odds are the wrong coordinates.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-core-move-take-logarithms&quot;&gt;The Core Move: Take Logarithms&lt;&#x2F;h2&gt;
&lt;p&gt;To fix this, we take the logarithm of the odds:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\log \frac{p}{1-p}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This quantity is the &lt;strong&gt;log-odds&lt;&#x2F;strong&gt;, also called the &lt;strong&gt;logit&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Why is this useful? Because odds are constrained to be positive, but log-odds can be any real number. A logit of $0$ corresponds to $p=0.5$, positive logits correspond to probabilities above $0.5$, and negative logits correspond to probabilities below $0.5$. This is the key move. The logarithm converts a positive ratio into a quantity that can now range over the whole real line. That means a linear model suddenly fits naturally:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\log \frac{p}{1-p} = w^T x + b.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is logistic regression in its cleanest form. The model says: not that the probability is linear, but that the &lt;strong&gt;log-odds&lt;&#x2F;strong&gt; are linear.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;now-solve-for-the-probability&quot;&gt;Now Solve for the Probability&lt;&#x2F;h2&gt;
&lt;p&gt;Now let us solve for $p$ carefully. Start with&lt;&#x2F;p&gt;
&lt;p&gt;$$
\log \frac{p}{1-p} = w^T x + b.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Exponentiate both sides:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\frac{p}{1-p} = e^{w^T x + b}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is where the exponential enters the story, and it is doing real mathematical work. It appears because it is the inverse of the logarithm, because it guarantees the odds stay positive, and because it converts an unrestricted real-valued score into a valid positive ratio.&lt;&#x2F;p&gt;
&lt;p&gt;Now multiply both sides by $(1-p)$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p = (1-p)e^{w^T x + b}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Expand the right-hand side:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p = e^{w^T x + b} - pe^{w^T x + b}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Collect the $p$ terms:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p + pe^{w^T x + b} = e^{w^T x + b}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Factor out $p$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p\left(1 + e^{w^T x + b}\right) = e^{w^T x + b}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;So&lt;&#x2F;p&gt;
&lt;p&gt;$$
p = \frac{e^{w^T x + b}}{1 + e^{w^T x + b}}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Divide numerator and denominator by $e^{w^T x + b}$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p = \frac{1}{1 + e^{-(w^T x + b)}}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is the sigmoid form of logistic regression:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p = \sigma(w^T x + b).
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is the full derivation. The sigmoid is not a decorative choice. It is what falls out when a linear model is placed on the log-odds.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-the-sigmoid-is-really-doing&quot;&gt;What the Sigmoid Is Really Doing&lt;&#x2F;h2&gt;
&lt;p&gt;The sigmoid takes a real-valued score and maps it to a number between $0$ and $1$:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\sigma(z) = \frac{1}{1+e^{-z}}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Here $z = w^T x + b$ is often called the &lt;strong&gt;logit&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The interpretation is immediate once you remember that $z$ is a logit. If $z \gg 0$, then $\sigma(z)$ is close to $1$. If $z = 0$, then $\sigma(z)=0.5$. If $z \ll 0$, then $\sigma(z)$ is close to $0$. So the sigmoid is the map from linear evidence to probability. That is why logistic regression has the familiar S-curve.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;logistic_sigmoid_curve.png&quot; alt=&quot;Sigmoid function&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;The sigmoid curve appears after solving the log-odds equation for the probability.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;the-model-needs-a-learning-signal&quot;&gt;The Model Needs a Learning Signal&lt;&#x2F;h2&gt;
&lt;p&gt;Once the model outputs probabilities, the next question is: what objective should it optimize? Logistic regression uses the Bernoulli likelihood. For one example with label $y \in {0,1}$ and predicted probability $\hat{y}$,&lt;&#x2F;p&gt;
&lt;p&gt;$$
P(y \mid x) = \hat{y}^y(1-\hat{y})^{1-y}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Taking logs gives the log-likelihood:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\log P(y \mid x) = y \log \hat{y} + (1-y)\log(1-\hat{y}).
$$&lt;&#x2F;p&gt;
&lt;p&gt;For $m$ observations, the negative average log-likelihood becomes the binary cross-entropy:&lt;&#x2F;p&gt;
&lt;p&gt;$$
L = -\frac{1}{m} \sum_{i=1}^m \left[y^{(i)} \log \hat{y}^{(i)} + (1-y^{(i)})\log(1-\hat{y}^{(i)})\right].
$$&lt;&#x2F;p&gt;
&lt;p&gt;This loss is worth staring at for a moment. It tells you exactly what the model fears: confident mistakes.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;logistic_bce_curve.png&quot; alt=&quot;Binary cross entropy curves&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Binary cross-entropy for the two possible true labels.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;That is the right behavior for a probabilistic model. If it is unsure, the penalty is moderate. If it is confidently wrong, the penalty should explode.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-optimization-is-still-just-gradient-descent&quot;&gt;The Optimization Is Still Just Gradient Descent&lt;&#x2F;h2&gt;
&lt;p&gt;Once we define the loss, the training rule is still gradient descent. The gradients are&lt;&#x2F;p&gt;
&lt;p&gt;$$
\nabla_w L = \frac{1}{m}X^T(\hat{y}-y),
\qquad
\nabla_b L = \frac{1}{m}\sum_{i=1}^{m}(\hat{y}^{(i)} - y^{(i)}).
$$&lt;&#x2F;p&gt;
&lt;p&gt;So the update step is&lt;&#x2F;p&gt;
&lt;p&gt;$$
w \leftarrow w - \alpha \nabla_w L,
\qquad
b \leftarrow b - \alpha \nabla_b L.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is worth emphasizing. Logistic regression sounds like a special model class, but algorithmically it is still simple: define probabilities, define a loss, differentiate, and descend.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-real-binary-example&quot;&gt;A Real Binary Example&lt;&#x2F;h2&gt;
&lt;p&gt;To make the binary story concrete, we use the Breast Cancer Wisconsin dataset. It has 569 samples, 30 numerical features, and two target classes: &lt;code&gt;0&lt;&#x2F;code&gt; for malignant and &lt;code&gt;1&lt;&#x2F;code&gt; for benign.&lt;&#x2F;p&gt;
&lt;p&gt;Before fitting anything, it helps to get a feel for the data itself.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;breast_cancer_overview.png&quot; alt=&quot;Breast Cancer dataset overview&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Class balance and a two-feature raw view of the Breast Cancer dataset.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;This is just the intuition view. The real training run uses all standardized features, not only the two shown here.&lt;&#x2F;p&gt;
&lt;p&gt;Below is a compact from-scratch implementation of logistic regression with gradient descent:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; sigmoid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;z&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;z&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; binary_cross_entropy&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;y_true&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; y_prob&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    eps&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1e-12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    y_prob&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;clip&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_prob&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; eps&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; eps&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mean&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_true&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_prob&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; y_true&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; y_prob&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; train_binary_logistic&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; y&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; epochs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1500&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    n_samples&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; n_features&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;shape&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    weights&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;zeros&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_features&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    bias&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    history&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;epochs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        logits&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; @&lt;&#x2F;span&gt;&lt;span&gt; weights&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; bias&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        probs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; sigmoid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;logits&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        error&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; probs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        grad_w&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;T&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; @&lt;&#x2F;span&gt;&lt;span&gt; error&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; n_samples&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        grad_b&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mean&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;error&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        weights&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -=&lt;&#x2F;span&gt;&lt;span&gt; learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; grad_w&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        bias&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -=&lt;&#x2F;span&gt;&lt;span&gt; learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; grad_b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        history&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;binary_cross_entropy&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; probs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; weights&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; bias&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;array&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;history&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The training loss falls in the way we expect:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;logistic_loss_curve.png&quot; alt=&quot;Logistic regression loss curve&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Training loss for from-scratch logistic regression.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;If we restrict ourselves to two features, we can also draw the probability surface directly:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;logistic_decision_boundary.png&quot; alt=&quot;Logistic regression decision boundary&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;A two-feature probability surface for logistic regression.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;That is enough for the binary story. The point is not to pile up diagnostics. The point is to see that logistic regression turns a linear score into a probability, and then learns that mapping through gradient descent.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-jump-to-many-classes&quot;&gt;The Jump to Many Classes&lt;&#x2F;h2&gt;
&lt;p&gt;Now comes the real question: what changes when there are not two classes, but many?&lt;&#x2F;p&gt;
&lt;p&gt;In binary logistic regression, we can think of the model as comparing two outcomes. The odds are&lt;&#x2F;p&gt;
&lt;p&gt;$$
\frac{p}{1-p}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;That ratio is already a comparison between classes. Softmax keeps the same spirit, but scales it from two classes to $K$ classes.&lt;&#x2F;p&gt;
&lt;p&gt;One useful way to think about this is to stop thinking in terms of labels first and think in terms of &lt;strong&gt;score comparisons&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Imagine an image classifier. A certain image might look a bit like a car, somewhat like a truck, and definitely not like a frog. In that situation, the model does not begin with probabilities. It begins with raw scores. Maybe the car score is high, the truck score is also fairly high, and the frog score is very low. That is already a meaningful picture. &quot;Car versus truck&quot; is a hard distinction because those classes can share visual structure. &quot;Car versus frog&quot; is easier because the scores should separate much more aggressively.&lt;&#x2F;p&gt;
&lt;p&gt;Softmax takes exactly this kind of score vector and says: fine, now turn those relative preferences into a probability distribution.&lt;&#x2F;p&gt;
&lt;p&gt;Suppose each class $k$ gets its own score&lt;&#x2F;p&gt;
&lt;p&gt;$$
z_k.
$$&lt;&#x2F;p&gt;
&lt;p&gt;If we want a multiclass probability model, the outputs should satisfy three requirements. Each probability must be positive, all probabilities must sum to $1$, and a larger score should correspond to a larger probability. The exponential is the natural choice here because $e^{z_k}$ is always positive, because if $z_a &amp;gt; z_b$ then $e^{z_a} &amp;gt; e^{z_b}$, and because exponentials turn unrestricted scores into positive quantities that can be normalized.&lt;&#x2F;p&gt;
&lt;p&gt;So we first define unnormalized class weights:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\tilde{p}_k = e^{z_k}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;These are not probabilities yet, because they do not sum to $1$. So we normalize them:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p_k = \frac{e^{z_k}}{\sum_{j=1}^{K} e^{z_j}}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;That normalization step is softmax.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-exponentials-keep-showing-up&quot;&gt;Why Exponentials Keep Showing Up&lt;&#x2F;h2&gt;
&lt;p&gt;At this point the exponential should no longer feel mysterious.&lt;&#x2F;p&gt;
&lt;p&gt;For logistic regression, we start from log-odds, exponentiation recovers the odds, and the final algebra gives the sigmoid probability. For softmax regression, we start from a score for each class, exponentiation turns each score into a positive class weight, and normalization turns those positive weights into a probability distribution. It is the same structural tool in both models: take unrestricted scores, convert them into positive quantities, and only then interpret them probabilistically.&lt;&#x2F;p&gt;
&lt;p&gt;There is also an intuitive reason practitioners like this view. A difference in score should matter more when it is decisive and less when two classes are nearly tied. Exponentials amplify those differences in a smooth way. If &quot;car&quot; scores only slightly above &quot;truck&quot;, the probabilities stay competitive. If &quot;car&quot; scores far above &quot;frog&quot;, the probability mass shifts much more aggressively.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;softmax-actually-contains-logistic-regression&quot;&gt;Softmax Actually Contains Logistic Regression&lt;&#x2F;h2&gt;
&lt;p&gt;Softmax is the multiclass extension of logistic regression, and we can show that mathematically.&lt;&#x2F;p&gt;
&lt;p&gt;Take the two-class case with scores $z_0$ and $z_1$. Then softmax gives&lt;&#x2F;p&gt;
&lt;p&gt;$$
p_1 = \frac{e^{z_1}}{e^{z_0} + e^{z_1}}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Factor out $e^{z_1}$ in the denominator:&lt;&#x2F;p&gt;
&lt;p&gt;$$
p_1 = \frac{e^{z_1}}{e^{z_1}\left(e^{z_0-z_1}+1\right)}
= \frac{1}{1 + e^{z_0-z_1}}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;Equivalently,&lt;&#x2F;p&gt;
&lt;p&gt;$$
p_1 = \frac{1}{1 + e^{-(z_1-z_0)}}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is exactly a sigmoid in the score difference $z_1-z_0$.&lt;&#x2F;p&gt;
&lt;p&gt;That is the bridge. Logistic regression is the binary special case, and softmax regression is the multiclass generalization.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-multiclass-loss-is-the-same-idea-again&quot;&gt;The Multiclass Loss Is the Same Idea Again&lt;&#x2F;h2&gt;
&lt;p&gt;In the multiclass setting, the true label is represented as a one-hot vector. If $Y$ is the one-hot target matrix and $P$ is the predicted softmax probability matrix, then the multiclass cross-entropy is&lt;&#x2F;p&gt;
&lt;p&gt;$$
L = -\frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{K} y_k^{(i)} \log p_k^{(i)}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is the same negative log-likelihood idea again, just written for many classes instead of two.&lt;&#x2F;p&gt;
&lt;p&gt;The gradient keeps a remarkably clean form:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\nabla_W L = \frac{1}{m}X^T(P-Y),
\qquad
\nabla_b L = \frac{1}{m}\sum_{i=1}^{m}(P^{(i)} - Y^{(i)}).
$$&lt;&#x2F;p&gt;
&lt;p&gt;So the optimization rule is still gradient descent:&lt;&#x2F;p&gt;
&lt;p&gt;$$
W \leftarrow W - \alpha \nabla_W L,
\qquad
b \leftarrow b - \alpha \nabla_b L.
$$&lt;&#x2F;p&gt;
&lt;p&gt;So the whole picture survives the transition almost unchanged. The binary weight vector becomes a class-wise weight matrix, and everything else follows.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-real-multiclass-example&quot;&gt;A Real Multiclass Example&lt;&#x2F;h2&gt;
&lt;p&gt;For the multiclass example, we use the Digits dataset. This is much more appropriate for softmax because it contains ten classes instead of two.&lt;&#x2F;p&gt;
&lt;p&gt;Each digit image is an $8 \times 8$ grayscale grid, flattened into a 64-dimensional vector.&lt;&#x2F;p&gt;
&lt;p&gt;Before fitting the model, it is worth seeing both raw samples and class averages:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;digits_overview.png&quot; alt=&quot;Digits dataset overview&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Raw digits samples and average digit images by class.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;Here is a compact from-scratch softmax regression implementation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; softmax&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;logits&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    shifted&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; logits&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;logits&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; axis&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; keepdims&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    exp_logits&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;shifted&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; exp_logits&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;exp_logits&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; axis&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; keepdims&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; multiclass_cross_entropy&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;y_one_hot&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; probs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    eps&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1e-12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    probs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;clip&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;probs&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; eps&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; eps&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mean&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_one_hot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;probs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; axis&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; train_softmax_regression&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;X&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; y&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; epochs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;3000&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    n_samples&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; n_features&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;shape&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    n_classes&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; len&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;unique&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    weights&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;zeros&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_features&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; n_classes&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    bias&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;zeros&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_classes&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    y_one_hot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;eye&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n_classes&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;y&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    history&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;epochs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        logits&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; @&lt;&#x2F;span&gt;&lt;span&gt; weights&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; bias&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        probs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; softmax&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;logits&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        error&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; probs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; y_one_hot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        grad_w&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; X&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;T&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; @&lt;&#x2F;span&gt;&lt;span&gt; error&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; n_samples&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        grad_b&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mean&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;error&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; axis&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        weights&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -=&lt;&#x2F;span&gt;&lt;span&gt; learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; grad_w&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        bias&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -=&lt;&#x2F;span&gt;&lt;span&gt; learning_rate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; grad_b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        history&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;multiclass_cross_entropy&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;y_one_hot&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; probs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; weights&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; bias&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;array&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;history&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;how-to-visualize-softmax-honestly&quot;&gt;How to Visualize Softmax Honestly&lt;&#x2F;h2&gt;
&lt;p&gt;Sigmoid has a universal curve. Softmax does not.&lt;&#x2F;p&gt;
&lt;p&gt;So the honest softmax visualization is not a fake one-dimensional curve. It is a sample-level transformation: start from a real digit image, compute one raw logit per class, and then exponentiate and normalize into probabilities. That is exactly what the next figure shows:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;softmax_real_transformation.png&quot; alt=&quot;Softmax transformation on a real digit&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;A real digit mapped into logits and then into softmax probabilities.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;That is the mental model to keep: softmax takes a vector of scores and turns it into a probability distribution.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, softmax is less about drawing one magical curve and more about resolving competitions among classes. Some competitions are close, like car versus truck. Some are not, like car versus frog. The probability vector tells us how that competition ended for one specific input.&lt;&#x2F;p&gt;
&lt;p&gt;The training loss on Digits decreases as expected:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;notes&amp;#x2F;softmax-regression&amp;#x2F;softmax_loss_curve.png&quot; alt=&quot;Softmax loss curve&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Training loss for from-scratch softmax regression on Digits.&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;That is enough for the multiclass side as well. Once the reader sees logits become probabilities on a real sample, the central idea has already landed. The remaining plots are useful in a notebook, but they are not necessary in the main essay.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-few-practical-tradeoffs&quot;&gt;A Few Practical Tradeoffs&lt;&#x2F;h2&gt;
&lt;p&gt;This is also a good place to be honest about what logistic regression and softmax regression can and cannot do. They are simple, fast, interpretable, and they produce probabilities directly, which is often exactly what we want. They also work surprisingly well when the classes are close to linearly separable in feature space. But the tradeoff is clear: if the real boundary is highly nonlinear, these models will struggle, the quality of the features matters a great deal, and multiclass confusion often comes from genuinely similar classes rather than from a bug in the formula.&lt;&#x2F;p&gt;
&lt;p&gt;This is also where people usually start reaching for nonlinear models. In deep learning, nonlinear functions become popular precisely because they can build more complex decision boundaries than a single linear logit layer can express. They are often used as activation functions inside deeper networks, and that added nonlinearity is what lets the model represent more complicated structure. But that is really another topic. Here the point is to understand the clean linear baseline first.&lt;&#x2F;p&gt;
&lt;p&gt;That is why these models are such good teaching models and such good baselines. When they succeed, they do so for understandable reasons. When they fail, they usually fail in ways that teach us something about the data.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-big-picture&quot;&gt;The Big Picture&lt;&#x2F;h2&gt;
&lt;p&gt;At this point the whole thing should feel like one continuous argument, not three unrelated formulas.&lt;&#x2F;p&gt;
&lt;p&gt;We started with probability, then moved to odds:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\frac{p}{1-p}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;We took logarithms to obtain log-odds:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\log \frac{p}{1-p}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;We modeled the log-odds linearly and solved for the probability, which produced the sigmoid and logistic regression.&lt;&#x2F;p&gt;
&lt;p&gt;Then we asked what happens when there are many classes instead of two. The same need appears again: we must turn unrestricted scores into valid positive ratios, and then normalize them into probabilities. That requirement leads directly to the softmax formula.&lt;&#x2F;p&gt;
&lt;p&gt;So logistic regression is a model for binary log-odds, and softmax regression is the same idea with more classes. The exponential appears because it converts linear scores into positive ratios, and gradient descent is the engine underneath both. Learn them together; separately they both look more arbitrary than they are.&lt;&#x2F;p&gt;
&lt;p&gt;Natural continuations from here: cross-entropy from maximum likelihood, and the step from softmax regression to a neural network, which is just softmax with learned features. Corrections welcome in the comments.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Statistical Testing in Regression</title>
        <published>2026-02-20T00:00:00+00:00</published>
        <updated>2026-02-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-08-statistical-testing/"/>
        <id>https://jienweng.github.io/notes/regression-08-statistical-testing/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-08-statistical-testing/">&lt;p&gt;This note covers hypothesis testing for regression coefficients and model terms, with emphasis on what p-values can and cannot tell you. The practical problem is making defensible decisions under uncertainty without overclaiming. I focus on test setup, assumptions, and interpretation boundaries.&lt;&#x2F;p&gt;
&lt;p&gt;In the &lt;a href=&quot;&#x2F;posts&#x2F;regression-07-confidence-intervals&quot;&gt;previous post&lt;&#x2F;a&gt;, we constructed confidence intervals for regression coefficients. Confidence intervals and hypothesis tests are two sides of the same coin. In this post, we formalize the hypothesis testing framework for regression coefficients.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hypothesis-testing-for-individual-coefficients&quot;&gt;Hypothesis Testing for Individual Coefficients&lt;&#x2F;h2&gt;
&lt;p&gt;The most common test in regression asks whether a particular predictor has a significant linear effect on the response. For coefficient $\beta_j$, the hypotheses are:&lt;&#x2F;p&gt;
&lt;p&gt;$$H_0: \beta_j = 0 \quad \text{(the predictor has no effect)},$$
$$H_1: \beta_j \neq 0 \quad \text{(the predictor has an effect)}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This is a two-sided test. One-sided alternatives ($H_1: \beta_j &amp;gt; 0$ or $H_1: \beta_j &amp;lt; 0$) are also possible when the direction of the effect is of interest.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-t-test-statistic&quot;&gt;The t-Test Statistic&lt;&#x2F;h2&gt;
&lt;p&gt;Under $H_0: \beta_j = 0$, the test statistic is:&lt;&#x2F;p&gt;
&lt;p&gt;$$t = \frac{\hat{\beta}_j - 0}{SE(\hat{\beta}_j)} = \frac{\hat{\beta}_j}{SE(\hat{\beta}_j)}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Under the null hypothesis and the normality assumption, this statistic follows a t-distribution:&lt;&#x2F;p&gt;
&lt;p&gt;$$t \sim t_{n-p},$$&lt;&#x2F;p&gt;
&lt;p&gt;where $n - p$ is the residual degrees of freedom.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;for-simple-linear-regression&quot;&gt;For Simple Linear Regression&lt;&#x2F;h3&gt;
&lt;p&gt;Testing $H_0: \beta_1 = 0$ (the slope is zero):&lt;&#x2F;p&gt;
&lt;p&gt;$$t = \frac{\hat{\beta}_1}{SE(\hat{\beta}_1)} = \frac{\hat{\beta}_1}{\sqrt{MSE&#x2F;S_{xx}}} = \frac{S_{xy}&#x2F;S_{xx}}{\sqrt{MSE&#x2F;S_{xx}}} = \frac{S_{xy}}{\sqrt{MSE \cdot S_{xx}}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This statistic follows $t_{n-2}$ under $H_0$.&lt;&#x2F;p&gt;
&lt;p&gt;Testing $H_0: \beta_0 = 0$ (the intercept is zero):&lt;&#x2F;p&gt;
&lt;p&gt;$$t = \frac{\hat{\beta}_0}{SE(\hat{\beta}_0)} = \frac{\hat{\beta}_0}{\sqrt{MSE(1&#x2F;n + \bar{x}^2&#x2F;S_{xx})}}.$$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;for-multiple-regression&quot;&gt;For Multiple Regression&lt;&#x2F;h3&gt;
&lt;p&gt;Testing $H_0: \beta_i = 0$ for any coefficient $\beta_i$:&lt;&#x2F;p&gt;
&lt;p&gt;$$t = \frac{\hat{\beta}_i}{SE(\hat{\beta}_i)} = \frac{\hat{\beta}_i}{\sqrt{MSE \cdot C_{ii}}},$$&lt;&#x2F;p&gt;
&lt;p&gt;where $C_{ii}$ is the appropriate diagonal element of $(\mathbf{X}^T\mathbf{X})^{-1}$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;decision-rule&quot;&gt;Decision Rule&lt;&#x2F;h2&gt;
&lt;p&gt;For a significance level $\alpha$ (commonly $\alpha = 0.05$):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Reject $H_0$&lt;&#x2F;strong&gt; if $|t| &amp;gt; t_{\alpha&#x2F;2, n-p}$.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Fail to reject $H_0$&lt;&#x2F;strong&gt; if $|t| \leq t_{\alpha&#x2F;2, n-p}$.&lt;&#x2F;p&gt;
&lt;p&gt;Rejecting $H_0$ means we have sufficient evidence that $\beta_j \neq 0$, suggesting that the predictor $x_j$ has a statistically significant effect on $y$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;p-values&quot;&gt;P-Values&lt;&#x2F;h2&gt;
&lt;p&gt;The p-value provides a continuous measure of the evidence against $H_0$. For a two-sided test:&lt;&#x2F;p&gt;
&lt;p&gt;$$p\text{-value} = 2 \cdot P(T &amp;gt; |t_{\text{obs}}|),$$&lt;&#x2F;p&gt;
&lt;p&gt;where $T \sim t_{n-p}$ and $t_{\text{obs}}$ is the observed test statistic.&lt;&#x2F;p&gt;
&lt;p&gt;Interpretation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The p-value is the probability of observing a test statistic at least as extreme as the one computed, assuming $H_0$ is true.&lt;&#x2F;li&gt;
&lt;li&gt;A small p-value (typically $&amp;lt; 0.05$) provides evidence against $H_0$.&lt;&#x2F;li&gt;
&lt;li&gt;A large p-value does not prove $H_0$ is true; it simply means the data do not provide strong evidence against it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The decision rule using p-values is equivalent to using critical values: reject $H_0$ if $p\text{-value} &amp;lt; \alpha$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;testing-against-a-non-zero-value&quot;&gt;Testing Against a Non-Zero Value&lt;&#x2F;h2&gt;
&lt;p&gt;More generally, we can test $H_0: \beta_j = \beta_{j,0}$ for any hypothesized value $\beta_{j,0}$:&lt;&#x2F;p&gt;
&lt;p&gt;$$t = \frac{\hat{\beta}_j - \beta_{j,0}}{SE(\hat{\beta}_j)}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The test for $\beta_j = 0$ is simply the special case where $\beta_{j,0} = 0$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;connection-to-confidence-intervals&quot;&gt;Connection to Confidence Intervals&lt;&#x2F;h2&gt;
&lt;p&gt;There is a direct equivalence between hypothesis tests and confidence intervals:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reject&lt;&#x2F;strong&gt; $H_0: \beta_j = \beta_{j,0}$ at level $\alpha$ if and only if $\beta_{j,0}$ falls outside the $(1 - \alpha) \times 100%$ confidence interval for $\beta_j$.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Fail to reject&lt;&#x2F;strong&gt; $H_0$ if and only if $\beta_{j,0}$ falls inside the confidence interval.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This means that when we check whether a 95% confidence interval for $\beta_1$ contains zero, we are implicitly performing a t-test at the $\alpha = 0.05$ level.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;testing-the-correlation&quot;&gt;Testing the Correlation&lt;&#x2F;h2&gt;
&lt;p&gt;For simple linear regression, testing $H_0: \beta_1 = 0$ is equivalent to testing $H_0: \rho = 0$ (the population correlation is zero). This is because $\hat{\beta}_1 = r \cdot S_y&#x2F;S_x$, and $\hat{\beta}_1 = 0$ if and only if $r = 0$.&lt;&#x2F;p&gt;
&lt;p&gt;The test statistic can also be written in terms of $r$:&lt;&#x2F;p&gt;
&lt;p&gt;$$t = r\sqrt{\frac{n - 2}{1 - r^2}},$$&lt;&#x2F;p&gt;
&lt;p&gt;which follows $t_{n-2}$ under $H_0: \rho = 0$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;practical-vs-statistical-significance&quot;&gt;Practical vs. Statistical Significance&lt;&#x2F;h2&gt;
&lt;p&gt;A statistically significant result (small p-value) does not necessarily mean the result is practically important. With a large sample size, even a tiny effect can be statistically significant because the standard error shrinks with $n$.&lt;&#x2F;p&gt;
&lt;p&gt;Conversely, a non-significant result does not mean the effect is zero; it may reflect insufficient sample size (low statistical power).&lt;&#x2F;p&gt;
&lt;p&gt;When interpreting regression results, consider both:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Statistical significance&lt;&#x2F;strong&gt;: Is the p-value small enough to reject $H_0$?&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Practical significance&lt;&#x2F;strong&gt;: Is the estimated effect $\hat{\beta}_j$ large enough to matter in the context of the problem?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The confidence interval is particularly helpful here because it shows both the direction and the plausible range of the effect size.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;multiple-testing-considerations&quot;&gt;Multiple Testing Considerations&lt;&#x2F;h2&gt;
&lt;p&gt;When testing multiple coefficients in a regression model, each at level $\alpha$, the probability of at least one false rejection increases. If you test $k$ independent hypotheses at $\alpha = 0.05$, the family-wise error rate is approximately $1 - (1 - \alpha)^k$.&lt;&#x2F;p&gt;
&lt;p&gt;Common corrections include:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bonferroni correction&lt;&#x2F;strong&gt;: Test each hypothesis at $\alpha&#x2F;k$.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Holm-Bonferroni method&lt;&#x2F;strong&gt;: A sequential version that is less conservative.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;False Discovery Rate (FDR) control&lt;&#x2F;strong&gt;: Controls the expected proportion of false rejections.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In regression, the overall model significance is typically assessed using the F-test (covered in the next post), which tests all predictors simultaneously.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we covered hypothesis testing for regression coefficients:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The t-statistic $t = \hat{\beta}_j &#x2F; SE(\hat{\beta}_j)$ follows $t_{n-p}$ under $H_0: \beta_j = 0$.&lt;&#x2F;li&gt;
&lt;li&gt;Reject $H_0$ when $|t| &amp;gt; t_{\alpha&#x2F;2, n-p}$ or equivalently when $p\text{-value} &amp;lt; \alpha$.&lt;&#x2F;li&gt;
&lt;li&gt;For $\beta_1$ in SLR: $t = S_{xy}&#x2F;\sqrt{MSE \cdot S_{xx}}$ with $n - 2$ degrees of freedom.&lt;&#x2F;li&gt;
&lt;li&gt;Testing $\beta_1 = 0$ is equivalent to testing $\rho = 0$.&lt;&#x2F;li&gt;
&lt;li&gt;Confidence intervals and hypothesis tests provide equivalent information.&lt;&#x2F;li&gt;
&lt;li&gt;Always consider practical significance alongside statistical significance.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will examine the ANOVA framework, which tests the overall significance of the regression model using the F-test.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Confidence Intervals for Regression Coefficients</title>
        <published>2026-02-13T00:00:00+00:00</published>
        <updated>2026-02-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-07-confidence-intervals/"/>
        <id>https://jienweng.github.io/notes/regression-07-confidence-intervals/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-07-confidence-intervals/">&lt;p&gt;This note shows how confidence intervals for regression coefficients are constructed and how to interpret them correctly. The core issue is avoiding binary thinking around single-point estimates. The objective is to connect standard errors, critical values, and interval interpretation in a reproducible way.&lt;&#x2F;p&gt;
&lt;p&gt;In the &lt;a href=&quot;&#x2F;posts&#x2F;regression-06-standard-error&quot;&gt;previous post&lt;&#x2F;a&gt;, we derived the standard errors of the regression coefficients. Standard errors tell us about the precision of our estimates, but they do not directly give us a range of plausible values for the true parameters. Confidence intervals do exactly that.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-sampling-distribution-of-hat-beta-j&quot;&gt;The Sampling Distribution of $\hat{\beta}_j$&lt;&#x2F;h2&gt;
&lt;p&gt;Under the normality assumption $\varepsilon_i \sim N(0, \sigma^2)$, the OLS estimators are also normally distributed:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_j \sim N\left(\beta_j, \sigma^2 C_{jj}\right),$$&lt;&#x2F;p&gt;
&lt;p&gt;where $C_{jj}$ is the appropriate diagonal element of $(\mathbf{X}^T\mathbf{X})^{-1}$.&lt;&#x2F;p&gt;
&lt;p&gt;Standardizing gives:&lt;&#x2F;p&gt;
&lt;p&gt;$$\frac{\hat{\beta}_j - \beta_j}{\sigma\sqrt{C_{jj}}} \sim N(0, 1).$$&lt;&#x2F;p&gt;
&lt;p&gt;However, $\sigma^2$ is unknown and we replace it with $MSE$. This introduces additional uncertainty, and the resulting distribution changes from a standard normal to a t-distribution:&lt;&#x2F;p&gt;
&lt;p&gt;$$\frac{\hat{\beta}_j - \beta_j}{SE(\hat{\beta}_j)} \sim t_{n - p},$$&lt;&#x2F;p&gt;
&lt;p&gt;where $SE(\hat{\beta}_j) = \sqrt{MSE \cdot C_{jj}}$ and $n - p$ is the degrees of freedom.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;confidence-interval-formula&quot;&gt;Confidence Interval Formula&lt;&#x2F;h2&gt;
&lt;p&gt;A $(1 - \alpha) \times 100%$ confidence interval for $\beta_j$ is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_j \pm t_{\alpha&#x2F;2,, n-p} \cdot SE(\hat{\beta}_j),$$&lt;&#x2F;p&gt;
&lt;p&gt;where $t_{\alpha&#x2F;2, n-p}$ is the critical value from the t-distribution with $n - p$ degrees of freedom such that $P(|T| &amp;gt; t_{\alpha&#x2F;2, n-p}) = \alpha$.&lt;&#x2F;p&gt;
&lt;p&gt;Equivalently, the interval is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\left[\hat{\beta}_j - t_{\alpha&#x2F;2,, n-p} \cdot SE(\hat{\beta}_j), \quad \hat{\beta}_j + t_{\alpha&#x2F;2,, n-p} \cdot SE(\hat{\beta}_j)\right].$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;confidence-interval-for-beta-1-simple-linear-regression&quot;&gt;Confidence Interval for $\beta_1$ (Simple Linear Regression)&lt;&#x2F;h2&gt;
&lt;p&gt;For the slope in simple linear regression, $n - p = n - 2$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_1 \pm t_{\alpha&#x2F;2,, n-2} \cdot SE(\hat{\beta}_1),$$&lt;&#x2F;p&gt;
&lt;p&gt;where:&lt;&#x2F;p&gt;
&lt;p&gt;$$SE(\hat{\beta}_1) = \sqrt{\frac{MSE}{S_{xx}}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Expanding fully:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_1 \pm t_{\alpha&#x2F;2,, n-2} \cdot \sqrt{\frac{SSE}{(n-2) \cdot S_{xx}}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This interval is centered at the OLS estimate $\hat{\beta}_1$ and its width depends on three factors:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Confidence level&lt;&#x2F;strong&gt; ($1 - \alpha$): Higher confidence means a wider interval (larger $t_{\alpha&#x2F;2}$).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Residual variability&lt;&#x2F;strong&gt; ($MSE$): More noise means a wider interval.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Predictor variability&lt;&#x2F;strong&gt; ($S_{xx}$): More spread in $x$ means a narrower interval.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;confidence-interval-for-beta-0-simple-linear-regression&quot;&gt;Confidence Interval for $\beta_0$ (Simple Linear Regression)&lt;&#x2F;h2&gt;
&lt;p&gt;For the intercept:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_0 \pm t_{\alpha&#x2F;2,, n-2} \cdot SE(\hat{\beta}_0),$$&lt;&#x2F;p&gt;
&lt;p&gt;where:&lt;&#x2F;p&gt;
&lt;p&gt;$$SE(\hat{\beta}_0) = \sqrt{MSE\left(\frac{1}{n} + \frac{\bar{x}^2}{S_{xx}}\right)}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The intercept&#x27;s confidence interval is typically wider than the slope&#x27;s because the variance of $\hat{\beta}_0$ includes an extra term involving $\bar{x}^2&#x2F;S_{xx}$. When $\bar{x}$ is far from zero, the intercept estimate becomes less precise.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;general-confidence-interval-for-beta-i-multiple-regression&quot;&gt;General Confidence Interval for $\beta_i$ (Multiple Regression)&lt;&#x2F;h2&gt;
&lt;p&gt;For any coefficient $\beta_i$ in the multiple regression model:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_i \pm t_{\alpha&#x2F;2,, n-p} \cdot SE(\hat{\beta}_i),$$&lt;&#x2F;p&gt;
&lt;p&gt;where:&lt;&#x2F;p&gt;
&lt;p&gt;$$SE(\hat{\beta}_i) = \sqrt{MSE \cdot C_{ii}}$$&lt;&#x2F;p&gt;
&lt;p&gt;and $C_{ii}$ is the $(i+1)$-th diagonal element of $(\mathbf{X}^T\mathbf{X})^{-1}$.&lt;&#x2F;p&gt;
&lt;p&gt;The degrees of freedom are $n - p$, reflecting that $p$ parameters have been estimated. As more predictors are included (larger $p$), fewer degrees of freedom remain, and the critical value $t_{\alpha&#x2F;2, n-p}$ increases, widening the intervals.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;interpretation-of-confidence-intervals&quot;&gt;Interpretation of Confidence Intervals&lt;&#x2F;h2&gt;
&lt;p&gt;A 95% confidence interval for $\beta_1$ does not mean there is a 95% probability that $\beta_1$ lies within the interval. The true parameter $\beta_1$ is a fixed (though unknown) constant; it either lies in the interval or it does not.&lt;&#x2F;p&gt;
&lt;p&gt;The correct interpretation is: if we were to repeat the experiment many times and construct a 95% confidence interval each time, approximately 95% of those intervals would contain the true value $\beta_1$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-the-interval-contains-zero&quot;&gt;When the Interval Contains Zero&lt;&#x2F;h2&gt;
&lt;p&gt;If the confidence interval for $\beta_j$ includes zero, this suggests that the data are consistent with $\beta_j = 0$, meaning the predictor $x_j$ may not have a statistically significant linear effect on $y$ at the chosen confidence level.&lt;&#x2F;p&gt;
&lt;p&gt;Conversely, if the interval does not include zero, we have evidence that the predictor has a significant effect. This connection between confidence intervals and hypothesis testing is explored in the next post.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;confidence-interval-for-the-mean-response&quot;&gt;Confidence Interval for the Mean Response&lt;&#x2F;h2&gt;
&lt;p&gt;We can also construct a confidence interval for the mean response at a given value $x_0$. The estimated mean response is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{y}_0 = \hat{\beta}_0 + \hat{\beta}_1 x_0.$$&lt;&#x2F;p&gt;
&lt;p&gt;The variance of this estimate is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\text{Var}(\hat{y}_0) = \sigma^2\left(\frac{1}{n} + \frac{(x_0 - \bar{x})^2}{S_{xx}}\right).$$&lt;&#x2F;p&gt;
&lt;p&gt;The confidence interval for the mean response is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{y}_0 \pm t_{\alpha&#x2F;2,, n-2} \cdot \sqrt{MSE\left(\frac{1}{n} + \frac{(x_0 - \bar{x})^2}{S_{xx}}\right)}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Notice that this interval is narrowest when $x_0 = \bar{x}$ and grows wider as $x_0$ moves away from $\bar{x}$. This reflects the fact that we are most confident about predictions near the center of the data.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we constructed confidence intervals for regression coefficients:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The pivotal quantity $(\hat{\beta}_j - \beta_j)&#x2F;SE(\hat{\beta}_j) \sim t_{n-p}$ forms the basis for inference.&lt;&#x2F;li&gt;
&lt;li&gt;For $\beta_1$: $\hat{\beta}_1 \pm t_{\alpha&#x2F;2, n-2} \cdot \sqrt{MSE&#x2F;S_{xx}}$.&lt;&#x2F;li&gt;
&lt;li&gt;For $\beta_0$: $\hat{\beta}_0 \pm t_{\alpha&#x2F;2, n-2} \cdot \sqrt{MSE(1&#x2F;n + \bar{x}^2&#x2F;S_{xx})}$.&lt;&#x2F;li&gt;
&lt;li&gt;For any $\beta_i$ in multiple regression: $\hat{\beta}_i \pm t_{\alpha&#x2F;2, n-p} \cdot \sqrt{MSE \cdot C_{ii}}$.&lt;&#x2F;li&gt;
&lt;li&gt;Interval width depends on the confidence level, residual variability, predictor spread, and sample size.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will use the same t-distribution framework to perform formal hypothesis tests on the regression coefficients.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Standard Error in Regression</title>
        <published>2026-02-06T00:00:00+00:00</published>
        <updated>2026-02-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-06-standard-error/"/>
        <id>https://jienweng.github.io/notes/regression-06-standard-error/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-06-standard-error/">&lt;p&gt;This note explains standard error in regression as a measure of uncertainty in coefficient estimates, not model quality by itself. The practical question is how noisy your estimate is if the data-generating process were repeated. I connect residual variance, sample size, and confidence in coefficients.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;estimating-the-error-variance&quot;&gt;Estimating the Error Variance&lt;&#x2F;h2&gt;
&lt;p&gt;Under the linear model $y_i = \beta_0 + \beta_1 x_i + \varepsilon_i$ with $\varepsilon_i \sim N(0, \sigma^2)$, the error variance $\sigma^2$ is unknown and must be estimated from the data.&lt;&#x2F;p&gt;
&lt;p&gt;The natural estimate is based on the residuals. The Mean Squared Error (MSE) is defined as:&lt;&#x2F;p&gt;
&lt;p&gt;$$MSE = \frac{SSE}{n - 2} = \frac{\sum^n_{i=1}(y_i - \hat{y}_i)^2}{n - 2}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The denominator is $n - 2$ (not $n$) because we lose two degrees of freedom for estimating $\hat{\beta}_0$ and $\hat{\beta}_1$. This correction ensures that MSE is an unbiased estimator of $\sigma^2$:&lt;&#x2F;p&gt;
&lt;p&gt;$$E(MSE) = \sigma^2.$$&lt;&#x2F;p&gt;
&lt;p&gt;For multiple regression with $p$ parameters, the denominator becomes $n - p$:&lt;&#x2F;p&gt;
&lt;p&gt;$$MSE = \frac{SSE}{n - p}.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;variance-of-the-ols-estimators&quot;&gt;Variance of the OLS Estimators&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;simple-linear-regression&quot;&gt;Simple Linear Regression&lt;&#x2F;h3&gt;
&lt;p&gt;The OLS estimators are functions of the random variable $\mathbf{Y}$ (since $\mathbf{X}$ is treated as fixed). Their variances can be derived from the model assumptions.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Variance of $\hat{\beta}_1$:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Since $\hat{\beta}_1 = S_{xy}&#x2F;S_{xx} = \sum^n_{i=1}c_i y_i$ where $c_i = (x_i - \bar{x})&#x2F;S_{xx}$, and the $y_i$ are independent with variance $\sigma^2$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\text{Var}(\hat{\beta}_1) = \sum^n_{i=1}c_i^2 \text{Var}(y_i) = \sigma^2 \sum^n_{i=1}\frac{(x_i - \bar{x})^2}{S_{xx}^2} = \sigma^2 \cdot \frac{S_{xx}}{S_{xx}^2} = \frac{\sigma^2}{S_{xx}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Variance of $\hat{\beta}_0$:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Since $\hat{\beta}_0 = \bar{y} - \hat{\beta}_1\bar{x}$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\text{Var}(\hat{\beta}_0) = \text{Var}(\bar{y}) + \bar{x}^2 \text{Var}(\hat{\beta}_1) = \frac{\sigma^2}{n} + \bar{x}^2 \cdot \frac{\sigma^2}{S_{xx}} = \sigma^2\left(\frac{1}{n} + \frac{\bar{x}^2}{S_{xx}}\right).$$&lt;&#x2F;p&gt;
&lt;p&gt;Note that $\text{Var}(\hat{\beta}_0)$ depends on the mean of $x$: the further $\bar{x}$ is from zero, the larger the variance of the intercept estimate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;standard-errors&quot;&gt;Standard Errors&lt;&#x2F;h2&gt;
&lt;p&gt;The standard error of an estimator is the square root of its estimated variance, obtained by replacing $\sigma^2$ with MSE.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Standard error of $\hat{\beta}_1$:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;$$SE(\hat{\beta}_1) = \sqrt{\frac{MSE}{S_{xx}}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Standard error of $\hat{\beta}_0$:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;$$SE(\hat{\beta}_0) = \sqrt{MSE\left(\frac{1}{n} + \frac{\bar{x}^2}{S_{xx}}\right)}.$$&lt;&#x2F;p&gt;
&lt;p&gt;These formulas show that the precision of our estimates improves (standard errors decrease) when:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$MSE$ is small (the model fits the data well).&lt;&#x2F;li&gt;
&lt;li&gt;$S_{xx}$ is large (there is more spread in the $x$ values).&lt;&#x2F;li&gt;
&lt;li&gt;$n$ is large (we have more data).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;general-case-multiple-regression&quot;&gt;General Case: Multiple Regression&lt;&#x2F;h2&gt;
&lt;p&gt;For the multiple regression model $\mathbf{Y} = \mathbf{X}\boldsymbol{\beta} + \boldsymbol{\varepsilon}$, the variance-covariance matrix of $\hat{\boldsymbol{\beta}}$ is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\text{Var}(\hat{\boldsymbol{\beta}}) = \sigma^2(\mathbf{X}^T\mathbf{X})^{-1}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The estimated variance-covariance matrix is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\widehat{\text{Var}}(\hat{\boldsymbol{\beta}}) = MSE \cdot (\mathbf{X}^T\mathbf{X})^{-1}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Let $(\mathbf{X}^T\mathbf{X})^{-1} = \mathbf{C}$, and let $C_{jj}$ denote the $(j+1)$-th diagonal element of $\mathbf{C}$ (corresponding to $\hat{\beta}_j$). Then:&lt;&#x2F;p&gt;
&lt;p&gt;$$\text{Var}(\hat{\beta}_j) = \sigma^2 C_{jj},$$&lt;&#x2F;p&gt;
&lt;p&gt;$$SE(\hat{\beta}_j) = \sqrt{MSE \cdot C_{jj}}.$$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;verification-for-simple-linear-regression&quot;&gt;Verification for Simple Linear Regression&lt;&#x2F;h3&gt;
&lt;p&gt;For simple linear regression, the design matrix is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{X} = \begin{pmatrix} 1 &amp;amp; x_1 \\ 1 &amp;amp; x_2 \\ \vdots &amp;amp; \vdots \\ 1 &amp;amp; x_n \end{pmatrix}, \quad \mathbf{X}^T\mathbf{X} = \begin{pmatrix} n &amp;amp; \sum x_i \\ \sum x_i &amp;amp; \sum x_i^2 \end{pmatrix}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The inverse is:&lt;&#x2F;p&gt;
&lt;p&gt;$$(\mathbf{X}^T\mathbf{X})^{-1} = \frac{1}{n\sum x_i^2 - (\sum x_i)^2}\begin{pmatrix} \sum x_i^2 &amp;amp; -\sum x_i \\ -\sum x_i &amp;amp; n \end{pmatrix}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Since $n\sum x_i^2 - (\sum x_i)^2 = n \cdot S_{xx}$, the diagonal elements are:&lt;&#x2F;p&gt;
&lt;p&gt;$$C_{00} = \frac{\sum x_i^2}{n \cdot S_{xx}} = \frac{1}{n} + \frac{\bar{x}^2}{S_{xx}}, \quad C_{11} = \frac{n}{n \cdot S_{xx}} = \frac{1}{S_{xx}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;These give us:&lt;&#x2F;p&gt;
&lt;p&gt;$$\text{Var}(\hat{\beta}_0) = \sigma^2\left(\frac{1}{n} + \frac{\bar{x}^2}{S_{xx}}\right), \quad \text{Var}(\hat{\beta}_1) = \frac{\sigma^2}{S_{xx}},$$&lt;&#x2F;p&gt;
&lt;p&gt;which matches our earlier derivations.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;interpreting-the-standard-error&quot;&gt;Interpreting the Standard Error&lt;&#x2F;h2&gt;
&lt;p&gt;The standard error measures the typical size of the sampling variability in the estimated coefficient. If we were to repeatedly draw new samples of size $n$ from the same population and compute $\hat{\beta}_1$ each time, the standard deviation of these estimates would be approximately $SE(\hat{\beta}_1)$.&lt;&#x2F;p&gt;
&lt;p&gt;A small standard error relative to the coefficient estimate suggests that the estimate is precise. A large standard error suggests substantial uncertainty. We will use the standard error to construct confidence intervals (Post 7) and perform hypothesis tests (Post 8).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we derived the standard errors of the regression coefficients:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$MSE = SSE&#x2F;(n-2)$ estimates the error variance $\sigma^2$ for simple linear regression, or $MSE = SSE&#x2F;(n-p)$ for multiple regression.&lt;&#x2F;li&gt;
&lt;li&gt;$SE(\hat{\beta}_1) = \sqrt{MSE&#x2F;S_{xx}}$ measures the precision of the slope estimate.&lt;&#x2F;li&gt;
&lt;li&gt;$SE(\hat{\beta}_0) = \sqrt{MSE(1&#x2F;n + \bar{x}^2&#x2F;S_{xx})}$ measures the precision of the intercept estimate.&lt;&#x2F;li&gt;
&lt;li&gt;For multiple regression, $SE(\hat{\beta}_j) = \sqrt{MSE \cdot C_{jj}}$ where $C_{jj}$ is the $(j+1)$-th diagonal element of $(\mathbf{X}^T\mathbf{X})^{-1}$.&lt;&#x2F;li&gt;
&lt;li&gt;Precision improves with larger $S_{xx}$, smaller $MSE$, and larger $n$.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will use these standard errors to construct confidence intervals for $\beta_0$, $\beta_1$, and $\beta_i$.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>R-squared: SST, SSE, SSR and the Relationship with Correlation</title>
        <published>2026-01-30T00:00:00+00:00</published>
        <updated>2026-01-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-05-r-squared-and-correlation/"/>
        <id>https://jienweng.github.io/notes/regression-05-r-squared-and-correlation/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-05-r-squared-and-correlation/">&lt;p&gt;This note explains SST, SSR, SSE, and R-squared, then connects these quantities to correlation in the simple regression setting. The aim is to clarify what variance decomposition actually tells you about model fit. It also points out where high R-squared can still be misleading.&lt;&#x2F;p&gt;
&lt;p&gt;In the &lt;a href=&quot;&#x2F;posts&#x2F;regression-04-correlation&quot;&gt;previous post&lt;&#x2F;a&gt;, we introduced the Pearson correlation coefficient $r$. In this post, we define the coefficient of determination $R^2$ through the decomposition of total variability, and then prove that $R^2 = r^2$ for simple linear regression.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-sum-of-squares-decomposition&quot;&gt;The Sum of Squares Decomposition&lt;&#x2F;h2&gt;
&lt;p&gt;The total variability in the observed response values can be decomposed into two components: the variability explained by the regression model and the variability left unexplained (the residuals).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;total-sum-of-squares-sst&quot;&gt;Total Sum of Squares (SST)&lt;&#x2F;h3&gt;
&lt;p&gt;$$SST = \sum^n_{i=1}(y_i - \bar{y})^2 = S_{yy}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This measures the total variability of $y$ around its mean.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sum-of-squares-due-to-regression-ssr&quot;&gt;Sum of Squares due to Regression (SSR)&lt;&#x2F;h3&gt;
&lt;p&gt;$$SSR = \sum^n_{i=1}(\hat{y}_i - \bar{y})^2.$$&lt;&#x2F;p&gt;
&lt;p&gt;This measures the variability in $y$ that is explained by the regression model.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sum-of-squared-errors-sse&quot;&gt;Sum of Squared Errors (SSE)&lt;&#x2F;h3&gt;
&lt;p&gt;$$SSE = \sum^n_{i=1}(y_i - \hat{y}_i)^2.$$&lt;&#x2F;p&gt;
&lt;p&gt;This measures the variability in $y$ that remains unexplained.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-identity&quot;&gt;The Identity&lt;&#x2F;h3&gt;
&lt;p&gt;The fundamental decomposition is:&lt;&#x2F;p&gt;
&lt;p&gt;$$SST = SSR + SSE.$$&lt;&#x2F;p&gt;
&lt;p&gt;To see why this holds, write:&lt;&#x2F;p&gt;
&lt;p&gt;$$y_i - \bar{y} = (\hat{y}_i - \bar{y}) + (y_i - \hat{y}_i).$$&lt;&#x2F;p&gt;
&lt;p&gt;Squaring both sides and summing over all observations:&lt;&#x2F;p&gt;
&lt;p&gt;$$\sum_{i=1}^n (y_i - \bar{y})^2 = \sum_{i=1}^n (\hat{y}_i - \bar{y})^2 + \sum_{i=1}^n (y_i - \hat{y}_i)^2 + 2\sum_{i=1}^n (\hat{y}_i - \bar{y})(y_i - \hat{y}_i).$$&lt;&#x2F;p&gt;
&lt;p&gt;The cross term vanishes because of the properties of OLS residuals:&lt;&#x2F;p&gt;
&lt;p&gt;$$\sum_{i=1}^n (\hat{y}_i - \bar{y})(y_i - \hat{y}_i) = \sum_{i=1}^n (\hat{y}_i - \bar{y}),e_i = 0.$$&lt;&#x2F;p&gt;
&lt;p&gt;This holds because OLS residuals are orthogonal to the fitted values. Therefore:&lt;&#x2F;p&gt;
&lt;p&gt;$$SST = SSR + SSE.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-coefficient-of-determination-r-2&quot;&gt;The Coefficient of Determination $R^2$&lt;&#x2F;h2&gt;
&lt;p&gt;The coefficient of determination is defined as:&lt;&#x2F;p&gt;
&lt;p&gt;$$R^2 = \frac{SSR}{SST} = 1 - \frac{SSE}{SST}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Both expressions are equivalent because $SSR = SST - SSE$.&lt;&#x2F;p&gt;
&lt;p&gt;The value of $R^2$ represents the proportion of the total variability in $y$ that is explained by the regression model:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$R^2 = 1$: the model explains all the variability (all data points lie exactly on the fitted line).&lt;&#x2F;li&gt;
&lt;li&gt;$R^2 = 0$: the model explains none of the variability (the fitted line is just $\hat{y} = \bar{y}$).&lt;&#x2F;li&gt;
&lt;li&gt;$0 \leq R^2 \leq 1$: in general, $R^2$ lies between 0 and 1 for models with an intercept.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;proof-that-r-2-r-2-for-simple-linear-regression&quot;&gt;Proof that $R^2 = r^2$ for Simple Linear Regression&lt;&#x2F;h2&gt;
&lt;p&gt;We now prove that for simple linear regression, the coefficient of determination equals the square of the Pearson correlation coefficient.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-1-express-ssr-in-terms-of-s-xx-s-xy&quot;&gt;Step 1: Express SSR in terms of $S_{xx}$, $S_{xy}$&lt;&#x2F;h3&gt;
&lt;p&gt;For simple linear regression, the fitted values are:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{y}_i = \hat{\beta}_0 + \hat{\beta}_1 x_i = (\bar{y} - \hat{\beta}_1\bar{x}) + \hat{\beta}_1 x_i = \bar{y} + \hat{\beta}_1(x_i - \bar{x}).$$&lt;&#x2F;p&gt;
&lt;p&gt;Therefore:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{y}_i - \bar{y} = \hat{\beta}_1(x_i - \bar{x}).$$&lt;&#x2F;p&gt;
&lt;p&gt;Substituting into SSR:&lt;&#x2F;p&gt;
&lt;p&gt;$$SSR = \sum_{i=1}^n (\hat{y}_i - \bar{y})^2 = \hat{\beta}_1^2 \sum_{i=1}^n (x_i - \bar{x})^2 = \hat{\beta}_1^2 \cdot S_{xx}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Since $\hat{\beta}_1 = S_{xy}&#x2F;S_{xx}$:&lt;&#x2F;p&gt;
&lt;p&gt;$$SSR = \left(\frac{S_{xy}}{S_{xx}}\right)^2 S_{xx} = \frac{S_{xy}^2}{S_{xx}}.$$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-2-compute-r-2&quot;&gt;Step 2: Compute $R^2$&lt;&#x2F;h3&gt;
&lt;p&gt;$$R^2 = \frac{SSR}{SST} = \frac{S_{xy}^2 &#x2F; S_{xx}}{S_{yy}} = \frac{S_{xy}^2}{S_{xx} \cdot S_{yy}}.$$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-3-compare-with-r-2&quot;&gt;Step 3: Compare with $r^2$&lt;&#x2F;h3&gt;
&lt;p&gt;From the &lt;a href=&quot;&#x2F;posts&#x2F;regression-04-correlation&quot;&gt;previous post&lt;&#x2F;a&gt;, the correlation coefficient is:&lt;&#x2F;p&gt;
&lt;p&gt;$$r = \frac{S_{xy}}{\sqrt{S_{xx} \cdot S_{yy}}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Squaring both sides:&lt;&#x2F;p&gt;
&lt;p&gt;$$r^2 = \frac{S_{xy}^2}{S_{xx} \cdot S_{yy}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Therefore:&lt;&#x2F;p&gt;
&lt;p&gt;$$R^2 = r^2. \quad \blacksquare$$&lt;&#x2F;p&gt;
&lt;p&gt;This result is elegant and important. It tells us that for simple linear regression, the proportion of variance explained by the model is exactly the square of the correlation between $x$ and $y$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;alternative-proof-via-sse&quot;&gt;Alternative Proof via SSE&lt;&#x2F;h2&gt;
&lt;p&gt;We can also prove the result by expressing SSE in terms of $S_{xx}$, $S_{yy}$, $S_{xy}$.&lt;&#x2F;p&gt;
&lt;p&gt;Since $e_i = y_i - \hat{y}_i = y_i - \bar{y} - \hat{\beta}_1(x_i - \bar{x})$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\begin{aligned}
SSE &amp;amp;= \sum_{i=1}^n \big[(y_i - \bar{y}) - \hat{\beta}_1 (x_i - \bar{x})\big]^2 \\
&amp;amp;= \sum_{i=1}^n (y_i - \bar{y})^2 - 2\hat{\beta}_1 \sum_{i=1}^n (x_i - \bar{x})(y_i - \bar{y}) + \hat{\beta}_1^2 \sum_{i=1}^n (x_i - \bar{x})^2 \\
&amp;amp;= S_{yy} - 2\hat{\beta}_1 S_{xy} + \hat{\beta}_1^2 S_{xx}.
\end{aligned}$$&lt;&#x2F;p&gt;
&lt;p&gt;Substituting $\hat{\beta}_1 = S_{xy}&#x2F;S_{xx}$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\begin{aligned}
SSE &amp;amp;= S_{yy} - 2\cdot\frac{S_{xy}}{S_{xx}}\cdot S_{xy} + \frac{S_{xy}^2}{S_{xx}^2}\cdot S_{xx} \\
&amp;amp;= S_{yy} - \frac{2S_{xy}^2}{S_{xx}} + \frac{S_{xy}^2}{S_{xx}} \\
&amp;amp;= S_{yy} - \frac{S_{xy}^2}{S_{xx}}.
\end{aligned}$$&lt;&#x2F;p&gt;
&lt;p&gt;Therefore:&lt;&#x2F;p&gt;
&lt;p&gt;$$R^2 = 1 - \frac{SSE}{SST} = 1 - \frac{S_{yy} - S_{xy}^2&#x2F;S_{xx}}{S_{yy}} = \frac{S_{xy}^2}{S_{xx} \cdot S_{yy}} = r^2.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;adjusted-r-2-for-multiple-regression&quot;&gt;Adjusted $R^2$ for Multiple Regression&lt;&#x2F;h2&gt;
&lt;p&gt;For multiple regression with $p$ parameters, $R^2$ always increases (or stays the same) when additional predictors are added, even if those predictors have no real relationship with $y$. To account for this, the adjusted $R^2$ penalizes for the number of predictors:&lt;&#x2F;p&gt;
&lt;p&gt;$$R^2_{\text{adj}} = 1 - \frac{SSE&#x2F;(n - p)}{SST&#x2F;(n - 1)} = 1 - \frac{n - 1}{n - p}(1 - R^2).$$&lt;&#x2F;p&gt;
&lt;p&gt;Unlike $R^2$, the adjusted version can decrease when an uninformative predictor is added, making it more suitable for model comparison.&lt;&#x2F;p&gt;
&lt;p&gt;Note that for simple linear regression ($p = 2$), $R^2 = r^2$ still holds, and $R^2_{\text{adj}}$ simplifies accordingly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-a-high-r-squared-can-mislead&quot;&gt;Where a High R-squared Can Mislead&lt;&#x2F;h2&gt;
&lt;p&gt;Everything above describes what $R^2$ measures. It is equally important to be clear about what it does not measure.&lt;&#x2F;p&gt;
&lt;p&gt;First, $R^2$ quantifies fit, not correctness. It tells us how much of the variability in $y$ is captured by the fitted line, but it says nothing about whether a line was the right model to fit. A clearly nonlinear pattern can still produce a high $R^2$ if the linear component happens to be strong. Anscombe&#x27;s quartet is the classic reference here: four datasets with essentially identical $R^2$ (and identical fitted lines), one of which is a clean linear relationship, one a perfect parabola, and two dominated by single outlying points. The residual plot distinguishes them; $R^2$ does not.&lt;&#x2F;p&gt;
&lt;p&gt;Second, plain $R^2$ never decreases when a predictor is added. Since OLS minimizes SSE over a strictly larger space of models, SSE can only stay the same or shrink, so $R^2 = 1 - SSE&#x2F;SST$ can only stay the same or grow, even when the new predictor is pure noise. This is exactly the problem the adjusted $R^2$ above is designed to correct.&lt;&#x2F;p&gt;
&lt;p&gt;Third, a high $R^2$ is not evidence of causation, and it is not a measure of predictive performance on new data. It is computed on the same observations used to fit the model, so it says nothing about how the model generalizes out of sample.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we covered the sum of squares decomposition and the coefficient of determination:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$SST = SSR + SSE$ decomposes total variability into explained and unexplained components.&lt;&#x2F;li&gt;
&lt;li&gt;$R^2 = SSR&#x2F;SST = 1 - SSE&#x2F;SST$ measures the proportion of variance explained.&lt;&#x2F;li&gt;
&lt;li&gt;For simple linear regression, $R^2 = r^2 = S_{xy}^2&#x2F;(S_{xx} \cdot S_{yy})$, proven by expressing SSR in terms of $S_{xx}$ and $S_{xy}$.&lt;&#x2F;li&gt;
&lt;li&gt;Adjusted $R^2$ penalizes for model complexity in multiple regression.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will derive the standard errors of the regression coefficients $\hat{\beta}_0$ and $\hat{\beta}_1$ using $S_{xx}$ and MSE.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Correlation</title>
        <published>2026-01-23T00:00:00+00:00</published>
        <updated>2026-01-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-04-correlation/"/>
        <id>https://jienweng.github.io/notes/regression-04-correlation/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-04-correlation/">&lt;p&gt;This note defines correlation clearly, explains how to compute it, and separates association from causation. The key issue is that correlation is often over-interpreted as evidence of mechanism. The goal is to make correlation a diagnostic tool, not a conclusion.&lt;&#x2F;p&gt;
&lt;p&gt;In the previous posts, we built the regression framework using $S_{xx}$ and $S_{xy}$ to derive the OLS estimators. Before we move to deeper inferential topics, it is essential to formalize a measure of the strength of the linear association between two variables. This measure is the Pearson correlation coefficient.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;recap-of-key-notation&quot;&gt;Recap of Key Notation&lt;&#x2F;h2&gt;
&lt;p&gt;In &lt;a href=&quot;&#x2F;posts&#x2F;regression-01-simple-linear-regression&quot;&gt;Simple Linear Regression&lt;&#x2F;a&gt;, we introduced:&lt;&#x2F;p&gt;
&lt;p&gt;$$S_{xx} = \sum^n_{i=1}(x_i - \bar{x})^2,$$&lt;&#x2F;p&gt;
&lt;p&gt;$$S_{xy} = \sum^n_{i=1}(x_i - \bar{x})(y_i - \bar{y}).$$&lt;&#x2F;p&gt;
&lt;p&gt;We now introduce the remaining quantity:&lt;&#x2F;p&gt;
&lt;p&gt;$$S_{yy} = \sum^n_{i=1}(y_i - \bar{y})^2.$$&lt;&#x2F;p&gt;
&lt;p&gt;These three summary statistics capture the variability of $x$, the variability of $y$, and their joint variability, respectively.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;sample-standard-deviations&quot;&gt;Sample Standard Deviations&lt;&#x2F;h2&gt;
&lt;p&gt;The sample standard deviations are defined as:&lt;&#x2F;p&gt;
&lt;p&gt;$$S_x = \sqrt{\frac{S_{xx}}{n - 1}} = \sqrt{\frac{1}{n-1}\sum^n_{i=1}(x_i - \bar{x})^2},$$&lt;&#x2F;p&gt;
&lt;p&gt;$$S_y = \sqrt{\frac{S_{yy}}{n - 1}} = \sqrt{\frac{1}{n-1}\sum^n_{i=1}(y_i - \bar{y})^2}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Note that $S_x$ and $S_y$ are the standard deviations using the $n - 1$ denominator (Bessel&#x27;s correction), which gives unbiased estimates of the population standard deviations $\sigma_x$ and $\sigma_y$.&lt;&#x2F;p&gt;
&lt;p&gt;The sample variances are simply:&lt;&#x2F;p&gt;
&lt;p&gt;$$S_x^2 = \frac{S_{xx}}{n-1}, \quad S_y^2 = \frac{S_{yy}}{n-1}.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-pearson-correlation-coefficient&quot;&gt;The Pearson Correlation Coefficient&lt;&#x2F;h2&gt;
&lt;p&gt;The sample Pearson correlation coefficient is defined as:&lt;&#x2F;p&gt;
&lt;p&gt;$$r = \frac{S_{xy}}{\sqrt{S_{xx} \cdot S_{yy}}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Equivalently, using the standard deviations:&lt;&#x2F;p&gt;
&lt;p&gt;$$r = \frac{\sum^n_{i=1}(x_i - \bar{x})(y_i - \bar{y})}{(n-1) S_x S_y} = \frac{S_{xy}}{(n-1) S_x S_y}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The first form (using $S_{xx}$, $S_{yy}$, $S_{xy}$) is often more convenient for algebraic manipulation, while the second form highlights that $r$ is a standardized measure of covariation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;properties-of-the-correlation-coefficient&quot;&gt;Properties of the Correlation Coefficient&lt;&#x2F;h2&gt;
&lt;p&gt;The Pearson correlation coefficient has several important properties:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-bounded-between-1-and-1&quot;&gt;1. Bounded between -1 and 1&lt;&#x2F;h3&gt;
&lt;p&gt;$$-1 \leq r \leq 1.$$&lt;&#x2F;p&gt;
&lt;p&gt;This follows from the Cauchy-Schwarz inequality, which states that $(S_{xy})^2 \leq S_{xx} \cdot S_{yy}$, with equality only when all data points lie exactly on a line.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-interpretation-of-values&quot;&gt;2. Interpretation of values&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;$r = 1$: perfect positive linear relationship (all points on a line with positive slope).&lt;&#x2F;li&gt;
&lt;li&gt;$r = -1$: perfect negative linear relationship (all points on a line with negative slope).&lt;&#x2F;li&gt;
&lt;li&gt;$r = 0$: no linear relationship (but there may be a nonlinear relationship).&lt;&#x2F;li&gt;
&lt;li&gt;$0 &amp;lt; |r| &amp;lt; 1$: partial linear association, with strength increasing as $|r|$ approaches 1.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;3-symmetry&quot;&gt;3. Symmetry&lt;&#x2F;h3&gt;
&lt;p&gt;$$r_{xy} = r_{yx}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The correlation between $x$ and $y$ is the same as the correlation between $y$ and $x$. This follows because $S_{xy}$ is symmetric in $x$ and $y$.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-invariance-under-linear-transformation&quot;&gt;4. Invariance under linear transformation&lt;&#x2F;h3&gt;
&lt;p&gt;If we define $u_i = a + bx_i$ and $v_i = c + dy_i$ with $b &amp;gt; 0$ and $d &amp;gt; 0$, then the correlation between $u$ and $v$ equals the correlation between $x$ and $y$. If either $b$ or $d$ is negative (but not both), the sign of $r$ flips.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-dimensionless&quot;&gt;5. Dimensionless&lt;&#x2F;h3&gt;
&lt;p&gt;The correlation coefficient has no units. The numerator $S_{xy}$ has units of $x$ times $y$, and the denominator $\sqrt{S_{xx} \cdot S_{yy}}$ also has units of $x$ times $y$, so they cancel.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;relationship-to-the-regression-slope&quot;&gt;Relationship to the Regression Slope&lt;&#x2F;h2&gt;
&lt;p&gt;Recall from Post 1 that the OLS slope estimator is $\hat{\beta}_1 = S_{xy}&#x2F;S_{xx}$. The correlation coefficient can be expressed in terms of $\hat{\beta}_1$:&lt;&#x2F;p&gt;
&lt;p&gt;$$r = \hat{\beta}_1 \sqrt{\frac{S_{xx}}{S_{yy}}} = \hat{\beta}_1 \cdot \frac{S_x}{S_y}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This shows that $r$ and $\hat{\beta}_1$ always share the same sign. A positive slope corresponds to a positive correlation, and a negative slope corresponds to a negative correlation.&lt;&#x2F;p&gt;
&lt;p&gt;Conversely, we can write the slope as:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_1 = r \cdot \frac{S_y}{S_x}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This expresses the regression slope as the correlation times the ratio of the standard deviations.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;population-correlation&quot;&gt;Population Correlation&lt;&#x2F;h2&gt;
&lt;p&gt;The sample correlation $r$ estimates the population correlation coefficient $\rho$ (rho), defined for a bivariate population as:&lt;&#x2F;p&gt;
&lt;p&gt;$$\rho = \frac{\text{Cov}(X, Y)}{\sigma_X \sigma_Y} = \frac{E[(X - \mu_X)(Y - \mu_Y)]}{\sigma_X \sigma_Y}.$$&lt;&#x2F;p&gt;
&lt;p&gt;When the data $(x_i, y_i)$ are sampled from a bivariate normal distribution, $r$ is a consistent estimator of $\rho$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;correlation-does-not-imply-causation&quot;&gt;Correlation Does Not Imply Causation&lt;&#x2F;h2&gt;
&lt;p&gt;A strong correlation between two variables does not mean that one causes the other. The association might be due to a lurking variable, reverse causation, or coincidence. Regression models describe associations; establishing causation requires careful experimental design or additional assumptions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we introduced the Pearson correlation coefficient:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$S_{yy} = \sum(y_i - \bar{y})^2$ completes the set of summary statistics alongside $S_{xx}$ and $S_{xy}$.&lt;&#x2F;li&gt;
&lt;li&gt;The sample standard deviations $S_x = \sqrt{S_{xx}&#x2F;(n-1)}$ and $S_y = \sqrt{S_{yy}&#x2F;(n-1)}$ measure spread.&lt;&#x2F;li&gt;
&lt;li&gt;The correlation $r = S_{xy}&#x2F;\sqrt{S_{xx} \cdot S_{yy}}$ quantifies the strength of the linear relationship.&lt;&#x2F;li&gt;
&lt;li&gt;$r$ is bounded between $-1$ and $1$, symmetric, and dimensionless.&lt;&#x2F;li&gt;
&lt;li&gt;The regression slope and correlation are related by $\hat{\beta}_1 = r \cdot S_y &#x2F; S_x$.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will define $R^2$ using SST, SSE, and SSR, and prove mathematically that $R^2 = r^2$ for simple linear regression.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Polynomial Regression</title>
        <published>2026-01-16T00:00:00+00:00</published>
        <updated>2026-01-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-03-polynomial-regression/"/>
        <id>https://jienweng.github.io/notes/regression-03-polynomial-regression/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-03-polynomial-regression/">&lt;p&gt;This note explains polynomial regression as a linear model in transformed features, not a different estimation framework. The motivation is to capture nonlinear trends while retaining familiar regression machinery. I highlight when polynomial terms help and when they mainly increase variance.&lt;&#x2F;p&gt;
&lt;p&gt;In the &lt;a href=&quot;&#x2F;posts&#x2F;regression-02-multiple-linear-regression&quot;&gt;previous post&lt;&#x2F;a&gt;, we introduced multiple linear regression using matrix notation. One natural question arises: what if the relationship between $x$ and $y$ is not a straight line? Polynomial regression addresses this by fitting a polynomial function of a single variable $x$, and it turns out to be a special case of the multiple linear regression framework we have already developed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-polynomial-model&quot;&gt;The Polynomial Model&lt;&#x2F;h2&gt;
&lt;p&gt;A polynomial regression model of degree $d$ takes the form:&lt;&#x2F;p&gt;
&lt;p&gt;$$y_i = \beta_0 + \beta_1 x_i + \beta_2 x_i^2 + \cdots + \beta_d x_i^d + \varepsilon_i.$$&lt;&#x2F;p&gt;
&lt;p&gt;This model is nonlinear in the variable $x$, but it is still linear in the parameters $\beta_0, \beta_1, \ldots, \beta_d$. This distinction is important because it means we can use the same OLS estimation procedure from multiple linear regression.&lt;&#x2F;p&gt;
&lt;p&gt;For example, a quadratic model ($d = 2$) is:&lt;&#x2F;p&gt;
&lt;p&gt;$$y_i = \beta_0 + \beta_1 x_i + \beta_2 x_i^2 + \varepsilon_i,$$&lt;&#x2F;p&gt;
&lt;p&gt;and a cubic model ($d = 3$) is:&lt;&#x2F;p&gt;
&lt;p&gt;$$y_i = \beta_0 + \beta_1 x_i + \beta_2 x_i^2 + \beta_3 x_i^3 + \varepsilon_i.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;design-matrix-construction&quot;&gt;Design Matrix Construction&lt;&#x2F;h2&gt;
&lt;p&gt;To fit a polynomial regression using the matrix approach, we define new variables:&lt;&#x2F;p&gt;
&lt;p&gt;$$z_1 = x, \quad z_2 = x^2, \quad z_3 = x^3, \quad \ldots, \quad z_d = x^d.$$&lt;&#x2F;p&gt;
&lt;p&gt;The design matrix for a polynomial of degree $d$ with $n$ observations is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{X} = \begin{pmatrix} 1 &amp;amp; x_1 &amp;amp; x_1^2 &amp;amp; \cdots &amp;amp; x_1^d \\ 1 &amp;amp; x_2 &amp;amp; x_2^2 &amp;amp; \cdots &amp;amp; x_2^d \\ \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\ 1 &amp;amp; x_n &amp;amp; x_n^2 &amp;amp; \cdots &amp;amp; x_n^d \end{pmatrix}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This is exactly the design matrix for a multiple linear regression with $d$ predictors $z_1, z_2, \ldots, z_d$. Therefore, the OLS estimator is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y},$$&lt;&#x2F;p&gt;
&lt;p&gt;which is the same formula we derived in the previous post.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-polynomial-regression-is-a-special-case-of-mlr&quot;&gt;Why Polynomial Regression is a Special Case of MLR&lt;&#x2F;h2&gt;
&lt;p&gt;The key insight is that &quot;linear&quot; in &quot;linear regression&quot; refers to linearity in the parameters, not in the predictors. Although the polynomial model includes terms like $x^2$ and $x^3$, each coefficient $\beta_j$ appears linearly. We can treat each power of $x$ as a separate predictor variable, and the entire OLS theory from multiple linear regression applies directly.&lt;&#x2F;p&gt;
&lt;p&gt;This means all the results we derived earlier carry over:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The normal equations $\mathbf{X}^T\mathbf{X}\hat{\boldsymbol{\beta}} = \mathbf{X}^T\mathbf{Y}$ hold.&lt;&#x2F;li&gt;
&lt;li&gt;The hat matrix $\mathbf{H} = \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T$ projects $\mathbf{Y}$ onto $\hat{\mathbf{Y}}$.&lt;&#x2F;li&gt;
&lt;li&gt;Residual properties remain the same.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;choosing-the-degree&quot;&gt;Choosing the Degree&lt;&#x2F;h2&gt;
&lt;p&gt;A natural question is: what degree $d$ should we use? The choice involves a tradeoff between model flexibility and model complexity.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Too low a degree&lt;&#x2F;strong&gt; (underfitting): The model is not flexible enough to capture the true relationship, leading to large systematic errors.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Too high a degree&lt;&#x2F;strong&gt; (overfitting): The model fits the training data very closely, including the noise, but performs poorly on new data.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A polynomial of degree $n - 1$ (where $n$ is the number of data points) can pass through every data point exactly, giving $SSE = 0$. However, such a model almost always overfits and generalizes badly.&lt;&#x2F;p&gt;
&lt;p&gt;Common approaches to select the degree include:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Visual inspection&lt;&#x2F;strong&gt;: Plot the data and the fitted curve for different degrees, and choose the one that captures the trend without fitting the noise.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cross-validation&lt;&#x2F;strong&gt;: Split the data into training and validation sets, fit models of various degrees on the training set, and select the degree with the lowest prediction error on the validation set.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Information criteria&lt;&#x2F;strong&gt;: Use metrics such as the Akaike Information Criterion (AIC) or the Bayesian Information Criterion (BIC) to balance goodness of fit with model complexity.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;multicollinearity-considerations&quot;&gt;Multicollinearity Considerations&lt;&#x2F;h2&gt;
&lt;p&gt;One practical concern with polynomial regression is multicollinearity. The predictors $x, x^2, x^3, \ldots$ are often highly correlated, especially when $x$ values span a narrow range. High multicollinearity inflates the variance of the coefficient estimates and makes $\mathbf{X}^T\mathbf{X}$ nearly singular.&lt;&#x2F;p&gt;
&lt;p&gt;A common remedy is to center the predictor before constructing polynomial terms. Instead of using $x$, we use $x - \bar{x}$:&lt;&#x2F;p&gt;
&lt;p&gt;$$z_1 = x - \bar{x}, \quad z_2 = (x - \bar{x})^2, \quad z_3 = (x - \bar{x})^3, \quad \ldots$$&lt;&#x2F;p&gt;
&lt;p&gt;Centering reduces the correlation among the polynomial terms and improves the numerical stability of the estimation.&lt;&#x2F;p&gt;
&lt;p&gt;Another approach is to use orthogonal polynomials, which are constructed so that $\mathbf{X}^T\mathbf{X}$ is diagonal, eliminating multicollinearity entirely.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;example-quadratic-fit&quot;&gt;Example: Quadratic Fit&lt;&#x2F;h2&gt;
&lt;p&gt;Consider a quadratic model $y_i = \beta_0 + \beta_1 x_i + \beta_2 x_i^2 + \varepsilon_i$ with the design matrix:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{X} = \begin{pmatrix} 1 &amp;amp; x_1 &amp;amp; x_1^2 \\ 1 &amp;amp; x_2 &amp;amp; x_2^2 \\ \vdots &amp;amp; \vdots &amp;amp; \vdots \\ 1 &amp;amp; x_n &amp;amp; x_n^2 \end{pmatrix}.$$&lt;&#x2F;p&gt;
&lt;p&gt;There are $p = 3$ parameters. The OLS solution $\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}$ gives us $\hat{\beta}_0$, $\hat{\beta}_1$, and $\hat{\beta}_2$ simultaneously. The coefficient $\hat{\beta}_2$ indicates the curvature of the fitted parabola: a positive $\hat{\beta}_2$ means the curve opens upward, and a negative $\hat{\beta}_2$ means it opens downward.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we explored polynomial regression:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The polynomial model $y_i = \beta_0 + \beta_1 x_i + \beta_2 x_i^2 + \cdots + \beta_d x_i^d + \varepsilon_i$ is nonlinear in $x$ but linear in the parameters.&lt;&#x2F;li&gt;
&lt;li&gt;By treating each power of $x$ as a separate predictor, polynomial regression becomes a special case of multiple linear regression.&lt;&#x2F;li&gt;
&lt;li&gt;The OLS estimator $\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}$ applies directly.&lt;&#x2F;li&gt;
&lt;li&gt;Choosing the polynomial degree requires balancing fit and complexity to avoid overfitting.&lt;&#x2F;li&gt;
&lt;li&gt;Centering or using orthogonal polynomials helps address multicollinearity.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will examine correlation, which quantifies the strength of the linear relationship between two variables using $S_{xx}$, $S_{yy}$, and $S_{xy}$.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Multiple Linear Regression</title>
        <published>2026-01-09T00:00:00+00:00</published>
        <updated>2026-01-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-02-multiple-linear-regression/"/>
        <id>https://jienweng.github.io/notes/regression-02-multiple-linear-regression/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-02-multiple-linear-regression/">&lt;p&gt;This note extends linear regression to multiple predictors and explains how interpretation changes once features interact through a shared model. The practical challenge is understanding coefficients conditionally, not in isolation. I focus on the model form, estimation intuition, and common interpretation mistakes.&lt;&#x2F;p&gt;
&lt;p&gt;In the &lt;a href=&quot;&#x2F;posts&#x2F;regression-01-simple-linear-regression&quot;&gt;previous post&lt;&#x2F;a&gt;, we explored simple linear regression with a single predictor. In practice, the response variable $y$ often depends on more than one predictor. Multiple linear regression extends the framework to accommodate $p - 1$ independent variables.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-model&quot;&gt;The Model&lt;&#x2F;h2&gt;
&lt;p&gt;The multiple linear regression model for the $i$-th observation is:&lt;&#x2F;p&gt;
&lt;p&gt;$$y_i = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \cdots + \beta_{p-1} x_{i,p-1} + \varepsilon_i,$$&lt;&#x2F;p&gt;
&lt;p&gt;where $x_{ij}$ is the value of the $j$-th predictor for the $i$-th observation, $\beta_j$ are the regression coefficients, and $\varepsilon_i$ is the error term. The total number of parameters is $p$ (including the intercept $\beta_0$).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;matrix-notation&quot;&gt;Matrix Notation&lt;&#x2F;h2&gt;
&lt;p&gt;Writing out the model for each observation individually becomes cumbersome as the number of predictors grows. Matrix notation provides a compact and powerful alternative.&lt;&#x2F;p&gt;
&lt;p&gt;Define the following:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{Y} = \begin{pmatrix} y_1 \\ y_2 \\ \vdots \\ y_n \end{pmatrix}, \quad \mathbf{X} = \begin{pmatrix} 1 &amp;amp; x_{11} &amp;amp; x_{12} &amp;amp; \cdots &amp;amp; x_{1,p-1} \\ 1 &amp;amp; x_{21} &amp;amp; x_{22} &amp;amp; \cdots &amp;amp; x_{2,p-1} \\ \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\ 1 &amp;amp; x_{n1} &amp;amp; x_{n2} &amp;amp; \cdots &amp;amp; x_{n,p-1} \end{pmatrix},$$&lt;&#x2F;p&gt;
&lt;p&gt;$$\boldsymbol{\beta} = \begin{pmatrix} \beta_0 \\ \beta_1 \\ \vdots \\ \beta_{p-1} \end{pmatrix}, \quad \boldsymbol{\varepsilon} = \begin{pmatrix} \varepsilon_1 \\ \varepsilon_2 \\ \vdots \\ \varepsilon_n \end{pmatrix}.$$&lt;&#x2F;p&gt;
&lt;p&gt;The model can now be written as:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{Y} = \mathbf{X}\boldsymbol{\beta} + \boldsymbol{\varepsilon}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Here, $\mathbf{Y}$ is an $n \times 1$ vector of responses, $\mathbf{X}$ is an $n \times p$ design matrix (the first column of ones accounts for the intercept), $\boldsymbol{\beta}$ is a $p \times 1$ vector of coefficients, and $\boldsymbol{\varepsilon}$ is an $n \times 1$ vector of errors.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ols-in-matrix-form&quot;&gt;OLS in Matrix Form&lt;&#x2F;h2&gt;
&lt;p&gt;The sum of squared errors can be written in matrix form as:&lt;&#x2F;p&gt;
&lt;p&gt;$$SSE = (\mathbf{Y} - \mathbf{X}\boldsymbol{\beta})^T(\mathbf{Y} - \mathbf{X}\boldsymbol{\beta}).$$&lt;&#x2F;p&gt;
&lt;p&gt;Expanding this expression:&lt;&#x2F;p&gt;
&lt;p&gt;$$SSE = \mathbf{Y}^T\mathbf{Y} - 2\boldsymbol{\beta}^T\mathbf{X}^T\mathbf{Y} + \boldsymbol{\beta}^T\mathbf{X}^T\mathbf{X}\boldsymbol{\beta}.$$&lt;&#x2F;p&gt;
&lt;p&gt;To minimize, we take the derivative with respect to $\boldsymbol{\beta}$ and set it to zero:&lt;&#x2F;p&gt;
&lt;p&gt;$$\frac{\partial SSE}{\partial \boldsymbol{\beta}} = -2\mathbf{X}^T\mathbf{Y} + 2\mathbf{X}^T\mathbf{X}\boldsymbol{\beta} = \mathbf{0}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This gives us the normal equations in matrix form:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{X}^T\mathbf{X}\hat{\boldsymbol{\beta}} = \mathbf{X}^T\mathbf{Y}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Provided that $\mathbf{X}^T\mathbf{X}$ is invertible (that is, the columns of $\mathbf{X}$ are linearly independent), we can solve for the OLS estimator:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This single formula generalizes the simple linear regression result. When $p = 2$ (one predictor plus the intercept), this reduces to $\hat{\beta}&lt;em&gt;1 = S&lt;&#x2F;em&gt;{xy}&#x2F;S_{xx}$ and $\hat{\beta}_0 = \bar{y} - \hat{\beta}_1\bar{x}$ as derived in the previous post.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;interpreting-the-coefficients&quot;&gt;Interpreting the Coefficients&lt;&#x2F;h2&gt;
&lt;p&gt;Each coefficient $\hat{\beta}_j$ (for $j = 1, 2, \ldots, p - 1$) represents the estimated change in $y$ for a one-unit increase in $x_j$, while holding all other predictors constant. This &quot;holding other variables constant&quot; interpretation is what distinguishes multiple regression from running separate simple regressions.&lt;&#x2F;p&gt;
&lt;p&gt;The intercept $\hat{\beta}_0$ represents the estimated value of $y$ when all predictors are equal to zero.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-hat-matrix&quot;&gt;The Hat Matrix&lt;&#x2F;h2&gt;
&lt;p&gt;The vector of fitted values is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\mathbf{Y}} = \mathbf{X}\hat{\boldsymbol{\beta}} = \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y} = \mathbf{H}\mathbf{Y},$$&lt;&#x2F;p&gt;
&lt;p&gt;where $\mathbf{H} = \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T$ is called the hat matrix. It &quot;puts a hat on&quot; $\mathbf{Y}$, transforming observed values into fitted values. The hat matrix is symmetric ($\mathbf{H}^T = \mathbf{H}$) and idempotent ($\mathbf{H}^2 = \mathbf{H}$).&lt;&#x2F;p&gt;
&lt;p&gt;The residual vector is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{e} = \mathbf{Y} - \hat{\mathbf{Y}} = (\mathbf{I} - \mathbf{H})\mathbf{Y}.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;assumptions&quot;&gt;Assumptions&lt;&#x2F;h2&gt;
&lt;p&gt;The assumptions for multiple linear regression extend those of simple linear regression:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Linearity&lt;&#x2F;strong&gt;: $\mathbf{Y} = \mathbf{X}\boldsymbol{\beta} + \boldsymbol{\varepsilon}$, the model is linear in the parameters.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Full rank&lt;&#x2F;strong&gt;: The design matrix $\mathbf{X}$ has full column rank, meaning $\text{rank}(\mathbf{X}) = p$. This ensures $\mathbf{X}^T\mathbf{X}$ is invertible.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Exogeneity&lt;&#x2F;strong&gt;: $E(\boldsymbol{\varepsilon} | \mathbf{X}) = \mathbf{0}$, the errors have zero conditional mean.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Homoscedasticity&lt;&#x2F;strong&gt;: $\text{Var}(\boldsymbol{\varepsilon} | \mathbf{X}) = \sigma^2\mathbf{I}_n$, the errors have constant variance and are uncorrelated.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Normality&lt;&#x2F;strong&gt; (for inference): $\boldsymbol{\varepsilon} \sim N(\mathbf{0}, \sigma^2\mathbf{I}_n)$.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;When assumptions 1 through 4 hold, the Gauss-Markov theorem guarantees that $\hat{\boldsymbol{\beta}}$ is the Best Linear Unbiased Estimator (BLUE).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;connection-to-simple-linear-regression&quot;&gt;Connection to Simple Linear Regression&lt;&#x2F;h2&gt;
&lt;p&gt;In the special case where $p = 2$, we have a single predictor $x$ and the design matrix becomes:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathbf{X} = \begin{pmatrix} 1 &amp;amp; x_1 \\ 1 &amp;amp; x_2 \\ \vdots &amp;amp; \vdots \\ 1 &amp;amp; x_n \end{pmatrix}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Computing $(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}$ in this case yields the familiar results $\hat{\beta}&lt;em&gt;1 = S&lt;&#x2F;em&gt;{xy}&#x2F;S_{xx}$ and $\hat{\beta}_0 = \bar{y} - \hat{\beta}_1\bar{x}$, confirming that the matrix formulation is a true generalization.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we extended the regression framework to handle multiple predictors:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The model $\mathbf{Y} = \mathbf{X}\boldsymbol{\beta} + \boldsymbol{\varepsilon}$ uses matrix notation to express the relationship compactly.&lt;&#x2F;li&gt;
&lt;li&gt;The OLS estimator $\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}$ generalizes the simple linear regression solution.&lt;&#x2F;li&gt;
&lt;li&gt;Each $\hat{\beta}_j$ measures the effect of one predictor while holding the others constant.&lt;&#x2F;li&gt;
&lt;li&gt;The hat matrix $\mathbf{H}$ projects observed values onto fitted values.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will explore polynomial regression, which is a special case of multiple linear regression where the predictors are powers of a single variable.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Simple Linear Regression</title>
        <published>2026-01-02T00:00:00+00:00</published>
        <updated>2026-01-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/regression-01-simple-linear-regression/"/>
        <id>https://jienweng.github.io/notes/regression-01-simple-linear-regression/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/regression-01-simple-linear-regression/">&lt;p&gt;This note introduces simple linear regression from first principles and focuses on how slope and intercept are estimated from data. The problem it solves is modeling a linear relationship between one predictor and one response. It is intended as the base layer for the later regression notes in this series.&lt;&#x2F;p&gt;
&lt;p&gt;In secondary school, we learn that the equation of a straight line is given by $y = mx + c$, where $m$ is the slope and $c$ is the y-intercept. In statistics and machine learning, we use a similar but more general form to model the relationship between a dependent variable $y$ and an independent variable $x$. This is known as simple linear regression.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-model&quot;&gt;The Model&lt;&#x2F;h2&gt;
&lt;p&gt;In simple linear regression, we express the relationship as:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{y}_i = \hat{\beta}_0 + \hat{\beta}_1 x_i,$$&lt;&#x2F;p&gt;
&lt;p&gt;where $\hat{y}_i$ is the predicted value of the dependent variable for the $i$-th observation, $\hat{\beta}_0$ is the estimated y-intercept, and $\hat{\beta}_1$ is the estimated slope coefficient. The &quot;hat&quot; notation indicates that these are estimates derived from data, not the true (unknown) population parameters $\beta_0$ and $\beta_1$.&lt;&#x2F;p&gt;
&lt;p&gt;The true model is assumed to be:&lt;&#x2F;p&gt;
&lt;p&gt;$$y_i = \beta_0 + \beta_1 x_i + \varepsilon_i,$$&lt;&#x2F;p&gt;
&lt;p&gt;where $\varepsilon_i$ represents the random error term for the $i$-th observation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ordinary-least-squares-ols&quot;&gt;Ordinary Least Squares (OLS)&lt;&#x2F;h2&gt;
&lt;p&gt;The question is: how do we find the best estimates $\hat{\beta}_0$ and $\hat{\beta}_1$? We need a systematic method that determines the line of best fit. The most common approach is Ordinary Least Squares (OLS).&lt;&#x2F;p&gt;
&lt;p&gt;OLS minimizes the sum of the squared differences between the observed values $y_i$ and the predicted values $\hat{y}_i$. These differences are called residuals, defined as $e_i = y_i - \hat{y}_i$. By squaring the residuals, we treat positive and negative errors equally and penalize larger deviations more heavily.&lt;&#x2F;p&gt;
&lt;p&gt;The objective function is the Sum of Squared Errors (SSE):&lt;&#x2F;p&gt;
&lt;p&gt;$$SSE = \sum^n_{i=1}(y_i - \hat{y}_i)^2 = \sum^n_{i=1}(y_i - \hat{\beta}_0 - \hat{\beta}_1 x_i)^2.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deriving-the-normal-equations&quot;&gt;Deriving the Normal Equations&lt;&#x2F;h2&gt;
&lt;p&gt;To minimize the SSE, we take partial derivatives with respect to $\hat{\beta}_0$ and $\hat{\beta}_1$ and set them equal to zero.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;partial-derivative-with-respect-to-hat-beta-0&quot;&gt;Partial derivative with respect to $\hat{\beta}_0$&lt;&#x2F;h3&gt;
&lt;p&gt;$$\frac{\partial SSE}{\partial \hat{\beta}_0} = -2\sum^n_{i=1}(y_i - \hat{\beta}_0 - \hat{\beta}_1 x_i) = 0.$$&lt;&#x2F;p&gt;
&lt;p&gt;Dividing both sides by $-2$ and expanding the sum:&lt;&#x2F;p&gt;
&lt;p&gt;$$\sum^n_{i=1} y_i - n\hat{\beta}_0 - \hat{\beta}_1 \sum^n_{i=1} x_i = 0.$$&lt;&#x2F;p&gt;
&lt;p&gt;Solving for $\hat{\beta}_0$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x},$$&lt;&#x2F;p&gt;
&lt;p&gt;where $\bar{x} = \frac{1}{n}\sum^n_{i=1}x_i$ and $\bar{y} = \frac{1}{n}\sum^n_{i=1}y_i$ are the sample means.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;partial-derivative-with-respect-to-hat-beta-1&quot;&gt;Partial derivative with respect to $\hat{\beta}_1$&lt;&#x2F;h3&gt;
&lt;p&gt;$$\frac{\partial SSE}{\partial \hat{\beta}_1} = -2\sum^n_{i=1}x_i(y_i - \hat{\beta}_0 - \hat{\beta}_1 x_i) = 0.$$&lt;&#x2F;p&gt;
&lt;p&gt;Dividing by $-2$ and expanding:&lt;&#x2F;p&gt;
&lt;p&gt;$$\sum^n_{i=1} x_i y_i - \hat{\beta}_0 \sum^n_{i=1} x_i - \hat{\beta}_1 \sum^n_{i=1} x_i^2 = 0.$$&lt;&#x2F;p&gt;
&lt;p&gt;Substituting $\hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x}$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\sum^n_{i=1} x_i y_i - (\bar{y} - \hat{\beta}_1 \bar{x})\sum^n_{i=1} x_i - \hat{\beta}_1 \sum^n_{i=1} x_i^2 = 0.$$&lt;&#x2F;p&gt;
&lt;p&gt;After simplification, we obtain:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_1 = \frac{\sum^n_{i=1}(x_i - \bar{x})(y_i - \bar{y})}{\sum^n_{i=1}(x_i - \bar{x})^2}.$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;introducing-s-xx-and-s-xy-notation&quot;&gt;Introducing $S_{xx}$ and $S_{xy}$ Notation&lt;&#x2F;h2&gt;
&lt;p&gt;To write the estimators more concisely, we define the following summary statistics:&lt;&#x2F;p&gt;
&lt;p&gt;$$S_{xx} = \sum^n_{i=1}(x_i - \bar{x})^2 = \sum^n_{i=1}x_i^2 - n\bar{x}^2,$$&lt;&#x2F;p&gt;
&lt;p&gt;$$S_{xy} = \sum^n_{i=1}(x_i - \bar{x})(y_i - \bar{y}) = \sum^n_{i=1}x_i y_i - n\bar{x}\bar{y}.$$&lt;&#x2F;p&gt;
&lt;p&gt;Using this notation, the OLS estimators become:&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_1 = \frac{S_{xy}}{S_{xx}},$$&lt;&#x2F;p&gt;
&lt;p&gt;$$\hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x}.$$&lt;&#x2F;p&gt;
&lt;p&gt;These are elegant expressions that reveal the structure of the estimates. The slope $\hat{\beta}_1$ is the ratio of the joint variability of $x$ and $y$ (captured by $S_{xy}$) to the variability of $x$ alone (captured by $S_{xx}$). The intercept $\hat{\beta}_0$ ensures the regression line passes through the point $(\bar{x}, \bar{y})$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;residuals-and-fitted-values&quot;&gt;Residuals and Fitted Values&lt;&#x2F;h2&gt;
&lt;p&gt;Once we have $\hat{\beta}_0$ and $\hat{\beta}_1$, we can compute:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fitted values&lt;&#x2F;strong&gt;: $\hat{y}_i = \hat{\beta}_0 + \hat{\beta}_1 x_i$ for each observation.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Residuals&lt;&#x2F;strong&gt;: $e_i = y_i - \hat{y}_i$ for each observation.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Two important properties of OLS residuals are worth noting:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The residuals sum to zero: $\sum^n_{i=1} e_i = 0$.&lt;&#x2F;li&gt;
&lt;li&gt;The residuals are uncorrelated with the fitted values: $\sum^n_{i=1} e_i \hat{y}_i = 0$.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;These properties follow directly from the normal equations.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;key-assumptions&quot;&gt;Key Assumptions&lt;&#x2F;h2&gt;
&lt;p&gt;For OLS to produce reliable estimates, the following assumptions are typically required:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Linearity&lt;&#x2F;strong&gt;: The relationship between $x$ and $y$ is linear in the parameters.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Independence&lt;&#x2F;strong&gt;: The observations are independent of one another.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Homoscedasticity&lt;&#x2F;strong&gt;: The variance of the error terms $\varepsilon_i$ is constant across all values of $x$.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Normality&lt;&#x2F;strong&gt;: The error terms are normally distributed, that is, $\varepsilon_i \sim N(0, \sigma^2)$.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;When these assumptions hold, OLS produces the Best Linear Unbiased Estimators (BLUE) according to the Gauss-Markov theorem.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;In this post, we covered the fundamentals of simple linear regression:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The model $\hat{y}_i = \hat{\beta}_0 + \hat{\beta}_1 x_i$ describes the estimated linear relationship between $x$ and $y$.&lt;&#x2F;li&gt;
&lt;li&gt;OLS minimizes the sum of squared errors to find the best-fitting line.&lt;&#x2F;li&gt;
&lt;li&gt;The estimators $\hat{\beta}&lt;em&gt;1 = S&lt;&#x2F;em&gt;{xy}&#x2F;S_{xx}$ and $\hat{\beta}_0 = \bar{y} - \hat{\beta}_1\bar{x}$ are derived from the normal equations.&lt;&#x2F;li&gt;
&lt;li&gt;The $S_{xx}$ and $S_{xy}$ notation provides a compact way to express these results.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the next post, we will extend this framework to handle multiple independent variables through multiple linear regression.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Let&#x27;s talk about Solar Photovoltaic Systems in WWTPs Malaysia</title>
        <published>2025-12-01T00:00:00+00:00</published>
        <updated>2025-12-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/photovoltaic-systems-in-wwtps-malaysia/"/>
        <id>https://jienweng.github.io/notes/photovoltaic-systems-in-wwtps-malaysia/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/photovoltaic-systems-in-wwtps-malaysia/">&lt;p&gt;This note reviews how solar photovoltaic systems can reduce energy cost and emissions in wastewater treatment plants (WWTPs) in Malaysia. The core problem is that WWTP operations are electricity-intensive and exposed to tariff volatility. I summarize constraints, control architecture, and where ML-based forecasting can improve deployment decisions.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-solar-pv-in-wwtps&quot;&gt;Why Solar PV in WWTPs?&lt;&#x2F;h1&gt;
&lt;p&gt;Wastewater treatment facilities are globally recognized as energy-intensive operations, often relying on electrical energy accounting for a significant portion of their operational expenditure (OPEX). For conventional WWTPs, energy use contributes between 25%-60% of the total OPEX &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Muzaffar2022&lt;&#x2F;sup&gt;
. This high dependency on the central electricity grid exposes these facilities to electricity tariffs volatility and supply uncertainties. The national sewerage company, Indah Water Konsortium (IWK) Sdn Bhd, has experienced a substantial increase with electricity costs ballooned from RM22.53 million in 2000 to RM256.30 million in 2020 &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;IWK2021&lt;&#x2F;sup&gt;
. This became the primary motivation for IWK to explore renewable energy sources, particularly solar PV systems, to mitigate energy costs and enhance sustainability.&lt;&#x2F;p&gt;
&lt;p&gt;IWK operates as Malaysia&#x27;s national sewerage company and manages a vast network of public treatment plants. As of December 2021, IWK operated and maintained 7,272 public Sewerage Treatment Plants (STPs) and 1,375 network pump stations across the country &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;IWK2021_report&lt;&#x2F;sup&gt;
. The regulatory oversight is provided by the Suruhanjaya Perkhidmatan Air Negara (SPAN), which ensures compliance with national water quality, ensuring that operators comply with stipulated standards and contractual obligations.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;energy-demand-characteristics-of-wwtps&quot;&gt;Energy Demand Characteristics of WWTPs&lt;&#x2F;h1&gt;
&lt;p&gt;Before implementing solar PV systems, it is crucial to understand the energy demand characteristics of WWTPs to correctly sizing and optimizing a solar PV system. The energy consumption profile of WWTP is highly influence by the pkant scale and the dominant treatment technology used.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;specific-energy-consumption-sec-and-categorization&quot;&gt;Specific Energy Consumption (SEC) and Categorization&lt;&#x2F;h2&gt;
&lt;p&gt;Energy intensity, generally measured as Specific Energy Consumption (SEC) in kilowatt-hour per cubic meter (kWh&#x2F;m³) of treated wastewater, is a key metric for evaluating the energy efficiency of WWTPs. The SEC values can vary significantly based on the treatment processes employed and the plant&#x27;s capacity. Data suggests that smaller WWTPs, particular those below $10,000 m^3&#x2F;month$ capacity, tends to bear a disproportionately higher electricity cost, accounting for 30%-40% of their total running costs &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Muzaffar2022&lt;&#x2F;sup&gt;
. In contrast, larger WWTPs with capacities exceeding typically exhibit lower SEC values, accounting for 15%-30% of the total running costs.&lt;&#x2F;p&gt;
&lt;p&gt;Specific data collected from village WWTPs in Romania indicated high annual average SEC values, ranging between $1.786 kWh&#x2F;m^3$ to $2.334 kWh&#x2F;m^3$ of treated wastewater &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Tokos2021&lt;&#x2F;sup&gt;
. Furthermore, the complexity of sewage sludge disposal capacity has increased rapidly alongside population growthm reaching 7 millions $m^3$ annually. Managing this sludge requires significant energy input, contributing to the sector&#x27;s overall energy consumption, with sewage sludge treatment alone consuming an estimated $544,900 GWh$ across IWK operations between 2016 and 2019 &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Quan2022&lt;&#x2F;sup&gt;
.&lt;&#x2F;p&gt;
&lt;p&gt;This observed variability indicates that the primary candidates for immediate solar PV intergartion are the small to medium sized conventional STPs. These plants have higher SEC values and higher dependence on expensive grid power, meaninf the marginal return from solar PV energy offset is maximized in this segment.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;identification-of-energy-intensive-processes&quot;&gt;Identification of Energy-Intensive Processes&lt;&#x2F;h2&gt;
&lt;p&gt;The greatest energy concentration of electrical energy demand within a conventional WWTP is typically found in the biological treatment line (BgT), primarily driven by the aeration systems. This process is necessary for activated sludge processes to maintain adequate dissolved oxygen levels for microbial activity. Data confirms that BgT accounts for the majority of total energy consumption, ranging from 63.2% to 72.9% in surveyed WWTPs &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Tokos2021&lt;&#x2F;sup&gt;
.&lt;&#x2F;p&gt;
&lt;p&gt;Whether thorugh diffused air of mechanical aerators (such as those used in &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.epa.gov&#x2F;system&#x2F;files&#x2F;documents&#x2F;2022-10&#x2F;oxidation-ditch-factsheet.pdf&quot;&gt;oxidation ditches&lt;&#x2F;a&gt;), aeration systems requires continuous operation to provide oxygen transfer, circulation, and mixing. This need for continuous, high-power during daytime operational hours, and we realized this characteristic is highly advantageous for solar PV integration. The steady daytime demand for blowers and aerators means that the generated solar power can be immediately and entirely consumed, leadning to high utilization rates, and diminish the need for Battery Energy Storage Systems (BESS), which is often a significant cost driver in renewable energy (RE) projects.&lt;&#x2F;p&gt;
&lt;p&gt;The high concentration of energy use in aeration suggests that before implementing solar PV systems, WWTP operators should measures energy efficiency in the aeration systems. Simply installing a large PV array without optimzing the blowers and biological processes may lead  to a larger and more costly system. Streamlining the anaerobic biological treatments is possible and should be prioritized to reduce the overall energy demand before sizing the solar PV system.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;advanced-pv-power-forecasting-and-control-systems&quot;&gt;Advanced PV Power Forecasting and Control Systems&lt;&#x2F;h1&gt;
&lt;p&gt;The modern integration of photovoltaic systems into WWTPs requires sophisticated forecasting and control architectures. Recent research demonstrates a clear evolution toward hybrid AI-based forecasting models that combine deep learning with optimization algorithms &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;IturraldeCarrera2025&lt;&#x2F;sup&gt;
. These advanced models significantly outperform classical deterministic methods in handling the highly dynamic and non-linear conditions of real-world PV generation.&lt;&#x2F;p&gt;
&lt;p&gt;Machine learning ensemble algorithms such as XGBoost, LightGBM, and CatBoost have emerged as foundational tools for high-accuracy solar power prediction &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Nguyen2025&lt;&#x2F;sup&gt;
. A critical finding is that humidity and ambient temperature emerge as the most influential factors affecting PV module efficiency, particularly in tropical and humid climates.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;integrated-energy-management&quot;&gt;Integrated Energy Management&lt;&#x2F;h2&gt;
&lt;p&gt;Effective PV system integration requires a multi-layered control hierarchy. The foundational layer is Maximum Power Point Tracking (MPPT), which continuously adjusts the DC-DC converter to extract maximum available power from the solar array. A critical technical constraint in many jurisdictions is the Zero Export requirement, which prohibits injecting electrical power back into the grid. To comply, Zero Export Controllers (ZEC) operate on near-instantaneous feedback loops &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Alnawafah2025&lt;&#x2F;sup&gt;
.&lt;&#x2F;p&gt;
&lt;p&gt;Battery Energy Storage Systems provide flexibility to address both PV intermittency and zero-export constraints. Advanced control algorithms optimize charging during low-cost periods and discharging during peak tariff hours &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Hvala2025&lt;&#x2F;sup&gt;
.&lt;&#x2F;p&gt;
&lt;!-- # Demand-Side Optimization

Maximizing PV self-consumption requires accurate prediction of not only generation but also internal treatment demand. Dynamic ensemble models utilizing machine learning have been successfully applied to predict water quality characteristics such as COD and TN, reducing prediction errors to 9.5%-15.2% MAPE &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Yu2025&lt;&#x2F;sup&gt;
. This intelligent demand-side management enables facilities to achieve substantial energy consumption reductions while modulating energy draw in response to predicted PV output. --&gt;
&lt;!-- # Floating Photovoltaic Systems

Floating Photovoltaic (FPV) installations over basins represent an emerging solution to land-use constraints. FPV systems provide inherent performance advantages through water cooling effects that counteract thermal degradation. By actively mitigating thermal stress, FPV improves both instantaneous yield and long-term reliability &lt;sup class=&quot;cite-ref&quot; title=&quot;bibliography&amp;#x2F;renewable_energy.bib&quot;&gt;Selj2025&lt;&#x2F;sup&gt;
.

The aquatic deployment environment introduces unique technical challenges requiring specialized components such as double-glass laminated modules and IP68-rated electrical components to ensure durability in humid and corrosive conditions. --&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;The successful integration of solar PV systems into Malaysian WWTPs needs a strategic of advanced forecasting. By combining accurate generation forecasting with sophisticated demand-side prediction, facilities can achieve near energy-autonomous operation while positioning themselves as flexible power assets responding to grid demands.&lt;&#x2F;p&gt;
&lt;p&gt;In the future research we need to explore the details implementation of predictive modelling and control algorithms in WWTPs, as well as conducting in-depth literature reviews on case studies of existing energy predictive systems in WWTP.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Gradient Descent Algorithm Explained</title>
        <published>2025-11-30T00:00:00+00:00</published>
        <updated>2025-11-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/gradient-descent/"/>
        <id>https://jienweng.github.io/notes/gradient-descent/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/gradient-descent/">&lt;p&gt;Gradient descent is simple to state and easy to get wrong in practice, almost always through the learning rate. This note works through the rule itself and then through what happens when the learning rate is too small, reasonable, and too large, with small examples you can follow by hand.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-update-rule&quot;&gt;The Update Rule&lt;&#x2F;h2&gt;
&lt;p&gt;Consider a differentiable function $f: \mathbb{R}^n \to \mathbb{R}$. The gradient of $f$ at a point $x \in \mathbb{R}^n$ is denoted as $\nabla f(x)$, which is a vector of partial derivatives. The gradient points in the direction of steepest ascent. To find a local minimum, gradient descent simply moves the other way:&lt;&#x2F;p&gt;
&lt;p&gt;$$x_{k+1} = x_k - \alpha \nabla f(x_k),$$&lt;&#x2F;p&gt;
&lt;p&gt;where:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$x_k$ is the current point,&lt;&#x2F;li&gt;
&lt;li&gt;$\alpha$ is the learning rate, where $\alpha &amp;gt; 0$,&lt;&#x2F;li&gt;
&lt;li&gt;$\nabla f(x_k)$ is the gradient of $f$ at point $x_k$.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;We simulate two scenarios, one where the loss has a positive gradient and one where it is negative.&lt;&#x2F;p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;scenario-1-positive-gradient&quot;&gt;Scenario 1: Positive Gradient&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s consider a simple quadratic function:&lt;&#x2F;p&gt;
&lt;p&gt;$$f(x) = x^2 + 4x + 4.$$&lt;&#x2F;p&gt;
&lt;p&gt;The gradient of this function is:&lt;&#x2F;p&gt;
&lt;p&gt;$$\nabla f(x) = 2x + 4.$$&lt;&#x2F;p&gt;
&lt;p&gt;Starting from an initial point, say $x_0 = 0$, and choosing a learning rate $\alpha = 0.1$, we can apply the gradient descent update rule iteratively:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Compute the gradient at the current point: $\nabla f(x_0) = 2(0) + 4 = 4$.&lt;&#x2F;li&gt;
&lt;li&gt;Update the point: $x_1 = x_0 - 0.1 \cdot 4 = 0 - 0.4 = -0.4$.&lt;&#x2F;li&gt;
&lt;li&gt;Repeat the process for a number of iterations.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;For the first 5 iteration, we can tabulate the results as follows:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Iteration ($k$)&lt;&#x2F;th&gt;&lt;th&gt;Current Point ($x_k$)&lt;&#x2F;th&gt;&lt;th&gt;Gradient ($\nabla f(x_k)$)&lt;&#x2F;th&gt;&lt;th&gt;Updated Point ($x_{k+1}$)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;0.0&lt;&#x2F;td&gt;&lt;td&gt;4.0&lt;&#x2F;td&gt;&lt;td&gt;-0.4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;-0.4&lt;&#x2F;td&gt;&lt;td&gt;3.2&lt;&#x2F;td&gt;&lt;td&gt;-0.72&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;-0.72&lt;&#x2F;td&gt;&lt;td&gt;2.56&lt;&#x2F;td&gt;&lt;td&gt;-0.976&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;-0.976&lt;&#x2F;td&gt;&lt;td&gt;2.048&lt;&#x2F;td&gt;&lt;td&gt;-1.1808&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;-1.1808&lt;&#x2F;td&gt;&lt;td&gt;1.6384&lt;&#x2F;td&gt;&lt;td&gt;-1.34464&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;We can observe that the gradient os loss function from positive value is moving backward each time step, and the first step size is larger but it gradually decreases as we approach the minimum point. This is the brilliant part of gradient descent, as it automatically take larger steps when we are far from the minimum and smaller steps as we get closer to the minimum.&lt;&#x2F;p&gt;
&lt;p&gt;We can visualize the process using a simple plot:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; matplotlib&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pyplot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; plt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Define the function and its gradient&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; grad_f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Gradient Descent parameters&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;alpha&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iterations&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 20&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Store the points&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;x0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;iterations&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    grad&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; grad_f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x_new&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x_points&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; alpha&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; grad&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x_points&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_new&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Plotting&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;linspace&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;plot&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; label&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;f(x) = x^2 + 4x + 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;scatter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;array&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; color&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;red&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;plot&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;array&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; color&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;red&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; linestyle&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; label&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Gradient Descent Path&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;title&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Gradient Descent on f(x)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;xlabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;ylabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;f(x)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;legend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;grid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;show&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After 20 iterations, the points converge towards the minimum point at $x = -2$. We can see how the points move along the curve of the function, gradually approaching the minimum.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;316430f9-5f75-41db-b0ba-4553e3763a94?format=jpeg&quot; alt=&quot;Gradient Descent Visualization&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;scenario-2-negative-gradient&quot;&gt;Scenario 2: Negative Gradient&lt;&#x2F;h3&gt;
&lt;p&gt;Now, let&#x27;s consider a function with a negative gradient:
$$f(x) = -x^3 + 4x^2 - 4.$$&lt;&#x2F;p&gt;
&lt;p&gt;The gradient of this function is:
$$\nabla f(x) = -3x^2 + 8x.$$&lt;&#x2F;p&gt;
&lt;p&gt;Similar to what we have done on the previous example, we start from an initial point, say $x_0 = 0$, and choosing a learning rate $\alpha = 0.01$, we can apply the gradient descent update rule iteratively:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Compute the gradient at the current point: $\nabla f(x_0) = -3(0)^2 + 8(0) = 0$.&lt;&#x2F;li&gt;
&lt;li&gt;Update the point: $x_1 = x_0 - 0.01 \cdot 0 = 0$.&lt;&#x2F;li&gt;
&lt;li&gt;Repeat the process for a number of iterations.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;For the first 5 iterations, we can tabulate the results as follows:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Iteration ($k$)&lt;&#x2F;th&gt;&lt;th&gt;Current Point ($x_k$)&lt;&#x2F;th&gt;&lt;th&gt;Gradient ($\nabla f(x_k)$)&lt;&#x2F;th&gt;&lt;th&gt;Updated Point ($x_{k+1}$)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;1.0000&lt;&#x2F;td&gt;&lt;td&gt;5.0000&lt;&#x2F;td&gt;&lt;td&gt;0.9500&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;0.9500&lt;&#x2F;td&gt;&lt;td&gt;4.7175&lt;&#x2F;td&gt;&lt;td&gt;0.9028&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;0.9028&lt;&#x2F;td&gt;&lt;td&gt;4.4533&lt;&#x2F;td&gt;&lt;td&gt;0.8583&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;0.8583&lt;&#x2F;td&gt;&lt;td&gt;4.2057&lt;&#x2F;td&gt;&lt;td&gt;0.8162&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;0.8162&lt;&#x2F;td&gt;&lt;td&gt;3.9734&lt;&#x2F;td&gt;&lt;td&gt;0.7765&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;In this scenario, we can observe that the gradient of loss function from negative value is moving forward each time step, and the first step size is larger but it gradually decreases as we approach the minimum point. Similar to the previous scenario, gradient descent automatically adjusts the step size based on the distance from the minimum. Similarly, we can visualize the process using a simple plot:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; matplotlib&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;pyplot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; plt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Define the function and its gradient&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;3&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; grad_f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;3&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Gradient Descent parameters&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;alpha&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.01&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iterations&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 40&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Store the points&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;x0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;iterations&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    grad&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; grad_f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x_new&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; x_points&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; alpha&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; grad&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x_points&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_new&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Plotting&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;linspace&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;plot&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; label&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;f(x) = -x^3 + 4x^2 - 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;scatter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;array&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; color&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;red&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;plot&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;np&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;array&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x_points&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; color&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;red&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; linestyle&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; label&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Gradient Descent Path&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;title&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Gradient Descent on f(x)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;xlabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;ylabel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;f(x)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;legend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;grid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;plt&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;show&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After 20 iterations, the points converge towards the minimum point at approximately $x = 0$. We can see how the points move along the curve of the function, gradually approaching the minimum. We plotted the gradient descent path on the function curve:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;081bc8a3-abb0-47db-8da6-97c1a7a4ba1b?format=jpeg&quot; alt=&quot;Gradient Descent Visualization&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;proof-of-convergence&quot;&gt;Proof of Convergence&lt;&#x2F;h2&gt;
&lt;p&gt;To prove the convergence of the gradient descent algorithm, we need to show that the sequence of points generated by the algorithm converges to a local minimum of the function $f(x)$. We assume that $f$ is a convex function with Lipschitz continuous gradients, meaning there exists a constant $L &amp;gt; 0$ such that for all $x, y \in \mathbb{R}^n$,
$$|\nabla f(x) - \nabla f(y)| \leq L |x - y|.$$&lt;&#x2F;p&gt;
&lt;p&gt;Under convexity alone, we show that the rate of convergence is sublinear. Specifically, we can show that after $k$ iterations, the function value satisfies:&lt;&#x2F;p&gt;
&lt;p&gt;$$
f(x_k) - f(x^*) \leq \frac{L |x_0-x^*|^2 }{2k},
$$&lt;&#x2F;p&gt;
&lt;p&gt;where $x^*$ is the global minimum point of $f$. This indicates that as the number of iterations $k$ increases, the function value approaches the minimum value at a rate inversely proportional to $k$.&lt;&#x2F;p&gt;
&lt;p&gt;This completes the proof of convergence for the gradient descent algorithm under the assumptions of convexity and Lipschitz continuous gradients. The algorithm effectively finds a local minimum of the function $f(x)$ by iteratively updating the points in the direction of the steepest descent.&lt;&#x2F;p&gt;
&lt;p&gt;But all of this hangs on the choice of learning rate $\alpha$. Too large and the algorithm overshoots the minimum and diverges. Too small and convergence crawls. There are various ways to adapt the learning rate during optimization, like schedules and optimizers such as Adam and RMSprop, which I may cover in a future post.&lt;&#x2F;p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;known-issues-of-gradient-descent&quot;&gt;Known Issues of Gradient Descent&lt;&#x2F;h2&gt;
&lt;p&gt;While gradient descent is a powerful optimization algorithm, it does have some known issues. In multivariate functions, the presence of saddle points can affect the convergence. Saddle points are points where the gradient is zero, but they are neither local minima nor local maxima. In high-dimensional spaces, saddle points are more prevalent than local minima, and gradient descent can get stuck at these points, leading to slow convergence or failure to find the global minimum. A popular example is the function $f(x, y) = x^2 - y^2$, which has a saddle point at $(0, 0)$. The direction vector at this point is zero, and gradient descent may struggle to escape this point.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;cdn.cosmos.so&amp;#x2F;41b26430-2fde-4cfb-8d2b-a9debca6d4ee?format=jpeg&quot; alt=&quot;Gradient Descent Saddle Point&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Gradient Descent Saddle Point&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;To mitigate the issues with saddle points, various techniques can be employed, such as adding noise to the gradients, using momentum-based methods, or employing second-order optimization methods that consider the curvature of the function, which we can explore in future discussions. But overall, gradient descent remains a fundamental and widely used optimization algorithm in machine learning and various other fields.&lt;&#x2F;p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;That is gradient descent: a one-line update rule that ends up as the backbone of most of machine learning.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s remarkable how such a simple iterative process can lead optimize almost any complex function in real life applications. I truly cannot appreciate enough the beauty of this elegant mathematical concept.&lt;&#x2F;p&gt;
&lt;p&gt;For those who are interested to learn more about gradient descent, I highly recommend watching the following video by StatQuest, which provides an excellent visual explanation of the algorithm:&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide video-embed&quot;&gt;
  &lt;div class=&quot;video-shell&quot;&gt;
    &lt;iframe
      src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;sDv4f4s2SB8&quot;
      title=&quot;Gradient Descent, Step-by-Step | StatQuest&quot;
      loading=&quot;lazy&quot;
      allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
      referrerpolicy=&quot;strict-origin-when-cross-origin&quot;
      allowfullscreen
    &gt;&lt;&#x2F;iframe&gt;
  &lt;&#x2F;div&gt;
  
  &lt;figcaption&gt;Gradient Descent, Step-by-Step | StatQuest&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;I hope this post has provided a clear understanding of the gradient descent.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My Gallery of Talentbank Boardroom Challenge 2025</title>
        <published>2025-11-14T00:00:00+00:00</published>
        <updated>2025-11-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/blog/talentbank-boardroom-challenge/"/>
        <id>https://jienweng.github.io/blog/talentbank-boardroom-challenge/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/blog/talentbank-boardroom-challenge/">&lt;p&gt;This post is a compact gallery plus debrief from the Talentbank Boardroom Challenge 2025. I keep the narrative focused on preparation, presentation decisions, and the specific lessons carried into later projects.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;cdn.cosmos.so&amp;#x2F;757eebfe-bcda-458c-a666-bb88ff978ed2?format=jpeg&quot; alt=&quot;Talentbank Boardroom Challenge 2025&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;cdn.cosmos.so&amp;#x2F;e9ab40f7-f55a-40b0-90f9-43afddce3592?format=jpeg&quot; alt=&quot;Talentbank Boardroom Challenge 2025&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;cdn.cosmos.so&amp;#x2F;22cf9c61-bcc4-4cb3-a5d3-901668997566?format=jpeg&quot; alt=&quot;Talentbank Boardroom Challenge 2025&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;cdn.cosmos.so&amp;#x2F;e06b9475-0616-4202-b7cb-7644eefba819?format=jpeg&quot; alt=&quot;Talentbank Boardroom Challenge 2025&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;cdn.cosmos.so&amp;#x2F;dc8979eb-c7e4-4c5a-9530-921feb0b0ae4?format=jpeg&quot; alt=&quot;Talentbank Boardroom Challenge 2025&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;cdn.cosmos.so&amp;#x2F;4a589e9c-6ab9-458d-a659-7c47b8f8b583?format=jpeg&quot; alt=&quot;Talentbank Boardroom Challenge 2025&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
&lt;&#x2F;figure&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reinforcement learning practices in healthcare applications</title>
        <published>2025-10-21T00:00:00+00:00</published>
        <updated>2025-10-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/reinforcement-learning-practices-in-healthcare/"/>
        <id>https://jienweng.github.io/notes/reinforcement-learning-practices-in-healthcare/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/reinforcement-learning-practices-in-healthcare/">&lt;p&gt;This note reviews practical reinforcement learning use cases in healthcare and the constraints that matter in deployment. The challenge is that policy learning in clinical settings is high-stakes, partially observed, and often offline. I summarize where RL is promising and where reliability and safety dominate design choices.&lt;&#x2F;p&gt;
&lt;p&gt;In healthcare applications, artificial intelligence (AI) plays a crucial role in transforming patient care, diagnostics, and treatment planning to make healthcare more efficient and effective. However, if AI is used improperly, it may leads to worse outcomes rather than improved ones.&lt;&#x2F;p&gt;
&lt;p&gt;In the subset of AI, reinforcement learning (RL) has shown great promise in optimising sequential decision-making processes, which are common settings in healthcare industry. However, applying RL in healthcare settings requires careful consideration of several important practices to ensure a safety outcome. To illustrate the pitfalls of reinforcement learning, we consider the sepsis management, which remains wide uncertainty in the way clinicians make decisions.&lt;&#x2F;p&gt;
&lt;p&gt;In the context of sepsis, a history may include a patient&#x27;s vital signs, laboratory results, administered treatments, and other relevant clinical information over time. The actions could involve decisions such as administering fluids, vasopressors, or antibiotics at different time points. The rewards are typically defined based on patient outcomes, such as survival rates, length of hospital stay, or improvement in clinical scores. Note that defining ideal sepsis resuscitation strategies is challenging due to the complex and dynamic nature of the condition, as well as the variability in patient responses to treatments, therefore it is not straightforward to define short-term rewards for each action taken.&lt;&#x2F;p&gt;
&lt;p&gt;Here are three fundamental concerns when applying reinforcement learning in healthcare:&lt;&#x2F;p&gt;
&lt;h1 id=&quot;is-the-ai-given-access-to-all-variables-that-influence-decision-making&quot;&gt;Is the AI given access to all variables that influence decision making?&lt;&#x2F;h1&gt;
&lt;p&gt;RL agent can only ook at the recorded data, however there are much more information and context that should be taken into consideration. Failing to consider all variables may result in esitmates that are confounded by spurious correlation.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, severely sick septic patients may receive fluids earlier than healthier patients yet have worse outcomes, which is clearly because of them being sicker in the first place, not because of fluids worsen the outcomes. Therefore, it is important to considers of pissble confounding factors, which even more than what is required for standard prediction studies, as the sequential nature of the problem could possibly lead to confounding effects in both long term and short term.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;how-big-was-that-big-data&quot;&gt;How big was that big data?&lt;&#x2F;h1&gt;
&lt;p&gt;This is relatively straightfoward. For any AI training, its necessary to feed the model an adequate amount of useful information, so is RL model. Logically, for RL model to evaluate a new policy, it needs to find a long, continuous sequence of decisions in the historical data that matches its new policy.&lt;&#x2F;p&gt;
&lt;p&gt;In clinical trials, the mismatches between new treatment policy against historical data, also known as off-policy evaluation, the effective sample size can become small. The mismatches grow with the number of decisions in a patient&#x27;s history. For one sepsis study, a cohort of 3,855 patients yielded an effective sample size of only a few dozen. Therefore, instead of exploring new treatment approaches, observational data shall be used only to refining existing practices.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;will-the-ai-behave-prospectively-as-intended&quot;&gt;Will the AI behave prospectively as intended?&lt;&#x2F;h1&gt;
&lt;p&gt;One of the core elements in the feedback loop of every RL is rewards. However, if the design of reward function is not handled properly (e.g. error in formulation, data processing etc.), the momdel will eventually laed to poor decisions.&lt;&#x2F;p&gt;
&lt;p&gt;Often, an overly simple reward function may neglect the long-term effects. For instance, rewarding only blood pressure targets may result in agent that harms long-term benefit by dosing excessive vasopressors to patients. Additionally, the learned policy might decay after a period of time if there&#x27;s changes in the treatment standards.&lt;&#x2F;p&gt;
&lt;p&gt;Therefore, it is neccessary to use interpretable machine learning to interrogate and assess whether learned policies will behave as intended in a prospective clinincal setting&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;In the end, although RL offers such promising opportunities to optimising sequential treatments in medical industry, we shall be cautious in deploying into production and requires due diligence to safely realise its potential in revolutionalising this life-saving industry.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;&amp;#x2F;img&amp;#x2F;posts&amp;#x2F;guidelines_for_reinforcement_learning_in_healthcare.jpg&quot; alt=&quot;Guidelines for Reinforcement Learning in Healthcare&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Guidelines for Reinforcement Learning in Healthcare&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;In the end, I would like to express my gratitude to Omer Gottesman et al. for providing such practical viewpoint in standardising the application of reinforcement learning in clinical settings.&lt;&#x2F;p&gt;
&lt;p&gt;References:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Gottesman, O., Johansson, F., Komorowski, M., Faisal, A., Sontag, D., Doshi-Velez, F., &amp;amp; Celi, L. A. (2019). Guidelines for reinforcement learning in healthcare. Nature Medicine, 25(1), 16–18. &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1038&#x2F;s41591-018-0310-5&quot;&gt;https:&#x2F;&#x2F;doi.org&#x2F;10.1038&#x2F;s41591-018-0310-5&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Action-value methods with incremental step size in reinforcement learning</title>
        <published>2025-10-17T00:00:00+00:00</published>
        <updated>2025-10-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/notes/action-value-methods-with-incremental-step-size/"/>
        <id>https://jienweng.github.io/notes/action-value-methods-with-incremental-step-size/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/notes/action-value-methods-with-incremental-step-size/">&lt;p&gt;This note derives the incremental update rule for action-value estimation in k-armed bandits and explains why it is preferable to recomputing full averages. The problem is memory and compute cost when rewards accumulate over time. By the end, you get a practical update equation you can implement directly in RL experiments.&lt;&#x2F;p&gt;
&lt;p&gt;Consider any &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Multi-armed_bandit&quot;&gt;k-armed bandit problem&lt;&#x2F;a&gt;, we consider each actions taken as pulling an arm of a slot machine, and the machine gives us a reward based on the action taken. We denote the action selected at time step $t$ as $A_t$, and the correspondiong reward received as $R_t$. Among the $k$ actions, the expected value of each action $a$ is denoted as $q_*(a)=\mathbb{E}[R_t|A_t=a]$, which is also known as the &lt;em&gt;value&lt;&#x2F;em&gt; of that action $a$. It is rationale to choose the action with the highest value, however in the practical applications, the value of each action is unknown. Therefore, we need to estimate the value of each action, denoted as $Q_t(a)$, read as the estimated value of action $a$ at time step $t$. The fundamental goal is to find such $Q_t(a)$ that is as close as possible to the $q_*(a)$. It is commonly known as the &lt;em&gt;true value&lt;&#x2F;em&gt; by community.&lt;&#x2F;p&gt;
&lt;p&gt;To estimate the values of each action, which we collectively call as the &lt;em&gt;action-value methods&lt;&#x2F;em&gt;, we can use the sample-average method to update the estimated value of action $a$ at time step $t$ as follows:&lt;&#x2F;p&gt;
&lt;p&gt;$$Q_{t}(a)={{\text{sum of rewards when $a$ taken prior to $t$}} \over {\text{number of times $a$ taken prior to $t$}}}.$$&lt;&#x2F;p&gt;
&lt;p&gt;This method simply takes the average of all the rewards received when action $a$ is taken prior to time step $t$. To simplify the notation, we focus on a single action. We denoted $R_i$ as the reward received the $i$-th time of this action is taken, and $n$ as the number of times action $a$ is taken prior to time step $t$. Logically, we can let $Q_n$ denote the estimate of its action value after it has been taken $n-1$ times. Therefore, we can rewrite the update rule as follows:&lt;&#x2F;p&gt;
&lt;p&gt;$$Q_n = \frac{\sum_{i=1}^{n-1}R_i}{n-1} = \frac{R_1+R_2+\ldots+R_{n-1}}{n-1}.$$&lt;&#x2F;p&gt;
&lt;p&gt;As the number of times of the action, $n$ increases, the obvious wat to update the estimate is to recalculate the average by summing up all the previous rewards and dividing it by $n-1$. However, as the number of times of the action increases to a large number, this method becomes progressively more expensive as we need to store all the previos rewards and recalculate the sum every time we need to update the estimate.&lt;&#x2F;p&gt;
&lt;p&gt;But is there a better way to update the estimate without storing all the previous rewards? The answer is yes. We devise the incremental formulas for updating the estimate by&lt;&#x2F;p&gt;
&lt;p&gt;$$
\begin{align*}
Q_{n+1} &amp;amp; = \frac{1}{n}\sum_{i=1}^{n}R_i \\
&amp;amp; = \frac{1}{n}\left(R_n + \sum_{i=1}^{n-1}R_i\right) \\
&amp;amp; = \frac{1}{n}\left(R_n + (n-1)\frac{1}{n-1}\sum_{i=1}^{n-1}R_i\right) \\
&amp;amp; = \frac{1}{n}\left(R_n + (n-1)Q_n\right) \\
&amp;amp; = \frac{1}{n}\left(R_n+nQ_n-Q_n \right) \\
&amp;amp; = Q_n + \frac{1}{n}[R_n - Q_n].
\end{align*}
$$&lt;&#x2F;p&gt;
&lt;p&gt;This incremental formula allows us to update the estimate $Q_n$ to $Q_{n+1}$ by only using the most recent reward $R_n$ and the previous estimate $Q_n$, without the need to store all the previous rewards. The term $\frac{1}{n}$ serves as the step size, which decreases as $n$ increases, ensuring that the estimate converges to the true value over time.&lt;&#x2F;p&gt;
&lt;p&gt;Even in $n=1$, we can still obtain $Q_2 = R_1$ for arbitrary initial estimate $Q_1$. In this case, the initial estimate $Q_1$ is completely ignored after the first update, as it should be. In processing the $n$th reward, the estimate is adjusted by a fraction of the error term $[R_n - Q_n]$, which is the difference between the received reward and the current estimate. This adjustment is scaled by the step size $\frac{1}{n}$, which ensures that as more data is collected, the updates become smaller, allowing the estimate to stabilize around the true value. Note that the step size here is not constant, it decreases as the number of times of the action increases.&lt;&#x2F;p&gt;
&lt;p&gt;Back to the bandit problem, the proposed simulation in for pseudo-code is as follows:&lt;&#x2F;p&gt;
&lt;details class=&quot;detail-block&quot;&gt;
  &lt;summary&gt;Bandit Problem with Incremental Step Size&lt;&#x2F;summary&gt;
  &lt;div class=&quot;detail-body&quot;&gt;
    &lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Initialize&lt;&#x2F;span&gt;&lt;span&gt; Q&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; arbitrarily&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; for&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; all&lt;&#x2F;span&gt;&lt;span&gt; actions&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;For&lt;&#x2F;span&gt;&lt;span&gt; each&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span&gt; step&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span&gt; = &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Select&lt;&#x2F;span&gt;&lt;span&gt; action&lt;&#x2F;span&gt;&lt;span&gt; A_t&lt;&#x2F;span&gt;&lt;span&gt; using&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span&gt; policy&lt;&#x2F;span&gt;&lt;span&gt; derived&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; from&lt;&#x2F;span&gt;&lt;span&gt; Q&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;e&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;g&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; ε&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;greedy&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Take&lt;&#x2F;span&gt;&lt;span&gt; action&lt;&#x2F;span&gt;&lt;span&gt; A_t&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; observe&lt;&#x2F;span&gt;&lt;span&gt; reward&lt;&#x2F;span&gt;&lt;span&gt; R_t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Update&lt;&#x2F;span&gt;&lt;span&gt; the&lt;&#x2F;span&gt;&lt;span&gt; estimate&lt;&#x2F;span&gt;&lt;span&gt; Q&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;A_t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; using&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        n&lt;&#x2F;span&gt;&lt;span&gt; = &lt;&#x2F;span&gt;&lt;span&gt;number&lt;&#x2F;span&gt;&lt;span&gt; of&lt;&#x2F;span&gt;&lt;span&gt; times&lt;&#x2F;span&gt;&lt;span&gt; action&lt;&#x2F;span&gt;&lt;span&gt; A_t&lt;&#x2F;span&gt;&lt;span&gt; has&lt;&#x2F;span&gt;&lt;span&gt; been&lt;&#x2F;span&gt;&lt;span&gt; taken&lt;&#x2F;span&gt;&lt;span&gt; prior&lt;&#x2F;span&gt;&lt;span&gt; to&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Q&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;A_t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; = &lt;&#x2F;span&gt;&lt;span&gt;Q&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;A_t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;R_t&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; Q&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;A_t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;End&lt;&#x2F;span&gt;&lt;span&gt; For&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;details&gt;
&lt;p&gt;In the end of this post, we derived the incremental step size method for updating the aciton-value estimates which extensively applied in reinforcement learning. This method is computationally efficient as it does not require storing all previous rewards, and it ensures convergence to the true action values over time.&lt;&#x2F;p&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;img src=&quot;https:&amp;#x2F;&amp;#x2F;m.media-amazon.com&amp;#x2F;images&amp;#x2F;I&amp;#x2F;81EBg4xmLgL._UF1000,1000_QL80_.jpg&quot; alt=&quot;Reinforcement Learning: An Introduction&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  
  &lt;figcaption&gt;Reinforcement Learning: An Introduction&lt;&#x2F;figcaption&gt;
  
&lt;&#x2F;figure&gt;
&lt;p&gt;I want to express my gratitude to Sutton and Barto for their excellent book &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;web.stanford.edu&#x2F;class&#x2F;psych209&#x2F;Readings&#x2F;SuttonBartoIPRLBook2ndEd.pdf&quot;&gt;Reinforcement Learning: An Introduction&lt;&#x2F;a&gt; that provides a comprehensive introduction to the concepts and algorithms of reinforcement learning.&lt;&#x2F;p&gt;
&lt;p&gt;References:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Sutton, R. S., &amp;amp; Barto, A. G. (2018). Reinforcement Learning: An Introduction (2nd ed.). MIT Press. &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;web.stanford.edu&#x2F;class&#x2F;psych209&#x2F;Readings&#x2F;SuttonBartoIPRLBook2ndEd.pdf&quot;&gt;https:&#x2F;&#x2F;web.stanford.edu&#x2F;class&#x2F;psych209&#x2F;Readings&#x2F;SuttonBartoIPRLBook2ndEd.pdf&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A Small Talk About Hackathons</title>
        <published>2025-08-07T00:00:00+00:00</published>
        <updated>2025-08-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/blog/something-about-hackathon/"/>
        <id>https://jienweng.github.io/blog/something-about-hackathon/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/blog/something-about-hackathon/">&lt;p&gt;This post is a short reflection on hackathon culture from a participant perspective: what helps teams learn fast, where teams usually waste time, and how to keep projects grounded under deadline pressure.&lt;&#x2F;p&gt;
&lt;p&gt;Along the way, I&#x27;ve met people who are far more experienced, far more knowledgeable than me. And honestly, I can&#x27;t compete with them. It&#x27;s easy to feel small in those moments.&lt;&#x2F;p&gt;
&lt;p&gt;For anyone who&#x27;s been through hackathons, you&#x27;ll know. The amount of time, energy, effort you need to commit is just INSANE. Most competitions require you to go through multiple stages: prelim round, and sometimes a semi-final, and then the final round. Every round is like a mini-marathon, endless brainstorming, last-minute changes, and almost no sleep just to push through and deliver something that works.&lt;&#x2F;p&gt;
&lt;p&gt;In the first few hackathons, I was genuinely excited. It was fun. Every time one ended, I was already looking forward to the next. But after round and round of hackathons, I started to feel the burn. Exhausted, Emptiness. YES, I still learned something new in every game, but the energy, the excitement just started to wear off.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually, it all stated to feel a bit empty.&lt;&#x2F;p&gt;
&lt;p&gt;There was also this lingering thought back in my mind: &quot;Why am I comparing myself to people who are fully in this industry, who live and breathe in tech every single day, when I&#x27;m still just a student trying to explore things outside my field?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s still one more hackathon coming up, and I&#x27;ll give it what I can. but after that, I really need a break. A proper one. Not just from hackathons, but from the constant cycle of proving myself. I need to breathe, reset and work on my inner health, mentally and emotionally.&lt;&#x2F;p&gt;
&lt;p&gt;Not saying I&#x27;m quitting hackathons forever. Not at all. But I know I need some time to focus more on myself, rebuild myself, and come back stronger.&lt;&#x2F;p&gt;
&lt;p&gt;Thanks for reading until here. Sometimes, the best thing you can do for your growth is step back, realign, and then go again.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Dead Internet Theory #1</title>
        <published>2025-07-22T00:00:00+00:00</published>
        <updated>2025-07-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/blog/dead-internet-theory-1/"/>
        <id>https://jienweng.github.io/blog/dead-internet-theory-1/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/blog/dead-internet-theory-1/">&lt;p&gt;This post is a personal reflection on authenticity online: what feels different now, why AI-generated social content often feels hollow, and where I might be overreacting. The goal is not to claim a grand theory, but to document a concrete shift in reading experience across LinkedIn and Reddit.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;b77a432b-7713-4b10-879d-e8256d284766?format=jpeg&quot; alt=&quot;You can&amp;#39;t tell whether the experience is real or not&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I do admit I used ChatGPT for my content in the past, but I realised that the content is not really what I’ve done before, the experience is basically “artificial”. I don’t deny AI as a productivity tool, but sometimes you just can’t tell the existence of the content written there. There’s a coldness, an emptiness that creeps in when scrolling through these posts, making it feel like shouting into a void where no real person listens.&lt;&#x2F;p&gt;
&lt;p&gt;It is really different nowadays to scroll through social media, LinkedIn, and Reddit. You don’t feel people there. It makes me really feel like the dead Internet Theory is here, and everything is full of bot activity and automatically generated content manipulated by algorithms. The authenticity of online interactions seems to be fading, they replaced by automated perfection that feels disturbingly hollow.&lt;&#x2F;p&gt;
&lt;p&gt;Chatbots or AI are really good for proofreading, but they are still only good at proofreading. Soon I think they will no longer have real content or real ideas when people are writing their experiences, their ideas; they just become full of BS nowadays. Now I even wish to find some long-ass article that is awfully organised, I find some fun to read through even though it was not good, but I can find authenticity there.&lt;&#x2F;p&gt;
&lt;p&gt;Has anyone noticed this? You could share them with me or offer me a perspective I haven’t considered before. I’d be more than happy to discuss it. I think we’ll might have another follow-up episode on this.&lt;&#x2F;p&gt;
&lt;p&gt;Share me your thoughts: &lt;a href=&quot;mailto:contact@jienweng.com&quot;&gt;contact@jienweng.com&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Making Deepseek R1 ChatBot</title>
        <published>2024-12-30T00:00:00+00:00</published>
        <updated>2024-12-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/blog/deepseek-r1-chatbot/"/>
        <id>https://jienweng.github.io/blog/deepseek-r1-chatbot/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/blog/deepseek-r1-chatbot/">&lt;p&gt;This post documents a small DeepSeek-R1 chatbot build: why I chose the model, what setup decisions mattered, and what worked in practice. Instead of focusing on AI industry drama, I keep the write-up centered on implementation choices and takeaways for future iterations.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;feed&#x2F;update&#x2F;urn:li:activity:7291035071992520704&#x2F;&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;d66e7a6d-8205-4e9c-ba4f-656971c79857?format=jpeg&quot; alt=&quot;“Good artists copy, great artists steal” - Steve Jobs&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;But forget the drama for a second, because the best part? Deepseek is open-source. That’s a huge win for the AI community. No more being locked behind API paywalls or waiting for some corporate overlord to decide what we can or can’t do. It’s out there, free to tinker with, and you bet I had to try it out for myself.&lt;&#x2F;p&gt;
&lt;p&gt;So, I went ahead and do something I wanted to do for soooo long -- built a chatbot. It’s not packed with fancy features (yet), but through this little experiment, I’ve discovered some pretty interesting things about how the Deepseek R1 model works. You can try it out live &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;btw, we won’t dive into the technical aspects just yet—that’s coming up in the next section! Stay tuned for more details on how these improvements will work behind the scenes.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-unique-thinking-approach&quot;&gt;The Unique &quot;Thinking&quot; Approach&lt;&#x2F;h3&gt;
&lt;p&gt;What blows my mind the most about this whole setup is how I managed to separate the model’s thinking process from its final response. Most chatbots out there? They just spit out an answer, and you have no idea what’s happening behind the scenes. But with this, you can actually see how the model thinks through a problem before giving an answer. It’s like watching an AI have an inner monologue, refining its thoughts before speaking. And honestly? I’ve never seen this before in any LLMs I’ve used.&lt;&#x2F;p&gt;
&lt;p&gt;At first, I didn’t even plan for this feature—it just happened while I was testing out different ways to improve response quality. I noticed that the model was generating some hidden reasoning steps before its final output. Instead of discarding them, I figured, Why not show them? And once I did, it was a game-changer. It made the AI feel so much more transparent—almost like it was thinking out loud.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;b65be583-5569-41e4-ab62-b4bf500120e1?format=jpeg&quot; alt=&quot;Fun Fact: The &amp;quot;thinking&amp;quot; parts are actually generated as HTML!&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;For example, if you ask it something like, “What do you think about climate change in Malaysia?”, you won’t just get a final answer out of nowhere. You’ll actually see the model go through a step-by-step breakdown of its thought process:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Breaking down the question components&lt;&#x2F;li&gt;
&lt;li&gt;Evaluating current knowledge&lt;&#x2F;li&gt;
&lt;li&gt;Forming logical connections&lt;&#x2F;li&gt;
&lt;li&gt;Synthesizing a comprehensive response&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;After seeing the model’s thinking process, what really stands out to me is how structured its response is. It doesn’t just throw out some generic take on climate change—it actually analyzes the question, breaks it down into different angles, and then builds a well-organized answer.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;6c9e3e2a-d318-4615-8786-7d60248fc049?format=jpeg&quot; alt=&quot;Interesting Observation: The model sometimes includes unexpected details—some accurate, some a bit off!&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That said, while the response does sound solid, there are some oddities that make me wonder what’s going on under the hood. For example, it mentions “the subtropical Andaman and Nicobar Islands”—which, uh, aren’t even part of Malaysia. Also, “Ch bamboo” initiative? Never heard of that one. These small but noticeable mistakes show that while the model is good at structuring its answers, it still struggles with factual accuracy.&lt;&#x2F;p&gt;
&lt;p&gt;But that’s exactly what makes having a visible thought process so useful. Instead of just blindly trusting AI responses, we can now see how the model arrives at its conclusions—which means we can spot errors more easily. If it had &lt;strong&gt;hallucinated&lt;&#x2F;strong&gt; this stuff in a normal chatbot, I might not have even noticed. But because I can watch it reason through the problem, I can tell where things might be going wrong.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;8cf44525-7111-4a09-87c7-ee6a09d3cb3b?format=jpeg&quot; alt=&quot;AI Hallucination Meme&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This kind of transparency is what makes AI feel less like a magic black box and more like an actual tool that we can guide, correct, and refine. And that’s honestly what excites me the most about this project.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;deployment-specifications&quot;&gt;Deployment Specifications&lt;&#x2F;h3&gt;
&lt;p&gt;The chatbot is currently hosted on Hugging Face Spaces, running on a basic-tier instance, which means it’s not exactly a powerhouse but still gets the job done. Here’s what it’s running on:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: 2 vCPUs&lt;&#x2F;li&gt;
&lt;li&gt;RAM: 16GB&lt;&#x2F;li&gt;
&lt;li&gt;Storage: Basic instance storage&lt;&#x2F;li&gt;
&lt;li&gt;Framework: Gradio&lt;&#x2F;li&gt;
&lt;li&gt;Inference Optimization: FP16 quantization&lt;&#x2F;li&gt;
&lt;li&gt;Average Response Time: 2-3 seconds&lt;&#x2F;li&gt;
&lt;li&gt;Concurrent Users Supported: Up to 10&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You might notice that the &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;live preview&lt;&#x2F;a&gt; here can be a bit slow while generating responses. That’s because the hardware isn’t optimized for LLM inference, so it’s working with some limitations. Hope you can bear with it! 😆&lt;&#x2F;p&gt;
&lt;p&gt;If you enjoy the project and want to see it run smoother, you can consider &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;buymeacoffee.com&#x2F;jianrong_jr&quot;&gt;sponsoring me&lt;&#x2F;a&gt;. Who knows? With enough support, I might upgrade the resources for future projects and push this even further :D&lt;&#x2F;p&gt;
&lt;h3 id=&quot;efficient-model-architecture&quot;&gt;Efficient Model Architecture&lt;&#x2F;h3&gt;
&lt;p&gt;The chatbot uses the Deepseek R1 Distilled 1.5B model, which is a significantly compressed version of the original 685B parameter model. Despite having only 1.5 billion parameters, it maintains impressive performance for many tasks.&lt;&#x2F;p&gt;
&lt;p&gt;Key points about the model:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Original model: &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-R1&quot;&gt;DeepSeek R1 (685B)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Distilled version: &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-R1-Distill-Qwen-1.5B&quot;&gt;DeepSeek R1 Distill Qwen 1.5B&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;440x parameter reduction while maintaining core capabilities&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;blog&#x2F;open-r1&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;8eb746ad-784b-4892-9eb0-4cd26a82af13?format=jpeg&quot; alt=&quot;Model Architecture&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;impressive-benchmark-results&quot;&gt;Impressive Benchmark Results&lt;&#x2F;h3&gt;
&lt;p&gt;What’s most fascinating about this model is how well it holds up when compared to much larger models. Despite having far fewer parameters, it manages to outperform some big names in the AI world for certain tasks.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;data-science-in-your-pocket&#x2F;deepseek-r1-distill-qwen-1-5b-the-best-small-sized-llm-14eee304d94b&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;2ac19291-5c72-4a60-b290-bd140a61a4d4?format=jpeg&quot; alt=&quot;Model Comparison Results&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;outstanding-performance-in-key-areas&quot;&gt;Outstanding Performance in Key Areas&lt;&#x2F;h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AIME 2024 (Math Competition)&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;DeepSeek R1 Distilled: 28.9% Pass@1&lt;&#x2F;li&gt;
&lt;li&gt;GPT-4o: 9.3% Pass@1&lt;&#x2F;li&gt;
&lt;li&gt;Claude 3.5: 16.0% Pass@1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MATH-500 (Mathematical Reasoning)&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;DeepSeek R1 Distilled: 83.9% Pass@1&lt;&#x2F;li&gt;
&lt;li&gt;GPT-4o: 74.6% Pass@1&lt;&#x2F;li&gt;
&lt;li&gt;Claude 3.5: 78.3% Pass@1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Codeforces (Competitive Programming)&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;DeepSeek R1 Distilled: 954 Rating&lt;&#x2F;li&gt;
&lt;li&gt;GPT-4o: 759 Rating&lt;&#x2F;li&gt;
&lt;li&gt;Claude 3.5: 717 Rating&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;model-strengths-limitations&quot;&gt;Model Strengths &amp;amp; Limitations&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;Strengths:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Superior reasoning capabilities, especially in mathematics&lt;&#x2F;li&gt;
&lt;li&gt;Highly efficient with only 1.5B parameters&lt;&#x2F;li&gt;
&lt;li&gt;Effective knowledge distillation from larger models&lt;&#x2F;li&gt;
&lt;li&gt;Excellent performance in zero-shot scenarios&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Limitations:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Lower performance in general coding tasks&lt;&#x2F;li&gt;
&lt;li&gt;Potential language mixing issues&lt;&#x2F;li&gt;
&lt;li&gt;Sensitivity to prompt formatting&lt;&#x2F;li&gt;
&lt;li&gt;Limited performance in broader general knowledge tasks&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This balanced perspective shows why I chose this model for my chatbot implementation - it provides exceptional reasoning capabilities while remaining lightweight enough for practical deployment.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;&#x2F;h3&gt;
&lt;p&gt;Due to iframe restrictions, you can access the live demo through these methods:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;Direct Link to Demo&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;docs&#x2F;hub&#x2F;spaces-sdks-docker#rest-api&quot;&gt;API Documentation&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&#x2F;tree&#x2F;main&quot;&gt;Source Code&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;0eab3363-76da-4dab-b44b-a4a5d7cdc96f?format=jpeg&quot; alt=&quot;Chatbot interface&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h3&gt;
&lt;p&gt;Deepseek has definitely shaken things up in the AI world, and the drama surrounding it is just the tip of the iceberg. Forget the finger-pointing—this move is a win for the AI community, especially since Deepseek is open-source. No more waiting around for companies to decide how we can use AI; now it’s out there for everyone to play with and improve.&lt;&#x2F;p&gt;
&lt;p&gt;And as for my little experiment—building a chatbot with the Deepseek R1 model—it’s not feature-packed yet, but it’s definitely been a fun ride. You can try it out live &lt;a href=&quot;https:&#x2F;&#x2F;jienweng.github.io&#x2F;blog&#x2F;deepseek-r1-chatbot&#x2F;(https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2)&quot;&gt;here&lt;&#x2F;a&gt; and see how it works for yourself!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;additional-resources&quot;&gt;Additional Resources&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-R1-Distill-Qwen-1.5B&quot;&gt;Model Card&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;docs&#x2F;hub&#x2F;spaces-overview&quot;&gt;Deployment Guide&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;mteb&#x2F;leaderboard&quot;&gt;Performance Benchmarks&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discuss.huggingface.co&#x2F;&quot;&gt;Community Discussion&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Feel free to experiment with the live demo and share your thoughts!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;DeepSeek R1 (685B)&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;The original DeepSeek R1 model, a large-scale AI model with 685 billion parameters, was the precursor to the distilled 1.5B version used in the chatbot.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-R1&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;DeepSeek R1 Distill Qwen 1.5B&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;This is the distilled version of the DeepSeek R1 model, compressed to 1.5 billion parameters while retaining core capabilities.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-R1-Distill-Qwen-1.5B&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Open R1 Model Architecture&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;Explore the detailed architecture of the DeepSeek R1 model, showcasing its design and structure.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;blog&#x2F;open-r1&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Medium - Deepseek R1 Distill Qwen 1.5B Performance&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;A comparison of the performance between Deepseek R1 Distilled and other models, showing its impressive results in multiple domains.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;data-science-in-your-pocket&#x2F;deepseek-r1-distill-qwen-1-5b-the-best-small-sized-llm-14eee304d94b&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face Space - Chatbot Demo&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;Live demo of the Deepseek R1 chatbot that showcases the model’s response and reasoning capabilities.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face - API Documentation&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;Official API documentation for Hugging Face Spaces, helping developers interact with models and integrate them into applications.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;docs&#x2F;hub&#x2F;spaces-sdks-docker#rest-api&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face - Source Code&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;Direct access to the source code of the Deepseek R1 chatbot project on Hugging Face Spaces for those interested in contributing or learning.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;jienweng&#x2F;chatbot_v2&#x2F;tree&#x2F;main&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face - Model Card&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;Official card for the Deepseek R1 Distilled model, providing details on its functionality and training specifications.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-R1-Distill-Qwen-1.5B&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face - Deployment Guide&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;Guidelines for deploying models and applications using Hugging Face Spaces.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;docs&#x2F;hub&#x2F;spaces-overview&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face - Performance Benchmarks&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;An overview of the model performance across various tasks and benchmarks, showcasing the strengths and weaknesses of different models.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;spaces&#x2F;mteb&#x2F;leaderboard&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face - Community Discussion&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
&lt;em&gt;Join the community discussions on Hugging Face, where users can ask questions, share insights, and discuss AI-related topics.&lt;&#x2F;em&gt;&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discuss.huggingface.co&#x2F;&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Eco Finance: A Sustainable Future Prototype</title>
        <published>2024-12-29T00:00:00+00:00</published>
        <updated>2024-12-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/blog/eco-finance/"/>
        <id>https://jienweng.github.io/blog/eco-finance/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/blog/eco-finance/">&lt;p&gt;This post focuses on the Eco Finance prototype itself: the problem we targeted, the product concept, and what we learned from turning an idea into a demo under hackathon constraints. It is written as a project debrief rather than an event recap.&lt;&#x2F;p&gt;
&lt;p&gt;We participated in PayHack 2024, quite a big hackathon event with many talented individuals.&lt;&#x2F;p&gt;
&lt;p&gt;Our project, Eco Finance, focuses on the implementation of a carbon tax expected to be released in 2026. Although it initially targets specific industries, it is crucial and trending to implement this for everyone in Malaysia. Read more about the 2026 carbon tax here.&lt;&#x2F;p&gt;
&lt;p&gt;European countries are already implementing ESG-centric policies in various industries, such as the automobile industry. These are just preliminary steps; now we want to delve deeper into the subject.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;payhack-2024.vercel.app&#x2F;&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;3034bd0a-9dae-4078-80a9-a641c747b58a?format=jpeg&quot; alt=&quot;Eco Finance Interface&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;understanding-the-idea&quot;&gt;Understanding the idea&lt;&#x2F;h2&gt;
&lt;p&gt;Do you know how much carbon footprint you generate from ordering a Shopee parcel? Or how much carbon you generate by driving to work instead of taking public transport? It&#x27;s challenging for people to visualize their footprint. Hence, we&#x27;re here to make this visible to people, increasing their awareness about this issue. According to Visa, 80% of Malaysians are aware of the environmental impact of consumption. With the release of Malaysia&#x27;s largest payment gateway provider and the latest project linking banks.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;&#x2F;h2&gt;
&lt;p&gt;The OpenFinance API can seamlessly integrate people&#x27;s transaction details, allowing us to aggregate a person&#x27;s transactions and their carbon footprints. The calculation would be the emission factor times the amount, giving us the carbon footprint from those transactions.&lt;&#x2F;p&gt;
&lt;p&gt;For example, if the emission factor for a specific merchant category is 0.5 kg CO2 per RM, and a person spends RM 100, the carbon footprint would be 0.5 kg CO2&#x2F;RM * 100 RM = 50 kg CO2.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s an established merchant category code (MCC) in transaction details, where we only need to fine-tune and investigate the actual emission factors for each merchant code. This could be done by collaborating with the Department of Statistics Malaysia (DOSM) to conduct surveys and research among Malaysians. In this project, we are using dummy variables based on this ideology only.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;monetizing-the-ecosystem&quot;&gt;Monetizing the Ecosystem&lt;&#x2F;h2&gt;
&lt;p&gt;We circulate this whole ecosystem by monetizing it. We extract carbon credits from them using the formula from WORLDMETER, where the baseline of average carbon emission per person in Malaysia is 8 tons per year. With that, we can pool up carbon credits, extracted from the surplus from the calculation. There&#x27;s a proven market potential with Bursa Carbon Exchange (BCX), established on 9 Dec 2022, which is available to trade carbon credits in Malaysia. Then we sell the carbon credits to major companies in Malaysia like Petronas and Maybank to help them offset their carbon credits. &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.allenandgledhill.com&#x2F;perspectives&#x2F;publications&#x2F;bulletins-malaysia&#x2F;2023&#x2F;bursa-malaysia-launches-voluntary-carbon-market-exchange&#x2F;&quot;&gt;Read more about BCX here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;YouTube id=&quot;1QKwHFVsEXE&quot; &#x2F;&gt;
&lt;p&gt;From &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.petronas.com&#x2F;sustainability&#x2F;delivering-net-zero&quot;&gt;Petronas&lt;&#x2F;a&gt;, it&#x27;s evident that their future plan aims for net-zero carbon emission by 2050. This proves there is a market in Malaysia, and more people will enter the market and participate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;encouraging-eco-friendly-practices&quot;&gt;Encouraging Eco-Friendly Practices&lt;&#x2F;h2&gt;
&lt;p&gt;How do we encourage eco-friendly spending habits in Malaysia? We aim to attract more people, even those who are not initially interested in eco-friendly practices, to join us. We can make Malaysia greener and more sustainable, at least in the sense of ESG. We choose to reward users with healthy spending habits in terms of eco-friendly spending and reward them with cash for being environmentally friendly.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;f54512bd-f014-43ac-a1b1-39628b5990d7?format=jpeg&quot; alt=&quot;The whole business plan of eco-finance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;sustainable-business-model&quot;&gt;Sustainable Business Model&lt;&#x2F;h2&gt;
&lt;p&gt;We can summarize the business circulation here and make it sustainable as well, where we can become self-sustaining:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Collect carbon credits from users&lt;&#x2F;li&gt;
&lt;li&gt;Pool up carbon credits&lt;&#x2F;li&gt;
&lt;li&gt;Certify carbon credits with Bursa Carbon Exchange (BCX)&lt;&#x2F;li&gt;
&lt;li&gt;Sell carbon credits to companies who need them&lt;&#x2F;li&gt;
&lt;li&gt;Reward users to encourage eco-friendly spending habits.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Basically that&#x27;s it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;project-random-thingy&quot;&gt;Project Random Thingy&lt;&#x2F;h2&gt;
&lt;p&gt;Now, it&#x27;s about the random thingys of the project. We have the project hosted at this link. Although it is not fully complete, it serves as a prototype and is yet to be an MVP. Considering we had only 24 hours to complete this from idea to execution, I mean, it&#x27;s good for a first-timer. Right...?&lt;&#x2F;p&gt;
&lt;p&gt;You can access the hosted project prototype &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;payhack-2024.vercel.app&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Feel free to browse through it. If you have any questions, please email me, and I&#x27;ll personally explain it to you. You can also see the admin page by accessing &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;payhack-2024.vercel.app&#x2F;admin&quot;&gt;here&lt;&#x2F;a&gt; or by changing the &lt;code&gt;&#x2F;dashboard&lt;&#x2F;code&gt; to &lt;code&gt;&#x2F;admin&lt;&#x2F;code&gt;. It looks something like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;7ee8a2c3-551c-4726-9551-f7d6ab743391?format=jpeg&quot; alt=&quot;Admin Dashboard&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Feel free to browse through it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wrap-up&quot;&gt;Wrap-Up&lt;&#x2F;h2&gt;
&lt;p&gt;In conclusion, Eco Finance aims to make carbon footprints visible to individuals, encouraging eco-friendly spending habits and contributing to a greener Malaysia. By monetizing carbon credits and rewarding users, we create a sustainable ecosystem that benefits both the environment and the economy. It&#x27;s really kesian that we couldn&#x27;t make it to final though T.T&lt;&#x2F;p&gt;
&lt;p&gt;Hope you guys like it :D&lt;&#x2F;p&gt;
&lt;p&gt;Oh btw! Once again.. You can read the full story &lt;a href=&quot;&#x2F;posts&#x2F;first-hackathon-experience&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>First Physical Hackathon Experience</title>
        <published>2024-12-02T00:00:00+00:00</published>
        <updated>2024-12-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://jienweng.github.io/blog/first-hackathon-experience/"/>
        <id>https://jienweng.github.io/blog/first-hackathon-experience/</id>
        
        <content type="html" xml:base="https://jienweng.github.io/blog/first-hackathon-experience/">&lt;p&gt;This post records what our first physical hackathon taught us as a math-heavy team entering a software-first environment. I focus on concrete lessons from ideation, mentoring, and pitching that we can reuse in future competitions.&lt;&#x2F;p&gt;
&lt;p&gt;I found myself staring at my phone, thumb hovering over the share button. &quot;Am I really qualified for this?&quot; I thought to myself. &quot;What if we make fools of ourselves?&quot; The doubts crept in like unwanted guests. But then another voice, stronger and more determined, pushed back: &quot;When else will we get a chance like this? We might not be coders, but we know how to solve problems. Isn&#x27;t that what hackathons are really about?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;I reached out to my fellow mathematics coursemates: Janice, Roius, and Gwyn. &quot;Hey, want to do something crazy?&quot; I asked, half expecting them to laugh it off. To my surprise, their responses came quickly, filled with enthusiasm despite (or maybe because of) our collective inexperience. Only 2 of us had ever participated in a hackathon before, and Gwyn had never even written a line of code. But there we were, four mathematics students from UTAR, signing up for one of the most competitive hackathons in the country.&lt;&#x2F;p&gt;
&lt;p&gt;The looks we got when we arrived were priceless. &quot;Are you guys from Computer Science?&quot; someone asked, eyeing our team with curiosity. We exchanged glances and grinned. &quot;Nope, we&#x27;re Math students, haha...&quot; The mixture of surprise and skepticism on their faces was something I&#x27;ll never forget. In those moments, our outsider status felt both terrifying and weirdly empowering.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;25b6ad42-8235-45ae-9505-a2c296a8ca2a?format=jpeg&quot; alt=&quot;Our first breakfast together at PayHack 2024 - nervous but excited! The calm before the storm.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;November 30th marked our first day, and it was a blur of ideation and learning. We came up with an innovative idea: creating an app to visualize transaction carbon footprints, with the ability to pool and trade carbon footprint surpluses with companies in need. On paper, it sounded promising—a perfect blend of fintech and environmental consciousness.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;b16e0c66-cefb-4cc6-8bf6-de5a087cc513?format=jpeg&quot; alt=&quot;Getting grilled during our mentoring session - each question pushed us to think deeper about our solution&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Then came the intense mentoring sessions with Johan Nasir. He didn&#x27;t hold back. &quot;What happens if the carbon credits are manipulated?&quot; he&#x27;d challenge. &quot;How do you ensure the authenticity of the footprint data?&quot; Another round of rethinking. Each session felt like an intense rotan session—tough love at its finest. He&#x27;d poke holes in our solutions, push us to think deeper, and force us to confront real-world problems that actually needed solving.&lt;&#x2F;p&gt;
&lt;p&gt;The week before the hackathon was a rollercoaster. Competing against almost 100 teams from across Malaysia, we were shocked to make it to the Top 32. When we saw Johan&#x27;s name among our judges and received the news of our advancement, our excitement was through the roof. But reality quickly set in—we had a major problem. None of us had real web development experience. My knowledge was limited to Python, SQL, and vanilla HTML&#x2F;CSS&#x2F;JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;With just four days between Tuesday and Friday, we had to make crucial technical decisions while juggling our internships. After intense research, we settled on Vue.js and Flask. Janice even traveled all the way from JB to KL for this. Every evening after our internships, we&#x27;d dive into tutorials, trying to absorb as much as we could about our chosen tech stack.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;0d444922-77c4-4d91-b645-fb96b7fd5d17?format=jpeg&quot; alt=&quot;3 AM and still debugging - running on determination and coffee&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The hackathon itself was intense. When exhaustion hit, we took turns napping wherever we could. I couldn&#x27;t get a tent, so I made do with a random bench for quick 4-hour power naps before jumping back into coding. Everything seemed to be going smoothly until the morning of the submission. At 8:30 AM, just after breakfast and 90 minutes before the deadline, our backend crashed—the information couldn&#x27;t be parsed properly. In a desperate move, we had to hardcode some components just to make the submission deadline at 10 AM.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;0784bdc4-dc31-4b2b-9d5a-2e04677a9ba1?format=jpeg&quot; alt=&quot;Presenting our final product: MouManTai - tracking and trading carbon footprints from financial transactions&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While we didn&#x27;t make it to the Top 10, watching the final pitches was an eye-opening experience. The winning teams showcased solutions that were not just technically impressive but also deeply thoughtful about real-world implementation. One team&#x27;s blockchain-based remittance system particularly stood out - their attention to regulatory compliance and market research was incredible. &quot;We should have done more market validation,&quot; I thought to myself. &quot;Next time, we need to focus not just on the technical solution but on the whole business case.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;The top teams also demonstrated masterful presentation skills. Their pitches weren&#x27;t just about features - they told compelling stories about why their solutions mattered. Each slide was carefully crafted, each demo was flawlessly executed, and their responses to judges&#x27; questions showed deep understanding of both technical and business aspects. I made mental notes: &quot;Practice the pitch more. Know your numbers. Be ready for any question.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Despite not making the finals, I did win a Samsung monitor in the lucky draw, a small consolation that brought some laughs to our tired team.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;3d119ab7-d3bf-4632-8675-6fbaf1963c08?format=jpeg&quot; alt=&quot;A silver lining - winning a Samsung monitor in the lucky draw!&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Looking back now, the sleepless nights and endless debugging sessions blur together, but certain moments stand crystal clear: the late-night breakthrough when our first feature finally worked, the proud smile on Johan&#x27;s face during our final presentation, and most importantly, the unshakeable bond formed between four mathematicians who dared to dream.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn.cosmos.so&#x2F;266c9030-58ad-4536-9e60-c88edb6df8a9?format=jpeg&quot; alt=&quot;Jien Weng, Janice, Gwyn and Roius&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To Janice, Roius, and Gwyn: thank you for taking this leap of faith with me. For believing that our mathematical minds could contribute something meaningful to the tech world. To Johan: your guidance went beyond mentorship—you showed us that innovation comes from daring to be different. And to PayNet and JomHack: thank you for creating a space where even mathematics students could discover their potential in technology.&lt;&#x2F;p&gt;
&lt;p&gt;They say the best stories come from stepping out of your comfort zone. Well, we didn&#x27;t just step—we took a giant leap. And while our first hackathon journey has ended, something tells me this is just the beginning of our adventure in the tech world. The equations and formulas we&#x27;ve studied for years are no longer just abstract concepts—they&#x27;re tools waiting to be applied in the vast playground of technology.&lt;&#x2F;p&gt;
&lt;p&gt;After all, who says mathematicians can&#x27;t be hackers too? Sometimes the best innovations come from those who dare to cross the boundaries between disciplines, who bring fresh perspectives to old problems. And maybe, just maybe, that&#x27;s exactly what the tech world needs more of.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
