<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[ZhiZhi Gewu]]></title><description><![CDATA[For I am and always have been one of those natures who must be guided by reason, whatever the reason may be which upon reflection appears to me to be the best.

– Plato’s Crito]]></description><link>https://www.zhizhi-gewu.com</link><image><url>https://www.zhizhi-gewu.com/img/substack.png</url><title>ZhiZhi Gewu</title><link>https://www.zhizhi-gewu.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 27 Jun 2026 17:06:10 GMT</lastBuildDate><atom:link href="https://www.zhizhi-gewu.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[KY John]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[zhizhigewu@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[zhizhigewu@substack.com]]></itunes:email><itunes:name><![CDATA[KY John]]></itunes:name></itunes:owner><itunes:author><![CDATA[KY John]]></itunes:author><googleplay:owner><![CDATA[zhizhigewu@substack.com]]></googleplay:owner><googleplay:email><![CDATA[zhizhigewu@substack.com]]></googleplay:email><googleplay:author><![CDATA[KY John]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The Last Mile From Analysis (Answers) to a Data Mart (Assets)]]></title><description><![CDATA[An analyst&#8217;s notebook is not a pipeline &#8212; and the gap is wider than it looks]]></description><link>https://www.zhizhi-gewu.com/p/the-last-mile-from-analysis-answers</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/the-last-mile-from-analysis-answers</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 21 Jun 2026 08:16:16 GMT</pubDate><content:encoded><![CDATA[<h2><strong>An analyst&#8217;s notebook is not a pipeline &#8212; and the gap is wider than it looks</strong></h2><p>Most useful analysis dies in a notebook. Someone writes a query that answers a real question, the answer ships in a deck, and the query is never seen again. When the same question comes back a month later, someone rewrites it &#8212; slightly differently, with a slightly different number.</p><p>The fix is well understood: turn that query into a durable asset in a data mart, scheduled, validated, and reusable. The problem is that the distance between &#8220;a query that returns the right answer&#8221; and &#8220;a pipeline asset an organization can depend on&#8221; is much larger than it appears, and the people best placed to close it &#8212; the analysts who understand the business logic &#8212; are usually the least incentivized to do so.</p><p>I spent a stretch recently converting a piece of validated analysis into a proper mart table, with an AI coding assistant as a pair. It changed my view of where the real barriers are, what AI actually moves, and &#8212; importantly &#8212; what it doesn&#8217;t.</p><h2><strong>The technical barrier: a wall made of small cliffs</strong></h2><p>A working analytical query and a production asset are different <em>kinds</em> of object. Crossing between them means absorbing a stack of concerns that have nothing to do with the question you set out to answer:</p><ul><li><p><strong>One-shot becomes idempotent.</strong> A <code>SELECT</code> you run once becomes a write that must produce the same result every time it runs, overwrite cleanly on re-run, and target exactly one partition per run.</p></li><li><p><strong>Implicit schema becomes an explicit contract.</strong> Column names, types, and order stop being incidental and become a contract that has to stay in sync across the SQL, a schema spec, and a DDL definition &#8212; with automated checks that fail the build if they drift.</p></li><li><p><strong>&#8220;Run it now&#8221; becomes scheduling.</strong> You inherit rolling date macros, dev/prod parametrization, and a surprising number of ways to get the date arithmetic subtly wrong.</p></li><li><p><strong>Standalone becomes dependent.</strong> The asset has upstreams, and the platform wants you to declare them &#8212; readiness signals, ordering, what waits on what.</p></li><li><p><strong>&#8220;Trust me&#8221; becomes CI (Continuous Integration).</strong> Naming rules, schema-match checks, dependency checks, catalog generation &#8212; a gauntlet your change must pass before anyone will look at it.</p></li><li><p><strong>One run becomes a backfill.</strong> Now you must reason about history: how far back, in what order, and whether re-running is safe.</p></li></ul><p>None of these is hard in isolation. Each is a small cliff. Stacked together, they&#8217;re a wall &#8212; and every one is an opportunity to ship something subtly wrong.</p><h2><strong>The incentive barrier: the one nobody puts in the diagram</strong></h2><p>The technical barrier is the one people talk about. The incentive barrier is the one that actually keeps the asset from getting built.</p><p>Analysts are measured on answers and on speed, not on maintainable assets. Productionizing a query is slow, its payoff is deferred, and the credit largely accrues to whoever uses the asset later. The platform itself has a learning curve, and that cost is paid by the individual for a benefit that is mostly collective. The &#8220;correct&#8221; alternative &#8212; hand it to a data engineering team &#8212; has its own friction: a queue, a spec, a handoff in which the very business context that made the analysis correct gets lossy, and the analyst demoted to a ticket-filer chasing their own request.</p><p>So the rational analyst, most of the time, just keeps the query in the notebook. The asset never gets built. The knowledge stays ephemeral. This is not a failure of diligence; it&#8217;s a predictable response to the incentives. The barrier was never only &#8220;can they?&#8221; &#8212; it&#8217;s also &#8220;is it worth it to them?&#8221; And often, honestly, it isn&#8217;t.</p><h2><strong>Where AI moves the line</strong></h2><p>This is the part that surprised me. The assistant was most valuable not as a code generator but as a <strong>translator and navigator</strong> &#8212; it collapsed the part of the wall that is pure tax.</p><ul><li><p><strong>Boilerplate and compliance.</strong> Scaffolding the asset to the repo&#8217;s conventions, keeping the schema/DDL/spec in lockstep, generating the dependency wiring, and grinding the change through the CI gates until they were green. The &#8220;translation tax&#8221; from analysis idiom to engineering idiom dropped sharply.</p></li><li><p><strong>Platform navigation.</strong> The tribal knowledge &#8212; how deploys work, which date-macro forms silently misbehave, what a readiness marker actually does &#8212; is normally extracted from an engineer&#8217;s calendar. Here it was a conversation.</p></li><li><p><strong>Tight validation loops.</strong> Replicate the trusted number, diff, hypothesize the cause of a mismatch, fix, re-run. Fast.</p></li><li><p><strong>Operational scaffolding.</strong> When a backfill ran, the assistant watched the outputs land and flagged &#8212; quickly &#8212; that something was off.</p></li></ul><p>The effect on the <em>incentive</em> math is the real story. When the fixed cost of productionizing drops, more analyses clear the bar where benefit exceeds cost. Work that wasn&#8217;t worth a handoff becomes worth doing yourself. The analyst can own more of the last mile &#8212; <strong>for localized, individual or small-team needs</strong> &#8212; without a full throw-over-the-wall.</p><h2><strong>Where AI did not &#8212; and could not &#8212; carry the weight</strong></h2><p>Here&#8217;s the part I want to be honest about, because the temptation is to stop at the previous section.</p><p>Every consequential moment in the project was a matter of <strong>judgment and skepticism</strong>, not typing &#8212; and those were mine to supply.</p><p>A backfill &#8220;succeeded&#8221; and wrote a tidy set of partitions. They were all empty. Understanding <em>why</em> required knowing that the table was anchored on a single point in a synchronized billing cycle, so most calendar dates legitimately have no rows &#8212; the empties were correct, not a bug. The assistant helped me run that down quickly, but only after I distrusted a green checkmark and asked the question.</p><p>Two tables looked joinable on their partition date. They aren&#8217;t &#8212; the same entity is anchored on different events in each, so the only correct join key is the entity id, and joining on the date would have silently produced almost nothing. That&#8217;s a contract-level fact about the data model, not something to infer from syntax.</p><p>An upstream turned out to be a view with no completion signal, which meant the &#8220;obvious&#8221; way to wire a dependency would have made the job wait forever &#8212; and the right response depended on understanding the difference between scheduled runs and backfills. And when I floated a redesign that would have made backfilling trivially easy, the right call was to <em>not</em> do it, because it would have traded away a clean one-row-per-entity contract and invited double-counting. That&#8217;s an architectural value judgment.</p><p>The pattern is consistent: <strong>AI accelerates generation and investigation; the human supplies the right questions, the skepticism, and the trade-off decisions about contracts, semantics, and scale.</strong> The moments that mattered were someone saying &#8220;this number looks wrong&#8221; or &#8220;but these segments don&#8217;t behave the same&#8221; &#8212; and <em>then</em> the tooling helped chase it down.</p><p>And note where the guardrails came from. The schema checks, the dependency rules, the review, the conventions the assistant dutifully satisfied &#8212; engineers built those. The AI operated <em>within</em> that frame; it did not invent it. Take the frame away and the same assistant will happily generate a swamp of plausible, subtly wrong, unmaintained assets.</p><h2><strong>Not replacement &#8212; redistribution</strong></h2><p>I don&#8217;t think any of this means an organization needs less data engineering. It means the boundary of what an analyst can responsibly own has moved.</p><p>The roughly 80% that is boilerplate and platform-navigation becomes self-serve. The 20% that is architecture, shared contracts, reliability, and scale stays with engineers &#8212; and with the analyst&#8217;s own judgment, now better-informed because the tooling made it cheap to investigate. The healthy version of this is fewer trivial tickets in the engineering queue, more analyses becoming durable assets, and engineers freed for the genuinely hard platform work that makes analyst self-serve <em>safe</em> in the first place. The better the guardrails, the more an analyst can be trusted to do alone &#8212; so investing in the platform matters <em>more</em>, not less.</p><p>The failure version is just as easy to reach: hand people a code generator without the guardrails or the skepticism, and you get a lake of confident, broken pipelines that nobody owns. The win is conditional.</p><p>So the honest summary is small and, I think, durable. The barrier to turning analysis into engineering was never only technical &#8212; it was also about incentives, and AI lowers both. An analyst can now cross more of that last mile on their own. But the bridge still rests on engineering foundations they didn&#8217;t build, and on judgment no model supplied for them.</p>]]></content:encoded></item><item><title><![CDATA[I Let a Neural Network Do My Feature Engineering — Then Made It Explain Itself]]></title><description><![CDATA[An experiment in automated feature discovery for credit risk: a sequential model trained on raw payment history, interrogated with explainability tools, and translated into 52 plain tabular features that closed the entire gap to the neural model on holdout.]]></description><link>https://www.zhizhi-gewu.com/p/i-let-a-neural-network-do-my-feature</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/i-let-a-neural-network-do-my-feature</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 13 Jun 2026 01:35:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ea791998-aa11-41ac-a89b-3f801f339deb_1560x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>An experiment in automated feature discovery for credit risk: a sequential model trained on raw payment history, interrogated with explainability tools, and translated into 52 plain tabular features that closed the entire gap to the neural model on holdout. Spread across two calendar days &#8212; with the human time measured in hours, not weeks.</em></p><div><hr></div><h2><strong>The job that eats our weeks</strong></h2><p>If you build behavioral risk models, you know the drill. The signal lives in history tables &#8212; millions of payment events, monthly card balances, bureau records &#8212; and the only way to feed that to a scorecard or GBM is to collapse it into <strong>aggregates</strong>: mean days past due in the last 6 months, max utilization ever, count of late payments in 12.</p><p>The pain is combinatorial. Every variable &#215; every window (3/6/12/24 months) &#215; every statistic (mean, max, count, trend) &#215; every condition you can dream up. Thousands of candidates. Weeks of analyst time. And when you ship, a quiet doubt remains: <em>what did we not think of?</em></p><p>So I ran an experiment around a simple inversion: <strong>what if the model does the searching, and the analyst only does the reading?</strong></p><h2><strong>The idea: the network is the search engine, not the product</strong></h2><p>The plan had three stages, each with a hard, falsifiable criterion:</p><ol><li><p><strong>Train a sequential neural network on near-raw history</strong> &#8212; no hand-crafted aggregates anywhere in its inputs &#8212; and require it to beat a meaningful bar: validation AUC &gt; 0.78. The data: a private competition dataset modeled on the well-known Home Credit Kaggle data &#8212; an application table plus six history tables; 246K applications, 8.1% bad (default) rate, 13.6M payment events plus card, point-of-sale and credit bureau tables. (The raw data can&#8217;t be redistributed, but the public Kaggle Home Credit dataset has the same shape if you want to reproduce the method.)</p></li><li><p><strong>Interrogate the frozen model</strong> with explainability tooling and write down, in plain language, what it learned.</p></li><li><p><strong>Rebuild the findings as ordinary tabular features</strong>, hand them to a plain LightGBM, and measure how much of the network&#8217;s edge they keep &#8212; on a holdout set touched exactly once.</p></li></ol><p>The crucial design decision: the neural network was never the deliverable. The deliverable was the <strong>feature list</strong>. The deployable model at the end is a boring, governance-friendly GBM over named columns &#8212; the network exists only to search a hypothesis space no analyst can cover by hand.</p><p>One constraint made the whole thing meaningful: the network&#8217;s inputs were raw event streams (due date, paid date, amounts, statuses&#8230;), with only &#8220;unit fixes&#8221; added &#8212; paid minus due = delay, paid over due = ratio. <strong>No windows, no decays, no counts-over-time.</strong> If we had fed it engineered aggregates, interrogating it would just have echoed our own assumptions back at us.</p><h2><strong>What actually happened (including the failures)</strong></h2><p>A disclosure that matters for everything that follows: I directed an AI coding agent (Claude Code) through the entire build &#8212; it wrote the pipeline, ran the experiments, and drafted the analyses; my own keyboard time across the two days went into design decisions at the start, a long stream of &#8220;explain this to me&#8221; challenges in the middle, and reading findings and approving feature names at the end. The honest log matters more than the highlight reel:</p><p><strong>The first model failed the gate.</strong> It stalled at 0.767 validation AUC &#8212; barely above the applications-only baseline of 0.763 (validation). Before burning more GPU time, I had a set of independent AI reviewer agents audit the data pipeline, each instructed to try to <em>refute</em> the others&#8217; bug reports so only solid findings survived. Three <em>silent</em> scaling bugs were confirmed. The worst: a clipping step had collapsed every applicant&#8217;s age to the same constant. <strong>Age &#8212; one of the strongest variables in retail credit &#8212; had been deleted from the model</strong>, and nothing crashed; the model trained merrily at a plausible-looking AUC. If you take one engineering lesson from this post: scaling bugs don&#8217;t announce themselves. Audit the <em>transformed</em> arrays &#8212; <code>nunique()</code>, clip-saturation &#8212; not just the raw data.</p><p><strong>Then the neural fusion head hit a ceiling.</strong> With the bugs fixed and standard tricks applied (an auxiliary head forcing the behavioral embedding to be predictive on its own; randomly hiding the application features during training so the model can&#8217;t lean on them), the network&#8217;s own head still topped out around 0.769 (validation). Neural nets are simply mediocre at flat tabular data, where GBMs shine. The fix was architectural honesty: keep the network as the <em>sequence reader</em>, freeze its 128-number behavioral embedding, and let a LightGBM do the fusion &#8212; raw application columns and embedding columns side by side. That, plus a small ensemble of encoder variants, passed the gate at <strong>0.7816 (validation)</strong>.</p><h2><strong>The interrogation: making the black box talk</strong></h2><p>This is the part I&#8217;d never done before, and it&#8217;s where the experiment earns its keep. Three tools plus one final probe, deliberately different in mechanism, all run on the frozen model:</p><p><strong>1. Attribution (Integrated Gradients)</strong> decomposes the behavioral score into per-event, per-channel contributions &#8212; a points table for a model that has no points table. It told us <em>where</em> the model looks: payment amounts and delays dominate, concentrated in the most recent ~10&#8211;15 installments; the monthly view is read as a <em>current portfolio snapshot</em> (the top channel was scheduled future installments &#8212; forward commitment burden, not delinquency); bureau signal concentrates in how recently accounts were opened. Best of all, plotting attribution against time produced a <strong>forgetting curve per data source</strong> &#8212; payments decay fast, applications and bureau records slowly. The model had learned different memory lengths for different data, something a uniform &#8220;last 12 months&#8221; window would have flattened.</p><p><strong>2. Counterfactual perturbation</strong> is stress-testing aimed at the model: edit real histories surgically, re-score, measure. Injecting a fresh 3-payment late streak: <strong>+14.8 percentage points</strong> of predicted default probability in the top-risk decile. The same streak placed 18 months back: +0.9. That ratio <em>is</em> a feature specification &#8212; recency-discounted delinquency with a half-life around five to six payments, which became a literal column (<code>&#931; late &#215; 0.89^k</code>, k = payments back from today). Deleting the <em>older</em> half of someone&#8217;s history &#8212; mostly clean events &#8212; <strong>raised</strong> their risk: tenure itself is protective. And the null results mattered just as much: chronic mild underpayment, days-past-due (DPD) on monthly statements, utilization <em>trend</em> &#8212; all moved the model barely at all. Each null is strong evidence (not proof &#8212; the network can miss things too) that a feature family isn&#8217;t worth building for this portfolio, with a measured justification attached.</p><p><strong>3. Embedding analysis</strong> treats the model&#8217;s internal customer summary (the encoders&#8217; 128-number embeddings, concatenated) as a map of &#8220;behavior space&#8221;. Clustering it produced six archetypes whose real default rates ran <strong>2.4% to 25.3%</strong> &#8212; and here&#8217;s the insight I keep retelling: median DPD was ~zero in <em>every</em> archetype. 92% of eventual defaulters had no statement delinquency at all in the prior year. The 10&#215; risk separation came from <em>soft</em> gradients &#8212; how early people pay (10 days early vs 5), how deep their history runs, their refusal history. The model invented a segmentation no DPD-based rule could see.</p><p>The embedding gave us one final probe, the strangest of the lot: take the model&#8217;s main internal risk axis (the combination of embedding numbers that best tracks its score) and regress it on ~23 statistics we <em>could</em> name. They explained 39% of its variance (linear R&#178;). The other 61% was the model knowing something we had no words for &#8212; so we pulled the customers at the extremes of that unexplained direction and read their raw files like underwriters. Two patterns emerged that none of our standard vocabulary captured: <strong>same-day bursts of refused applications</strong> (8&#8211;14 refusals, specific rejection codes) and <strong>heavy external debt sitting on a thin internal file</strong>. Both became features. Both are the kind of thing you find in year three of working a portfolio, not week one.</p><h2><strong>The verdict</strong></h2><p>Each finding became one or more named features &#8212; 52 in total, every one with a provenance note linking it to the finding that motivated it, implemented in plain pandas with no neural network anywhere at inference. Then the only test that matters, on the untouched holdout (holdout numbers run a little below the validation figures quoted above &#8212; expected, and exactly why the holdout exists):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ptw-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ptw-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 424w, https://substackcdn.com/image/fetch/$s_!ptw-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 848w, https://substackcdn.com/image/fetch/$s_!ptw-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 1272w, https://substackcdn.com/image/fetch/$s_!ptw-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ptw-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png" width="916" height="362" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:362,&quot;width&quot;:916,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51093,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/201822271?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ptw-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 424w, https://substackcdn.com/image/fetch/$s_!ptw-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 848w, https://substackcdn.com/image/fetch/$s_!ptw-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 1272w, https://substackcdn.com/image/fetch/$s_!ptw-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6771fbc-333d-4c5b-aca9-070e2ad57b57_916x362.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Gap closure: 102% on the point estimates</strong> &#8212; read that as a statistical tie: the 0.0006 AUC by which the features &#8220;beat&#8221; the network is well within noise on a single holdout, and I make nothing of it. The claim that matters is that the interpretable features kept essentially <em>everything</em> the network found (a second check agrees: the features-alone model matches the network&#8217;s behavior-only score head almost exactly). And no, that doesn&#8217;t make the network redundant: it found the features. Without it, the same list would have required exactly the manual search this experiment set out to replace.</p><h2><strong>Why I think this changes the feature-development workflow</strong></h2><p><strong>Time.</strong> Two calendar days end-to-end, and most of that was compute, not human attention. The human&#8217;s job is compressed into: design decisions at the start, reading findings in the middle, naming features at the end.</p><p><strong>Features arrive with evidence, not hunches.</strong> Every feature came with a measured functional form (the decay constant was <em>fitted by the model and read off a dose-response curve</em>, not guessed), a confirmed direction (useful for monotonicity constraints and model documentation), and &#8212; the underrated half &#8212; <em>licensed omissions</em>: measured evidence that the network, which saw everything, found no use for certain feature families, so nobody burns a sprint building them &#8220;just in case&#8221;.</p><p><strong>Insights are a first-class output, not a by-product.</strong> The defaulters-look-clean finding, the archetype segmentation, the credit-appetite signal in bureau account openings &#8212; those are portfolio knowledge with uses far beyond one model: strategy, monitoring, collections prioritization. Manual feature engineering produces features; this produces features <em>and</em> an explanation of the portfolio.</p><p><strong>The deployable artifact stays boring.</strong> Nothing about production changes: a GBM, named columns, documented provenance. For anyone working under model governance, that&#8217;s the difference between &#8220;interesting research&#8221; and &#8220;something I can actually ship&#8221;.</p><h2><strong>What this is not</strong></h2><p>Honesty section. The method is not human-free: someone chooses the raw representation, reads the extreme cases, and names the features &#8212; that&#8217;s the point, not a flaw (translation into human language requires a human). The perturbation effects are <em>within-model</em> sensitivities, not causal claims about customers. The probe is linear, so &#8220;61% unexplained&#8221; is an upper bound on mystery &#8212; some of it is interactions of known quantities. This is one dataset, one domain; the playbook needs adapting for very rare outcomes, drifting populations (use out-of-time holdouts!), or domains where the risk signal is what <em>stopped</em> happening. And it needs label volume &#8212; supervised embeddings want thousands of positives.</p><p>Also: I ran the interrogation as a single pass because that was the experiment&#8217;s design. In practice you&#8217;d loop &#8212; generate features, measure the remaining gap to the network, re-interrogate <em>targeted at the cases where the features and the network disagree most</em>, and repeat until converged. The disagreement cases are exactly where the unextracted signal hides.</p><h2><strong>Try it</strong></h2><p>The code and analysis are public: the full pipeline, the interrogation scripts, the findings documents with per-feature provenance, a tutorial written for risk professionals with no neural-network background, and a generalized, self-contained playbook for applying the method to other domains (churn, fraud, claims). The raw dataset itself can&#8217;t be redistributed &#8212; but the public Kaggle Home Credit data has the same shape if you want to run the method end to end:</p><p><strong><a href="https://github.com/kychanbp/auto-feature-gen-with-nn-interogation">https://github.com/kychanbp/auto-feature-gen-with-nn-interogation</a></strong></p><p>My takeaway after two days: feature engineering didn&#8217;t disappear. It moved up a level &#8212; from <em>writing</em> thousands of candidate aggregates to <em>reading</em> what a model that saw everything chose to care about. I suspect that inversion is where the interesting work is moving.</p>]]></content:encoded></item><item><title><![CDATA[I knew the rules of Go. Writing them for a machine taught me I didn't understand them.]]></title><description><![CDATA[I&#8217;m rebuilding AlphaGo from scratch &#8212; 9&#215;9 board, no shortcuts &#8212; to actually understand how it works instead of just running someone else&#8217;s repo.]]></description><link>https://www.zhizhi-gewu.com/p/i-knew-the-rules-of-go-writing-them</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/i-knew-the-rules-of-go-writing-them</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 30 May 2026 02:00:50 GMT</pubDate><content:encoded><![CDATA[<p>I&#8217;m rebuilding AlphaGo from scratch &#8212; 9&#215;9 board, no shortcuts &#8212; to actually <em>understand</em> how it works instead of just running someone else&#8217;s repo. The spark was <a href="https://www.youtube.com/watch?v=X_ZVSPcZhtw&amp;t=8968s">this podcast</a>, which walks through building AlphaGo from scratch and makes the whole thing feel approachable enough to actually try. Stage 1 was the unglamorous part: the Go engine. Place a stone, capture dead groups, and score the board. I know the rules of Go, so I figured this would be a quick warm-up.</p><p>It was not. And the reason it wasn&#8217;t is the whole point of this post.</p><h2><strong>The machine takes nothing for granted</strong></h2><p>Knowing the rules of Go is enough to follow a game: &#8220;these stones are captured,&#8221; &#8220;that&#8217;s obviously territory,&#8221; &#8220;you can&#8217;t replay there, that&#8217;s ko.&#8221; But knowing a rule well enough to <em>follow</em> it lets you lean on intuition for the messy parts &#8212; you recognize the situations without ever pinning down the exact mechanics, because as a human, you never have to.</p><p>A computer has none of that slack. To write the engine, I had to turn every piece of &#8220;obvious&#8221; knowledge into a rule precise enough that a machine with zero common sense could follow it. That process is where I discovered how <em>little</em> I actually understood the rules I thought I knew.</p><p>Here are the moments that humbled me.</p><h2><strong>Pitfall 1: The bug hid exactly where I didn&#8217;t look</strong></h2><p>My first function just listed a point&#8217;s neighbors. I wrote it, tested the top-left corner, an edge, and the center &#8212; all correct. Moved on.</p><p>Then a test of the <em>bottom-right</em> corner returned neighbors that were off the board. The bug: I&#8217;d written <code>r + 1 &lt;= size</code> instead of <code>r + 1 &lt; size</code> &#8212; a classic off-by-one. My earlier tests had all exercised the <em>top</em> and <em>left</em> edges, where a different check fires. The bug lived in the one region my tests never touched.</p><p>Lesson, burned in permanently: <strong>a passing test suite doesn&#8217;t mean correct code &#8212; it means correct code </strong><em><strong>in the cases you tested</strong></em><strong>.</strong> Bugs hide in the gaps.</p><h2><strong>Pitfall 2: the rule I knew, in an order I never questioned</strong></h2><p>Anyone who knows Go &#8220;knows&#8221; you remove stones that have no liberties. But here&#8217;s a question I&#8217;d never had to answer: when you place a stone, <em>what order</em> do you resolve things in?</p><p>It turns out the order is load-bearing. You must remove the <strong>opponent&#8217;s</strong> dead stones <strong>before</strong> checking whether your <em>own</em> move was suicide. Why? Because a move that looks like suicide &#8212; your stone dropping into a spot surrounded by enemies &#8212; can actually be a <em>capture</em>. Removing the opponent first frees up the very liberty that saves your stone.</p><p>I knew the capture rule cold &#8212; but I&#8217;d never had to ask in what <em>order</em> captures and suicide resolve, because when you&#8217;re just following along, the board sorts itself out and you never notice the sequence. Writing it down for the machine forced the rule into focus.</p><h2><strong>Pitfall 3: the simple rule that wasn&#8217;t &#8212; ko</strong></h2><p>I knew ko: you can&#8217;t immediately recapture and put the board back the way it was. Simple.</p><p>Except the engine needs something stronger &#8212; <em>positional superko</em>: you can&#8217;t recreate <strong>any</strong> board position that has <em>ever</em> occurred in the game, not just the last one. And when I asked <em>why</em>, the answer reframed how I think about the whole project: <strong>self-play games must be guaranteed to terminate.</strong> AlphaGo trains by playing millions of games and scoring the result. A game that loops forever can never be scored. Simple ko only stops the 2-move loop; longer cycles (triple ko and friends) would still hang. Superko forbids <em>all</em> repetition, so every game ends.</p><p>I knew the rule. I&#8217;d never known what it was <em>for</em>.</p><p>(Bonus humbling: my first superko test &#8220;proved&#8221; the feature was broken. The board kept repeating. The bug wasn&#8217;t in the engine &#8212; it was in my <em>test</em>: I&#8217;d built the position by poking the board directly, which skipped the bookkeeping that records past positions. The engine was fine; I&#8217;d lied to it. Even your tests can be wrong.)</p><h2><strong>Pitfall 4: three functions, three jobs, and the danger of blur</strong></h2><p>The trickiest stretch was a refactor: I needed one function to <em>mutate</em> the board, another to check a move&#8217;s legality without changing anything, and a third to <em>commit</em> a real move. Three jobs.</p><p>I kept letting them bleed into each other. One version had the &#8220;check&#8221; function quietly <em>committing</em> moves, so generating the list of legal moves would have played 80 phantom moves. Another had the commitment silently, not placing the stone at all. Each bug came from the same root: a function doing more than its one job.</p><p>This is the kind of clean separation an AI would write correctly on the first try. And that&#8217;s precisely why I&#8217;m glad I wrote it myself &#8212; <em>badly</em>, then fixed it. Reasoning through why the board stopped updating taught me something about side effects and single-responsibility that a correct-on-the-first-try answer never would have.</p><h2><strong>The actual lesson: AI can write the code. It can&#8217;t transfer the understanding.</strong></h2><p>Through all of this, I had an AI tutor &#8212; but deliberately <em>not</em> as a code generator. It refused to hand me answers. It asked leading questions, reviewed what I wrote, pointed out the region my tests didn&#8217;t cover, and made me <em>predict</em> every output before running it. I found or fixed every bug above myself.</p><p>I could have typed &#8220;write me a 9&#215;9 Go engine&#8221; and had a working one in seconds. I&#8217;d also have understood exactly nothing. The understanding doesn&#8217;t live in the code &#8212; it lives in the <em>struggle of producing the code</em>: choosing the data structures, predicting outputs, and especially <strong>debugging</strong>. Debugging is where the abstract rule collides with a concrete failure, and the lesson finally lands.</p><p>And the sharpest version of this, for me: <strong>implementing the rules of Go taught me the rules of Go</strong> &#8212; at a precision that simply <em>knowing</em> them never required. Knowing a rule well enough to follow it and understanding it well enough to build it are different kinds of knowledge, and only the second one transfers to the next problem.</p><p>That&#8217;s the whole reason I&#8217;m building this by hand. AlphaGo&#8217;s real magic &#8212; the search, the self-improving network &#8212; is still ahead of me (Stage 2 is Monte Carlo Tree Search). But if Stage 1 taught me anything, it&#8217;s that the value isn&#8217;t in <em>having</em> the engine. It&#8217;s in becoming the kind of person who could have written it.</p><p><em>Next up: teaching a computer to search &#8212; without a neural net yet.</em></p><div><hr></div><p><em>A note on process: this post was drafted by AI and reviewed, corrected, and signed off by me, which, given the argument above, feels worth saying out loud. The code, the bugs, and the understanding are mine; the write-up is a collaboration. Using AI to help narrate the journey is fine. Using it to skip the journey would have defeated the entire point.</em></p>]]></content:encoded></item><item><title><![CDATA[Why Singapore has no policy rate, the yen collapsed, and Brazil paid you 14% to hold a rising currency?]]></title><description><![CDATA[How Every Country Actually Runs Its Money]]></description><link>https://www.zhizhi-gewu.com/p/why-singapore-has-no-policy-rate</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/why-singapore-has-no-policy-rate</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 24 May 2026 08:55:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HW-o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HW-o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HW-o!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 424w, https://substackcdn.com/image/fetch/$s_!HW-o!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 848w, https://substackcdn.com/image/fetch/$s_!HW-o!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 1272w, https://substackcdn.com/image/fetch/$s_!HW-o!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HW-o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png" width="1280" height="1659" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1659,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HW-o!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 424w, https://substackcdn.com/image/fetch/$s_!HW-o!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 848w, https://substackcdn.com/image/fetch/$s_!HW-o!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 1272w, https://substackcdn.com/image/fetch/$s_!HW-o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cece73c-ee73-4929-914b-a28babfc88f5_1280x1659.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Exchange rates and interest rates shape our everyday lives. The exchange rate affects our purchasing power; the interest rate affects borrowing costs and asset prices. If you earn Singapore dollars, a trip to Japan looks attractive. If you buy a house in Singapore, you&#8217;ll notice the mortgage rate moves in line with rates in the United States. Yet most of us don&#8217;t know the mechanism underneath &#8212; how these variables move and interact.</p><h3><strong>The Impossible Trilemma</strong></h3><p>The impossible trilemma states that a country can have only two of three things: control of its <strong>interest rate</strong>, control of its <strong>exchange rate</strong>, and <strong>free movement of capital</strong>.</p><p>When capital flows freely, the no-arbitrage relationship between interest rates and the exchange rate is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;1+i_d = \\frac{F}{S}(1+i_f)&quot;,&quot;id&quot;:&quot;IVNKWSLSWT&quot;}" data-component-name="LatexBlockToDOM"></div><p></p><p>where S is the spot rate (domestic currency per unit of foreign), <em>F</em> the forward rate, i_d the domestic interest rate, and  i_f the foreign rate.</p><p>The formula says: holding one unit of domestic currency (which grows to 1+i_d&#8203;) must equal converting it to foreign currency at the spot (1/S), earning the foreign rate (1+i_f), and converting it back through a forward contract (F).</p><p>The equation is just a <em>constraint</em> &#8212; it doesn't say which variable drives the others. That's a <em>choice</em>. A country pins one lever, and the formula then determines the rest. </p><p>Take Singapore. The MAS uses the <em>exchange rate</em>, not the interest rate, as its main policy tool &#8212; managing the Singapore dollar against a trade-weighted basket (the S$NEER) within a band, usually on a gently appreciating path. When the market expects the Singapore dollar to strengthen, the formula does the rest: F &lt; S , so F/S&lt;1, which pushes SGD interest rates <em>below</em> foreign rates. The MAS never sets the domestic rate directly &#8212; it falls out of the currency policy.</p><p>But here&#8217;s the crucial limit: <strong>this formula pins down the </strong><em><strong>forward</strong></em><strong> rate, not the </strong><em><strong>future spot</strong></em><strong> rate.</strong> The forward is locked in by covered arbitrage; where the spot actually goes depends on expectations, risk premia, capital flows, and policy credibility.</p><p>Japan makes the point. Its interest rate is far below the United States&#8217;. The formula puts the yen at a <em>forward premium</em> &#8212; so you might expect it to strengthen. In reality, the yen depreciates.</p><h3><strong>Covered and Uncovered Interest Parity</strong></h3><p>The formula above is <strong>covered interest parity</strong> &#8212; &#8220;covered&#8221; because you hedge the currency risk with a forward. It&#8217;s a no-arbitrage benchmark, and in deep, liquid markets it holds very closely. (It can drift only when funding stress or balance-sheet constraints open a &#8220;cross-currency basis,&#8221; as they have at times since 2008.) But notice what it does and doesn&#8217;t say: it pins the <em>forward</em> price to the interest-rate gap. It says nothing about where the <em>spot</em> rate will actually go.</p><p>That second question belongs to <strong>uncovered interest parity</strong> &#8212; the <em>unhedged</em> version. In theory, a low-yielding currency should appreciate just enough to offset its lower interest rate. In practice this often fails, because investors demand risk premia, chase carry, or react to safe-haven and policy shifts.</p><p>Japan is the clearest case. Its low rate puts the yen at a forward premium, but in the spot market traders <strong>borrow cheap yen to buy higher-yielding foreign assets</strong>, creating selling pressure that pushes the yen <em>down</em>. Switzerland shows the other side: despite equally low rates, the franc tends to <em>appreciate</em>, because it enjoys <strong>safe-haven demand</strong>.</p><p><strong>Notice the pattern: the same low interest rate sends the yen down and the franc up. The interest rate alone never tells you which way a currency moves &#8212; what matters is which lever the country chose to control, and what it leaves to the market.</strong></p><h3><strong>Around the World</strong></h3><p><strong>China</strong> has restricted the free flow of capital, which lets it steer both the interest rate and the exchange rate more independently &#8212; because the arbitrage channel that would otherwise link them is constrained. The link isn&#8217;t fully severed (trade flows, approved investment channels, and the offshore CNH market still matter), but it&#8217;s loose enough that domestic rates and the yuan can diverge from what open-market arbitrage would dictate.</p><p><strong>Taiwan</strong> faces recurring appreciation pressure from its strong external position and capital inflows. To lean against excessive TWD appreciation and protect export competitiveness, its central bank buys foreign currency and accumulates reserves (now over US$600 billion), while using monetary and prudential tools to manage domestic liquidity.</p><p><strong>Brazil</strong>, as of 2026, shows the opposite of Japan. With a high policy rate, the real&#8217;s forwards imply depreciation under interest parity &#8212; yet the spot real has <em>appreciated</em>, because high carry, improved sentiment, and capital inflows more than offset the forward-implied decline.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!j9rx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!j9rx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 424w, https://substackcdn.com/image/fetch/$s_!j9rx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 848w, https://substackcdn.com/image/fetch/$s_!j9rx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 1272w, https://substackcdn.com/image/fetch/$s_!j9rx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!j9rx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png" width="1456" height="1830" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1830,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:477241,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/199038955?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!j9rx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 424w, https://substackcdn.com/image/fetch/$s_!j9rx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 848w, https://substackcdn.com/image/fetch/$s_!j9rx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 1272w, https://substackcdn.com/image/fetch/$s_!j9rx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6529f1b2-1d57-4952-a920-ccfaa2d7254c_2085x2621.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3><strong>Closing</strong></h3><p>Three levers &#8212; interest rate, exchange rate, free capital &#8212; and you can pin only two. The third is whatever the market makes of it. So the next time your Singapore mortgage tracks the Fed, or your yen holiday gets cheaper, you&#8217;re watching the same equation at work &#8212; quietly linking interest rates, exchange rates, and the flow of capital across the world.</p><p></p>]]></content:encoded></item><item><title><![CDATA[What's Really Going On in Machine Learning? A Simplest Example by Stephen Wolfram]]></title><description><![CDATA[From Neural Networks to Rule Arrays: A Minimal Model of Learning]]></description><link>https://www.zhizhi-gewu.com/p/whats-really-going-on-in-machine</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/whats-really-going-on-in-machine</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 16 May 2026 02:01:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cIg2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a Neural Net (NN)<sup>[1]</sup>, the standard setup includes an input layer, hidden layers that sit between the input and output layer, and neurons that contain the weights, a bias, and a reference to the activation function. The architecture defines the computation graph (visually, that is, the connections between nodes); the forward pass evaluates that graph for a given input. Back-propagation computes gradients of the loss with respect to the parameters; an optimizer such as SGD or Adam then updates the weights.</p><p>To understand it, we can simplify as many components as possible. First, let&#8217;s restrict connections to local neighbors only &#8212; for example, the Mesh Neural Net. Second, let&#8217;s make everything discrete instead of continuous. The simplest discrete analog is a rule array &#8212; a cellular-automaton-like grid in which each cell independently picks which rule to apply from a small fixed panel (see Appendix for an introduction to cellular automata). The value of a cell is discrete: either 1 or 0. The value of the cell depends on the left, center, and right cells preceding it by a rule set. Analogously, each cell in the grid is a neuron in NN. The row in the grid is the layers. The computation graph is constructed as a mesh network that depends on local neighbors. Random mutation is analogous to a training/optimization procedure, but unlike gradient descent, it does not use gradients. It is closer to stochastic hill climbing or evolutionary search. The value of the cell is similar to activation, the value the neuron produces after the forward pass computation.</p><h2>Implementation</h2><p>First, we defined the grid size to be <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;(T, W) = (60, 60)&quot;,&quot;id&quot;:&quot;lt7ujmdxly&quot;}" data-component-name="InlineLatexToDOM"></span> where <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;T&quot;,&quot;id&quot;:&quot;ltm8k76wje&quot;}" data-component-name="InlineLatexToDOM"></span> is the number of time steps and <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;W&quot;,&quot;id&quot;:&quot;ltedom0ies&quot;}" data-component-name="InlineLatexToDOM"></span> is the width of the grid.</p><p>Second, we define a function to generate the rule set, converting decimal to binary (see Appendix for details).</p><pre><code><code>def rule_to_lut(n):
    """Convert rule number to a lookup table."""
    return [ (n // 2**i) % 2 for i in range(8) ]</code></code></pre><p>We will mainly use rule 4 and rule 146. The lookup tables are:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Uvk3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Uvk3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 424w, https://substackcdn.com/image/fetch/$s_!Uvk3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 848w, https://substackcdn.com/image/fetch/$s_!Uvk3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 1272w, https://substackcdn.com/image/fetch/$s_!Uvk3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Uvk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png" width="1456" height="659" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:659,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50200,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/197846245?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Uvk3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 424w, https://substackcdn.com/image/fetch/$s_!Uvk3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 848w, https://substackcdn.com/image/fetch/$s_!Uvk3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 1272w, https://substackcdn.com/image/fetch/$s_!Uvk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ae35242-15af-49f0-8f22-c811b62d465e_1635x740.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Third, we construct a rule array that indicates whether to apply rule 4 or rule 146 to each cell, and initialize it to zeros.</p><pre><code><code>rule_array = [[0] * W for _ in range(T)]</code></code></pre><p>Fourth, we define a function to evolve the row one by one in the grid, starting with the initial configuration <code>[0000...010...0000]</code>. Then, we loop through the cell from top to bottom and left to right, and apply the corresponding rule set (Rule 4 or Rule 146) based on the value in the rule array (0 or 1).</p><pre><code><code>def evolve(rule_array, initial_row):
    T = len(rule_array)
    W = len(initial_row)
    pat = [list(initial_row)]  # start with the initial row
    for t in range(T):  # loop through time steps
        new_row = []
        for w in range(W):  # loop through cell width
            left = pat[t][w-1] if w-1 &gt;= 0 else pat[t][W-1]  # if hitting left wall, treat as the rightmost cell
            center = pat[t][w]
            right = pat[t][w+1] if w+1 &lt; W else pat[t][0]  # if hitting right wall, treat as the leftmost cell
            idx = left * 4 + center * 2 + right  # convert binary to decimal index

            if rule_array[t][w] == 0:
                new_row.extend([rule_4_lut[idx]])  # apply rule 4
            else:
                new_row.extend([rule_146_lut[idx]])  # apply rule 146
        pat.append(new_row)

    return pat</code></code></pre><p>Fifth, we have to define our loss function. Our goal is to have a Cellular Automata that stops at a certain lifetime, say 30. The definition of lifetime is the index of the last nonzero row. The loss is just the absolute difference between our target and our lifetime.</p><pre><code><code>def lifetime(pat):
    """Calculate the lifetime of the pattern."""

    for t in range(len(pat) - 1, -1, -1):  # start from the last time step; -1 means go backwards; -1 means stop at the first time step
        if any(pat[t]):
            return t
    return 0

def loss(rule_array, target):
    pat = evolve(rule_array, initial_row)
    return abs(lifetime(pat) - target)</code></code></pre><p>Finally, we have to write our training loop that mutates the rule array, similar to how we find the weights in the NN. We start by initializing the <code>rule_array</code> randomly, then flip the bit in the array if the loss is less than or equal to the current loss. The reason to use <code>&lt;=</code> instead of <code>&lt;</code> is that by including the run with the same loss, we avoid getting stuck in the same mutation (weights).</p><pre><code><code>def train(target, max_steps=10000):

    random_rule_array = [[random.choice([0, 1]) for _ in range(W)] for _ in range(T)]  # Initialize a random rule array
    current_loss = loss(random_rule_array, target)
    loss_history = [current_loss]

    for step in range(max_steps):
        # Randomly flip a bit in the rule array
        t = random.randint(0, T-1)
        w = random.randint(0, W-1)
        random_rule_array[t][w] = 1 - random_rule_array[t][w]  # 1-0 = 1 and 1-1 = 0, so this effectively flips the bit

        new_loss = loss(random_rule_array, target)

        if new_loss == 0:
            current_loss = new_loss
            loss_history.append(current_loss)
            break
        elif new_loss &lt;= current_loss:
            current_loss = new_loss
            loss_history.append(current_loss)
        else:  # Revert the change if it doesn't improve the loss
            random_rule_array[t][w] = 1 - random_rule_array[t][w]
            loss_history.append(current_loss)

    return random_rule_array, loss_history</code></code></pre><h2>Results and Implications</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fKdi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fKdi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 424w, https://substackcdn.com/image/fetch/$s_!fKdi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 848w, https://substackcdn.com/image/fetch/$s_!fKdi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 1272w, https://substackcdn.com/image/fetch/$s_!fKdi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fKdi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png" width="790" height="390" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:390,&quot;width&quot;:790,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:25742,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/197846245?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fKdi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 424w, https://substackcdn.com/image/fetch/$s_!fKdi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 848w, https://substackcdn.com/image/fetch/$s_!fKdi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 1272w, https://substackcdn.com/image/fetch/$s_!fKdi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F839156fb-a700-4d65-9790-56b9470ac5bb_790x390.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cIg2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cIg2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 424w, https://substackcdn.com/image/fetch/$s_!cIg2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 848w, https://substackcdn.com/image/fetch/$s_!cIg2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 1272w, https://substackcdn.com/image/fetch/$s_!cIg2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cIg2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif" width="1200" height="660" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:660,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:735826,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/197846245?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cIg2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 424w, https://substackcdn.com/image/fetch/$s_!cIg2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 848w, https://substackcdn.com/image/fetch/$s_!cIg2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 1272w, https://substackcdn.com/image/fetch/$s_!cIg2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e4f70a3-4fb0-4e9c-a6d3-ac10ec0a2766_1200x660.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>We can observe that the simplified NN construction did &#8220;learn.&#8221; However, the solutions seem to have no pattern. Somehow, some rules did minimize the loss function. In fact, the simplest solution is to apply only one rule-146 in row 30.</p><p>The main messages from the book by Stephen are:</p><ul><li><p>Many trained systems do not work by discovering clean, human-readable mechanisms. Training is an adaptive process that searches an enormous computational space and retains any behavior that happens to align with the constraints.</p></li><li><p>ML works because of computational irreducibility (the concept introduced by Stephen Wolfram in <em>A New Kind of Science</em>). You cannot shortcut their behavior with a closed-form theory. ML exploits this by exploring the vast, irreducible computational space, which almost always finds one whose dynamics satisfy the need.</p></li><li><p>There is a trade-off between explainability (for example, a closed-form solution) and performance, because if we require explainability, we restrict the search in computational irreducible space.</p></li></ul><h2>Appendix: Cellular Automata</h2><p>A one-dimensional Cellular Automata (CA) of two states starts with an array of ones and zeros. The next generation (the next row) is generated by a set of rules that take two adjacent cells and itself as inputs, and output the next cell.</p><p>Since there are <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;2^3 = 8&quot;,&quot;id&quot;:&quot;ltjx54ok9a&quot;}" data-component-name="InlineLatexToDOM"></span> combinations of inputs, the rule set can be expressed in 8 digits. For example, binary 00011110 = decimal 30; this is called Rule 30 (see Appendix: Base Conversion for more information). Therefore, there are, in total, <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;2^8 = 256&quot;,&quot;id&quot;:&quot;ltya8lkrow&quot;}" data-component-name="InlineLatexToDOM"></span> possible rule sets.</p><p>For example, the following is the space-time pattern<sup>[2]</sup> of Rule 30:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zkSb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zkSb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 424w, https://substackcdn.com/image/fetch/$s_!zkSb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 848w, https://substackcdn.com/image/fetch/$s_!zkSb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 1272w, https://substackcdn.com/image/fetch/$s_!zkSb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zkSb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif" width="400" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:13788,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/197846245?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zkSb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 424w, https://substackcdn.com/image/fetch/$s_!zkSb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 848w, https://substackcdn.com/image/fetch/$s_!zkSb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 1272w, https://substackcdn.com/image/fetch/$s_!zkSb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95fe6ed-4967-4c90-8753-105ee8e79749_400x400.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Appendix: Base Conversion</h2><p>In Python, the conversion can be achieved in one line by answering whether <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;2^i&quot;,&quot;id&quot;:&quot;ltxy5xhykh&quot;}" data-component-name="InlineLatexToDOM"></span> is in the sum. <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;2^7&quot;,&quot;id&quot;:&quot;lt4ckemwny&quot;}" data-component-name="InlineLatexToDOM"></span> is in the sum, while <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;2^6&quot;,&quot;id&quot;:&quot;ltiu9rc4we&quot;}" data-component-name="InlineLatexToDOM"></span> is not, as we saw above. It is easier to see in base-10 first. To answer whether <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;40&quot;,&quot;id&quot;:&quot;ltdivqr3hu&quot;}" data-component-name="InlineLatexToDOM"></span> is in the sum of <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;146&quot;,&quot;id&quot;:&quot;lt2thbdau4&quot;}" data-component-name="InlineLatexToDOM"></span> (<span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;146 = 100 + 40 + 6&quot;,&quot;id&quot;:&quot;ltoh9vcgtw&quot;}" data-component-name="InlineLatexToDOM"></span>) &#8212; in other words, what is the tens digit of <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;146&quot;,&quot;id&quot;:&quot;lt93agfb7g&quot;}" data-component-name="InlineLatexToDOM"></span> &#8212; we can first chop off the last digit (mathematically, <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;146 \\,//\\, 10&quot;,&quot;id&quot;:&quot;ltq1e8d9sp&quot;}" data-component-name="InlineLatexToDOM"></span><sup>[4]</sup>) and look at the rightmost digit of what&#8217;s left (mathematically, <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;146 \\,//\\, 10 \\bmod 10 = 4&quot;,&quot;id&quot;:&quot;ltibtp75fz&quot;}" data-component-name="InlineLatexToDOM"></span><sup>[5]</sup>). Applying the same logic to base-2 of number <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;n&quot;,&quot;id&quot;:&quot;lty6teokb4&quot;}" data-component-name="InlineLatexToDOM"></span>, it becomes <span class="latex-inline" data-attrs="{&quot;persistentExpression&quot;:&quot;n \\,//\\, 2^i \\bmod 2&quot;,&quot;id&quot;:&quot;lt13h3s56i&quot;}" data-component-name="InlineLatexToDOM"></span>.</p><h2>Appendix: Code</h2><p>Polish by Claude:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c5a0306b-d61b-4aa5-99f0-e2d9497820cd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">"""
Wolfram &#167;5 replication &#8212; train a rule array to a target lifetime.

Replicates the lifetime-training experiment from Stephen Wolfram,
*What's Really Going On in Machine Learning? Some Minimal Models* (2025),
Section 5 ("Machine Learning in Discrete Rule Arrays").

Each cell of a T x W rule array selects between two elementary cellular
automata (rule 4, decay; rule 146, chaotic) to apply at that (time, space)
position. From a single black cell in row 0, the pattern evolves forward T
steps. Training searches for a rule array whose pattern survives EXACTLY
`target` steps, using single-point mutation hill-climbing with the rule
"accept iff new loss &lt;= old loss". The equality is what lets the search
drift across plateaus until it stumbles into a downhill move.

Phenomena reproduced:
  1. Random mutation actually trains.
  2. Trained rule arrays look like noise (no obvious mechanism).
  3. Different runs find different solutions.
  4. Plateau-and-breakthrough learning curves.
  5. Engineered solutions exist but training does not discover them.

Outputs (under --out-dir, default = script directory):
  - learning_curves.png        loss vs. mutation step, all runs overlaid
  - trained_runs.png           trained rule arrays + spacetime patterns
  - trained_spacetime.gif      animated row-by-row evolution

Usage:
    python wolfram_lifetime.py
    python wolfram_lifetime.py --t 30 --w 30 --target 15 --max-steps 10000
    python wolfram_lifetime.py --no-gif
"""

from __future__ import annotations

import argparse
import random
from pathlib import Path

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter


# -----------------------------------------------------------------------------
# Elementary cellular automata
# -----------------------------------------------------------------------------

def rule_to_lut(n: int) -&gt; list[int]:
    """8-entry lookup table for an elementary (k=2, r=1) CA.
    Indexed by (left &lt;&lt; 2) | (center &lt;&lt; 1) | right."""
    return [(n &gt;&gt; i) &amp; 1 for i in range(8)]


RULE_4_LUT = rule_to_lut(4)      # class 2 -- decay
RULE_146_LUT = rule_to_lut(146)  # class 3 -- chaotic


def evolve(rule_array: list[list[int]], initial_row: list[int]) -&gt; list[list[int]]:
    """Run the 1D CA forward, selecting rule 4 or rule 146 per cell.

    `rule_array[t][w]` selects which rule applies at time t, column w
    (0 -&gt; rule 4, 1 -&gt; rule 146). Width is cyclic.

    Returns the spacetime pattern: a (T+1) x W list of lists.
    """
    T = len(rule_array)
    W = len(initial_row)
    pat = [list(initial_row)]
    for t in range(T):
        prev = pat[t]
        new_row = [0] * W
        for w in range(W):
            # cyclic neighborhood: modulo handles both walls
            idx = (prev[(w - 1) % W] &lt;&lt; 2) | (prev[w] &lt;&lt; 1) | prev[(w + 1) % W]
            lut = RULE_4_LUT if rule_array[t][w] == 0 else RULE_146_LUT
            new_row[w] = lut[idx]
        pat.append(new_row)
    return pat


def lifetime(pat: list[list[int]]) -&gt; int:
    """Largest row index containing any black cell. 0 if dead from the start."""
    for t in range(len(pat) - 1, -1, -1):
        if any(pat[t]):
            return t
    return 0


# -----------------------------------------------------------------------------
# Training: single-point mutation hill climbing
# -----------------------------------------------------------------------------

def loss(rule_array: list[list[int]], initial_row: list[int], target: int) -&gt; int:
    return abs(lifetime(evolve(rule_array, initial_row)) - target)


def train(
    target: int,
    T: int,
    W: int,
    initial_row: list[int],
    max_steps: int = 20_000,
    seed: int | None = None,
) -&gt; tuple[list[list[int]], list[int]]:
    """Hill-climb a rule array to lifetime = target.

    Starts from a random binary rule array of shape (T, W). Each iteration
    flips one random cell; the flip is kept iff `new_loss &lt;= cur_loss` (the
    `&lt;=` is what lets the search cross plateaus). Early-stops on loss == 0.

    Returns (trained_rule_array, loss_history). The history has one entry
    per iteration, so its length equals the number of mutations attempted
    (+ 1 for the initial state).
    """
    rng = random.Random(seed)
    arr = [[rng.randint(0, 1) for _ in range(W)] for _ in range(T)]
    cur_loss = loss(arr, initial_row, target)
    history = [cur_loss]

    for _ in range(max_steps):
        i, j = rng.randrange(T), rng.randrange(W)
        arr[i][j] = 1 - arr[i][j]  # flip
        new_loss = loss(arr, initial_row, target)
        if new_loss &lt;= cur_loss:
            cur_loss = new_loss
        else:
            arr[i][j] = 1 - arr[i][j]  # revert
        history.append(cur_loss)
        if cur_loss == 0:
            break

    return arr, history


# -----------------------------------------------------------------------------
# Visualization
# -----------------------------------------------------------------------------

def plot_learning_curves(runs, target: int, out_path: Path) -&gt; None:
    """Loss vs. mutation step for all runs, overlaid."""
    fig, ax = plt.subplots(figsize=(8, 4))
    for k, (_, hist) in enumerate(runs):
        ax.plot(hist, lw=0.9, alpha=0.85, label=f"run {k+1} ({len(hist)-1} steps)")
    ax.set_xlabel("mutation step")
    ax.set_ylabel(f"loss = |lifetime - {target}|")
    ax.set_title(f"Learning curves - {len(runs)} independent runs, target={target}")
    ax.legend(fontsize=8)
    ax.grid(alpha=0.3)
    fig.tight_layout()
    fig.savefig(out_path, dpi=150, bbox_inches="tight")
    plt.close(fig)


def plot_trained_runs(runs, initial_row, target: int, out_path: Path) -&gt; None:
    """Trained rule arrays (top) and their spacetime patterns (bottom)."""
    n = len(runs)
    fig, axes = plt.subplots(2, n, figsize=(3 * n, 7.2), constrained_layout=True)
    if n == 1:
        axes = axes.reshape(2, 1)

    for k, (arr, hist) in enumerate(runs):
        axes[0, k].imshow(arr, cmap="binary", aspect="equal",
                          interpolation="nearest", vmin=0, vmax=1)
        axes[0, k].set_title(f"run {k+1}: rule array\n({len(hist)-1} mutations)",
                             fontsize=10)
        axes[0, k].set_xticks([]); axes[0, k].set_yticks([])
        if k == 0:
            axes[0, k].set_ylabel("LEARNED\nrule array\n(black=146, white=4)",
                                  fontsize=9)

        pat = evolve(arr, initial_row)
        axes[1, k].imshow(pat, cmap="binary", aspect="equal",
                          interpolation="nearest", vmin=0, vmax=1)
        axes[1, k].axhline(target + 0.5, color="red", lw=1.0, ls="--",
                           alpha=0.8, label=f"target row {target}")
        axes[1, k].set_title(f"spacetime (lifetime {lifetime(pat)})", fontsize=10)
        axes[1, k].set_xticks([]); axes[1, k].set_yticks([])
        if k == 0:
            axes[1, k].set_ylabel("RESULTING\nspacetime\n(time flows down)",
                                  fontsize=9)
            axes[1, k].legend(loc="lower right", fontsize=7)

    fig.suptitle(
        f"Wolfram &#167;5 replication - {n} independent training runs, target lifetime = {target}\n"
        "Each run learns a different rule array; spacetimes are unrelated but all die at the target row.",
        fontsize=11,
    )
    fig.savefig(out_path, dpi=150, bbox_inches="tight")
    plt.close(fig)


def animate_trained_runs(
    runs, initial_row, target: int, out_path: Path, fps: int = 5
) -&gt; None:
    """Animated gif: rule array (top) and spacetime (bottom) reveal in lockstep,
    one row per frame. Rule-array row t produces spacetime row t+1.
    """
    n = len(runs)
    arrs = [np.array(arr) for arr, _ in runs]
    pats = [np.array(evolve(arr, initial_row)) for arr, _ in runs]
    lifetimes = [lifetime(p.tolist()) for p in pats]
    mutations = [len(h) - 1 for _, h in runs]
    n_frames = pats[0].shape[0]  # T + 1

    fig, axes = plt.subplots(2, n, figsize=(3 * n, 6.6))
    if n == 1:
        axes = axes.reshape(2, 1)
    fig.subplots_adjust(top=0.86, bottom=0.04, left=0.06, right=0.98,
                        hspace=0.25, wspace=0.10)

    def render(t):
        for k in range(n):
            ax_top = axes[0, k]
            ax_top.clear()
            ax_top.set_xticks([]); ax_top.set_yticks([])
            ax_top.set_title(
                f"run {k+1}: rule array\n({mutations[k]} mutations)", fontsize=10
            )
            display_arr = np.zeros_like(arrs[k])
            if t &gt; 0:
                display_arr[:t] = arrs[k][:t]
            ax_top.imshow(display_arr, cmap="binary", aspect="equal",
                          interpolation="nearest", vmin=0, vmax=1)
            if k == 0:
                ax_top.set_ylabel("LEARNED\nrule array\n(black=146, white=4)",
                                  fontsize=9)

            ax_bot = axes[1, k]
            ax_bot.clear()
            ax_bot.set_xticks([]); ax_bot.set_yticks([])
            ax_bot.set_title(f"spacetime (lifetime {lifetimes[k]})", fontsize=10)
            display_pat = np.zeros_like(pats[k])
            display_pat[: t + 1] = pats[k][: t + 1]
            ax_bot.imshow(display_pat, cmap="binary", aspect="equal",
                          interpolation="nearest", vmin=0, vmax=1)
            ax_bot.axhline(target + 0.5, color="red", lw=1.0, ls="--",
                           alpha=0.8, label=f"target row {target}")
            if k == 0:
                ax_bot.set_ylabel("RESULTING\nspacetime\n(time flows down)",
                                  fontsize=9)
                ax_bot.legend(loc="lower right", fontsize=7)

        fig.suptitle(
            f"Wolfram &#167;5 replication - {n} independent training runs, "
            f"target lifetime = {target}  -  step {t}/{n_frames - 1}\n"
            "Each run learns a different rule array; spacetimes are unrelated "
            "but all die at the target row.",
            fontsize=11,
            y=0.985,
        )

    anim = FuncAnimation(fig, render, frames=n_frames, interval=1000 // fps)
    anim.save(out_path, writer=PillowWriter(fps=fps))
    plt.close(fig)


# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

def main() -&gt; None:
    ap = argparse.ArgumentParser(
        description="Train a rule array to a target lifetime (Wolfram &#167;5)."
    )
    ap.add_argument("--t", type=int, default=60, help="time steps (rule-array rows)")
    ap.add_argument("--w", type=int, default=60, help="width (rule-array cols)")
    ap.add_argument("--target", type=int, default=30, help="target lifetime")
    ap.add_argument("--max-steps", type=int, default=20_000,
                    help="max mutations per run")
    ap.add_argument("--n-runs", type=int, default=4,
                    help="number of independent training runs")
    ap.add_argument("--seed", type=int, default=7,
                    help="base seed (per-run seeds are derived)")
    ap.add_argument("--out-dir", type=Path,
                    default=Path(__file__).resolve().parent,
                    help="where to save figures")
    ap.add_argument("--no-gif", action="store_true",
                    help="skip the animated gif (faster)")
    args = ap.parse_args()

    if not 0 &lt;= args.target &lt;= args.t:
        raise SystemExit(f"target must be in [0, {args.t}]; got {args.target}")

    initial_row = [0] * args.w
    initial_row[args.w // 2] = 1

    rng = random.Random(args.seed)
    seeds = [rng.randrange(10**9) for _ in range(args.n_runs)]

    print(f"Training {args.n_runs} runs at T={args.t}, W={args.w}, target={args.target}...")
    runs = []
    for k, s in enumerate(seeds):
        arr, hist = train(
            args.target, args.t, args.w, initial_row,
            max_steps=args.max_steps, seed=s,
        )
        lt = lifetime(evolve(arr, initial_row))
        print(f"  run {k+1} seed={s}: final loss={hist[-1]}, "
              f"lifetime={lt}, mutations={len(hist) - 1}")
        runs.append((arr, hist))

    args.out_dir.mkdir(parents=True, exist_ok=True)
    plot_learning_curves(runs, args.target, args.out_dir / "learning_curves.png")
    plot_trained_runs(runs, initial_row, args.target,
                      args.out_dir / "trained_runs.png")
    print(f"Saved learning_curves.png and trained_runs.png to {args.out_dir}")

    if not args.no_gif:
        animate_trained_runs(runs, initial_row, args.target,
                             args.out_dir / "trained_spacetime.gif")
        print(f"Saved trained_spacetime.gif to {args.out_dir}")


if __name__ == "__main__":
    main()
</code></pre></div><div><hr></div><p><strong>Footnotes</strong></p><p>[1] If readers are not familiar with NN, here is a great introduction: <a href="https://www.youtube.com/watch?v=VMj-3S1tku0&amp;t=582s">3Blue1Brown &#8212; But what </a><em><a href="https://www.youtube.com/watch?v=VMj-3S1tku0&amp;t=582s">is</a></em><a href="https://www.youtube.com/watch?v=VMj-3S1tku0&amp;t=582s"> a neural network?</a>.</p><p>[2] The column is space, and the row is time.</p><p>[3] In the case of a list in Python, we usually write from left to right.</p><p>[4] Integer division.</p><p>[5] Modulo.</p>]]></content:encoded></item><item><title><![CDATA[Interchange Fee and the Universal Acceptance of Cards]]></title><description><![CDATA[Payment is fundamentally a two-sided market. Cardholders want more merchants accepting their cards. Merchants want more cardholders. Increasing the number of merchants in the network comes with costs on the acquirer but also benefits the cardholders, and vice versa.]]></description><link>https://www.zhizhi-gewu.com/p/interchange-fee-and-the-universal</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/interchange-fee-and-the-universal</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 09 May 2026 08:00:45 GMT</pubDate><content:encoded><![CDATA[<p>Payment is fundamentally a two-sided market<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. Cardholders want more merchants accepting their cards. Merchants want more cardholders. Increasing the number of merchants in the network comes with costs on the acquirer<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> but also benefits the cardholders, and vice versa. In economic terms, it is called externalities.</p><p>We, therefore, cannot analyze the payments industry by looking at only one side. The total cost of payment equals the sum of the issuer&#8217;s cost<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>, the acquirer&#8217;s cost, and other frictions. Unfortunately, the cost structure is quite imbalanced for credit payment. The marginal cost of processing an additional transaction to issuers typically includes the cost of funds, cost of risk<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>, payment fraud, and customer incentives<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>. The marginal cost to acquirers is essentially zero once the infrastructure, such as a POS machine, is set up, ignoring transaction fees paid to the card scheme and issuers<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. Increasing the number of cardholders benefits acquirers more because the acquirer keeps the MDR<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a> from more transactions without doing anything extra.</p><p>Negotiations between issuers and acquirers on cost sharing are infeasible if universal acceptance is the goal. The number of bilateral agreements needed in the network, assuming only pure issuers and acquirers exist and their numbers are the same, is n&#178;. Card schemes, such as VISA and Mastercard, internalize externalities and reduce the transaction costs of negotiating bilateral agreements, thereby making universal card acceptance possible. In the court case between National Bancard Corporation (NaBANCO) and VISA in the 1980s<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a>, the court concluded that &#8220;The IRF<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a> is a mechanism by which VISA ensures the universality of its card, not a price fixing device to squeeze out entrepreneurs,&#8221; and &#8220;redistribution of revenues or costs is a must for the continued existence of the product.&#8221;</p><p>The court case also addressed the market in which VISA operates. NaBANCO believed there were three distinct markets: card issuing, merchant servicing, and interchange for receivables. VISA believed there was only a single market that included substitutes, such as cash, cheque, ATM cards, Amex, and Diners Club. The market definition dispute matters because interchange costs cannot be viewed solely as merchant costs. If customers can switch among substitutes, charging interchange fees shifts usage across payment instruments rather than simply lowering merchant cost.</p><p>In some markets, regulators cap interchange fees, including Spain, Australia, the United States, and the European Union. The International Center for Law &amp; Economics has written a paper on <em><a href="https://laweconcenter.org/resources/the-effects-of-price-controls-on-payment-card-interchange-fees-a-review-and-update/">The Effects of Price Controls on Payment Card Interchange Fees: A Review and Update</a></em>. The empirical evidence is mixed and institutionally contested. Critics of interchange caps argue that caps reduce issuer revenue, weaken rewards, and lead to incomplete merchant pass-through. Regulators such as the European Commission, however, argue that caps lowered merchant service charges and produced consumer benefits through lower prices or improved retail services.</p><p>In Southeast Asia, QR payment has become an important digital payment method. Unlike China, Central Banks in the Region act as intermediaries, providing national QR infrastructure and effectively setting the interchange fee to zero and capping the MDR to a very low level. The inclusion of credit issuers inevitably reduces. However, the Central Banks seem to optimize for payment-rail inclusion more than credit-rail inclusion and are determined to transition from a cash economy to a digital economy.</p><p>The framework, therefore, predicts which side has a structural advantage in each market. Where MDR and interchange are high, credit issuers can participate profitably in off-us transactions<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-10" href="#footnote-10" target="_self">10</a>. Where MDR is compressed, and interchange is zero or near zero, acquirers and payment-rail operators may still benefit from transaction volume, but credit issuers face weaker unit economics unless a separate credit monetization layer exists, such as higher consumer interest, merchant-funded BNPL fees, on-us ecosystems, or issuing credit cards.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Two-sided market does not mean it involves two parties: a buyer and a seller. It means the total volume depends on the number of participants on the other side.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Enabling merchants to accept cards.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Availing buyer the means to send payments. Be it debit, credit, cheque, or cash.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Payment methods involve credit facilities, such as credit cards.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Individuals tend to be more price-sensitive than merchants.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>VISA or Mastercard charged acquirers a card scheme fee for using the network and an interchange fee to balance the costs.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>Merchant Discount Rate: the fee charged to merchants for processing the payment.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>National Bancard Corp. (NaBanco) v. Visa USA, Inc., 779 F. 2d 592 - Court of Appeals, 11th Circuit 1986.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><p>Issuer reimbursement fee: another name for interchange fee.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-10" href="#footnote-anchor-10" class="footnote-number" contenteditable="false" target="_self">10</a><div class="footnote-content"><p>The merchants are not acquired by the issuers.</p></div></div>]]></content:encoded></item><item><title><![CDATA[What 50 years of Singapore property prices tell us about risk]]></title><description><![CDATA[Why "average return" lies, why one quarter in 1993 still distorts a 35-year statistic, and what this means for anyone planning to buy property]]></description><link>https://www.zhizhi-gewu.com/p/what-50-years-of-singapore-property</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/what-50-years-of-singapore-property</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 25 Apr 2026 08:07:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!EQE6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>[Ideas are mine but analysis and texts are generated by AI]</p><p>Imagine two friends, Ahmad and Bao. Both decide to buy a private apartment in Singapore. Same building, same size. Ahmad buys in 1996 Q3. Bao buys in 2004 Q1. They both hold for five years.</p><p>Ahmad&#8217;s apartment, by 2001, is worth about 30% less than what he paid. He still has his mortgage. He&#8217;s underwater for almost a decade.</p><p>Bao&#8217;s apartment, by 2009, is worth about 45% more than what he paid. He&#8217;s the smart investor at every dinner party.</p><p>Same asset class. Same country. Same five-year holding period. Wildly different outcomes.</p><p>Most people, looking at this, will say &#8220;Ahmad got unlucky.&#8221; Or &#8220;Bao timed the market well.&#8221; Both are wrong, in an interesting way. The honest explanation is statistical, and once you see it, you&#8217;ll never read a &#8220;long-run average return&#8221; line in a property advert the same way again.</p><p>This piece walks through what 50 years of Singapore property data actually look like, what statisticians call fat tails, and the single most important &#8212; and most under-appreciated &#8212; fact about long-term property risk.</p><h2>The &#8220;average return&#8221; story</h2><p>Open any property-marketing brochure and you&#8217;ll see something like: &#8220;Singapore private property has appreciated by an average of 6.4% per year over the past 50 years.&#8221;</p><p>That&#8217;s not wrong. We pulled 50 years of quarterly data from SingStat &#8212; the URA Private Residential Property Price Index, from 1975 Q1 to 2025 Q4 &#8212; and the average comes out to about +1.57% per quarter, which annualises to roughly +6.4%. So far, so consistent.</p><p>The trouble is that the average is one of the least useful numbers you can compute about property returns. Here&#8217;s the actual time series:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s3Wq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s3Wq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 424w, https://substackcdn.com/image/fetch/$s_!s3Wq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 848w, https://substackcdn.com/image/fetch/$s_!s3Wq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 1272w, https://substackcdn.com/image/fetch/$s_!s3Wq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s3Wq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png" width="1300" height="650" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:650,&quot;width&quot;:1300,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:88384,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/195420447?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s3Wq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 424w, https://substackcdn.com/image/fetch/$s_!s3Wq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 848w, https://substackcdn.com/image/fetch/$s_!s3Wq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 1272w, https://substackcdn.com/image/fetch/$s_!s3Wq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa81f7bc3-c102-45e3-bc15-3b5899e9e34f_1300x650.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The top panel is the price index. The bottom panel is the quarterly log-returns &#8212; basically, how much the index moved each quarter. Notice anything?</p><p>That bottom chart is not a tame, well-behaved process. It has periods of intense activity (the 1980&#8211;81 boom, the 1993&#8211;96 boom, the 1997&#8211;98 Asian crisis, the 2008 GFC dip and bounce) and long stretches of relative calm. The quarterly returns range from &#8722;15.2% (worst, 2009 Q1) to +24.4% (best, 1981 Q1). A single quarter can easily move the price by more than the average annual return.</p><p>Calling the average &#8220;+6.4%/yr&#8221; technically true and practically misleading is what statisticians call a Mediocristan-vs-Extremistan problem. We&#8217;ll come back to that.</p><h2>The thing we don&#8217;t talk about: kurtosis</h2><p>Here is the single most useful number for thinking about risk in fat-tailed worlds, and almost nobody outside finance ever encounters it: kurtosis.</p><p>(Pronounced &#8220;kur-TOH-sis.&#8221; Greek.)</p><p>It&#8217;s a measure of how much of the action is in the extremes versus the middle of a distribution. The bigger it is, the more the rare events dominate.</p><p>For a &#8220;normal&#8221; bell-curve distribution &#8212; the kind you learned about in school &#8212; kurtosis is 3.</p><p>For Singapore private residential property quarterly returns? 6.5.</p><p>For Singapore HDB resale flat returns? 18.2.</p><p>The HDB number is genuinely shocking. It is a direct symptom of one specific quarter: 1993 Q2, when HDB resale prices jumped by 27% in three months as a speculative boom rolled through public housing. That one observation, three decades later, still accounts for 74% of the total kurtosis of the entire HDB return distribution since 1990.</p><p>Translation in plain English: when you compute &#8220;the kurtosis of HDB returns&#8221; using 35 years of quarterly data, three-quarters of the answer comes from one quarter. The other 142 quarters together explain only the remaining 26% of it.</p><p>This is what &#8220;fat-tailed&#8221; means in practice. A single, unusual event dominates a statistic that&#8217;s supposed to summarise 35 years.</p><h2>A picture is worth a thousand standard deviations</h2><p>Here is the same point in chart form. The bars compare four Singapore property indices on four different &#8220;fat-tailedness&#8221; measures:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!THBf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!THBf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 424w, https://substackcdn.com/image/fetch/$s_!THBf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 848w, https://substackcdn.com/image/fetch/$s_!THBf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 1272w, https://substackcdn.com/image/fetch/$s_!THBf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!THBf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png" width="1456" height="416" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:416,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:80306,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/195420447?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!THBf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 424w, https://substackcdn.com/image/fetch/$s_!THBf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 848w, https://substackcdn.com/image/fetch/$s_!THBf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 1272w, https://substackcdn.com/image/fetch/$s_!THBf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac5a381-49d2-4491-93aa-2095fcb60a6b_1820x520.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The red dashed lines show what a &#8220;normal-ish&#8221; distribution would look like.</p><p>Pearson kurtosis should be 3 for normal. SG indices range 5.6 to 18.2. Max quartic share &#8212; how much of the kurtosis is driven by one observation &#8212; should be about 0.05 for normal. SG indices range 0.31 to 0.74. Student-T fitted df, a sophisticated fat-tail measure where lower means fatter, with values near 2 indicating the underlying variance estimate is unreliable: SG indices 2.13 to 2.51. Hill &#945; tail exponent, where lower means heavier tail and below 4 means kurtosis itself is mathematically infinite in the population: SG indices 2.17 to 2.91.</p><p>Across every measure, every SG property index is in the &#8220;fat-tailed&#8221; zone. None of them resemble the bell curve that most financial planning quietly assumes.</p><h2>Why this matters, in dollars</h2><p>Most retirement planning, mortgage stress-testing, and &#8220;how much can I afford?&#8221; calculations implicitly assume returns behave like a normal distribution. They use words like &#8220;expected return &#177; standard deviation&#8221; and reason about probabilities the way you&#8217;d reason about coin flips.</p><p>In a normal-distribution world, a quarter that&#8217;s 5&#963; off the mean is essentially impossible. About 1 in 1.7 million.</p><p>In a fat-tailed world, what financial textbooks call a &#8220;5&#963; event&#8221; can be a 1-in-100 event, or even more frequent. The 1993 Q2 HDB jump is a 7&#963; event in a normal model. The 1981 Q1 URA spike is a ~5&#963; event. The 2009 Q1 GFC dip is a ~3.4&#963; event. We&#8217;ve had multiple of these in 50 years, in just one country, in one asset class.</p><p>What that actually means if you&#8217;re buying a Singapore property today: your downside in any single quarter could be 15% or more, even with no leverage. Your upside in any single quarter could be 25% or more. The &#8220;1-in-50-years&#8221; event is more like 1-in-10. Standard deviation, as a measure of &#8220;how much can this move,&#8221; is simply not informative.</p><p>But there&#8217;s a more important fact than any of these &#8212; and it&#8217;s the one I think almost nobody knows about.</p><h2>The real lesson: it&#8217;s clustering, not noise</h2><p>Here is the most underappreciated finding in all of this. The chart below is the headline diagnostic from Nassim Taleb&#8217;s Statistical Consequences of Fat Tails. We applied it to Singapore data:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EQE6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EQE6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 424w, https://substackcdn.com/image/fetch/$s_!EQE6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 848w, https://substackcdn.com/image/fetch/$s_!EQE6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 1272w, https://substackcdn.com/image/fetch/$s_!EQE6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EQE6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png" width="1456" height="1008" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1008,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:279826,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/195420447?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EQE6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 424w, https://substackcdn.com/image/fetch/$s_!EQE6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 848w, https://substackcdn.com/image/fetch/$s_!EQE6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 1272w, https://substackcdn.com/image/fetch/$s_!EQE6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc19487a-d729-45b4-bfd7-539792f66e2b_1690x1170.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Each panel is one Singapore property index. The blue line shows the kurtosis (fat-tailedness) of returns aggregated over different time windows &#8212; 1 quarter, 2 quarters, all the way to 12 quarters (3 years). The green dashed line shows the same calculation, but with the returns randomly reshuffled in time.</p><p>Reshuffling preserves the distribution of returns. Same average, same variance, same fat marginal tails. What it destroys is the order: the relationship between consecutive quarters.</p><p>Look at what happens. The blue line (real, actual time-ordered data) stays elevated. Even at 12-quarter aggregation, kurtosis is well above the &#8220;normal&#8221; baseline of 3. The green dashed line (reshuffled) drops to roughly 3 &#8212; Gaussian &#8212; by lag 8 or 10.</p><p>If property returns were truly fat-tailed but independent quarter-to-quarter, the blue and green lines would look the same. They don&#8217;t.</p><p>What this means: the multi-year fat-tailed risk in Singapore property is not primarily about how fat-tailed the quarterly distribution is. It&#8217;s about the fact that bad quarters cluster together, and good quarters cluster together. The market spends years at a time in a &#8220;boom regime&#8221; or a &#8220;bust regime.&#8221;</p><p>Visible in the data: </p><ul><li><p>the 1980&#8211;81 boom was 6 consecutive quarters of double-digit gains. </p></li><li><p>The 1993 HDB boom was two consecutive quarters of +27% then +18%. </p></li><li><p>The 1996&#8211;2004 URA bust was 10-quarter, ~45% peak-to-trough drawdown, after which the index stayed below the 1996 peak for over a decade. </p></li><li><p>The 2008 GFC dip was 4 consecutive negative quarters before recovery.</p></li></ul><p>This is the difference between two mental models. Model A says fat-tailed but independent &#8212; each quarter is an independent draw from a fat-tailed distribution; long-run drawdowns wash out via central-limit theorem; standard fat-tail stats describe the risk well; 5-year worst case is moderate. Model B says clustered booms and busts &#8212; quarters within a regime are correlated; regimes are persistent; long-run drawdowns compound because all the bad quarters land in the same window; you also need to model regime persistence; 5-year worst case is severe (45%+ drawdowns observed).</p><p>The data say Model B is correct.</p><h2>What this means for an actual buyer</h2><p>If you buy property today expecting &#8220;the long-run average return is 6.4% per year, so over 5 years I should average 32% appreciation,&#8221; you&#8217;re using Model A. Model A says 5 years of bad luck is a freak event.</p><p>Model B says: the 5-year outcome is dominated by what regime you&#8217;re in, which is largely determined by when you bought. It&#8217;s not about luck. It&#8217;s about timing relative to the regime.</p><p>Practical consequences:</p><ol><li><p><strong>Late-regime entries are more dangerous than they look.</strong> Buying at the top of a sustained price run (high transaction volume, optimistic narratives, rising leverage in the system) raises the conditional probability that you&#8217;re at the start of a bust regime. Buying at the bottom of a sustained drawdown (low transaction volume, pessimistic narratives, distressed listings) does the opposite. Same property, same physical asset &#8212; wildly different conditional 5-year outcomes.</p></li><li><p><strong>Holding period interacts with regime, not with calendar time.</strong> A 5-year holder who happens to enter the start of a boom regime exits at peak. A 5-year holder who enters at the start of a bust compounds losses. Same hold length, completely different experience.</p></li><li><p><strong>Leverage that&#8217;s &#8220;safe in normal times&#8221; can ruin you in a long bust.</strong> A 70% LTV ratio is a manageable risk in a stable regime &#8212; even with the occasional bad quarter. In a 7-year bust regime where prices fall 40%, the same 70% LTV means negative equity for years. You can&#8217;t refinance, you can&#8217;t sell at non-distressed prices, and you have to keep servicing the loan throughout. Th==e risk is not the worst single quarter; it&#8217;s the duration of the bust regime exceeding the buffer your finances can absorb.</p></li><li><p><strong>Stress-test using historical events, not multiples of &#963;.</strong> When your bank or your spouse asks &#8220;what&#8217;s the worst case?&#8221;, don&#8217;t say &#8220;two standard deviations down.&#8221; Say &#8220;what if 1996&#8211;2004 happens again?&#8221; That&#8217;s a 45% drawdown over 7 years with the property essentially impossible to sell at non-distressed prices throughout. If your finances can survive that, you can buy. If they can&#8217;t, you can&#8217;t, regardless of what the average suggests.</p></li><li><p><strong>Don&#8217;t outsource your regime-judgment to property gurus.</strong> Most property advice in Singapore is sold by people whose income depends on you transacting. They are structurally biased to believe whatever bullish or bearish narrative drives volume. Your actual cycle-positioning decision should rest on regime indicators (transaction volumes, price-to-rent ratios, mortgage-to-income ratios, supply pipelines, policy direction) &#8212; not on someone else&#8217;s confidence.</p></li></ol><h2>The bigger lesson</h2><p>You can extend this beyond property. Almost every domain you care about is fat-tailed in this same way. </p><ul><li><p>Equity markets have clustered crashes (1929, 1987, 2000, 2008, 2020)</p></li><li><p>Career outcomes are dominated by a few breakthrough years. </p></li><li><p>Most of your lifetime medical risk is concentrated in a few specific events.</p></li><li><p>A small fraction of relationships carry most of the long-term consequence.</p></li></ul><p>The intuitive ways we think about averages and volatility &#8212; trained by exam questions about coin flips and dice &#8212; break down in these domains. The standard deviation underestimates the spread, the mean is dominated by outliers, and &#8220;averaging out over the long run&#8221; doesn&#8217;t happen because the long run is dominated by clustered regimes, not by independent draws.</p><h2>Want to verify this yourself?</h2><p>Here&#8217;s the entire analysis pipeline. Less than 100 lines of Python. The data is publicly available &#8212; no special access required.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">
import json
import urllib.request
import numpy as np
import pandas as pd
from scipy import stats

# 1. Fetch URA Private Residential PPI from SingStat (public API)
URL = "https://tablebuilder.singstat.gov.sg/api/table/tabledata/M212261?limit=5000"
data = json.loads(urllib.request.urlopen(URL).read())
row = next(r for r in data["Data"]["row"] if r["rowText"] == "Residential Properties")
ppi = pd.Series(
    [float(c["value"]) for c in row["columns"]],
    index=pd.PeriodIndex(
        [pd.Period(f"{c['key'].split()[0]}Q{c['key'].split()[1][0]}", freq="Q")
         for c in row["columns"]],
        freq="Q",
    ),
)

# 2. Compute quarterly log-returns
r = np.log(ppi / ppi.shift(1)).dropna().values

# 3. Headline statistics
print(f"n = {len(r)} quarterly observations, 1975Q1 to 2025Q4")
print(f"mean per quarter   = {r.mean():+.4f}")
print(f"Pearson kurtosis   = {stats.kurtosis(r, fisher=False):.2f}  (Gaussian = 3)")

# 4. Single-event dominance test
x4 = r ** 4
print(f"Max single-quarter share of total x^4: {x4.max() / x4.sum():.3f}")

# 5. Clustering test (Taleb's Fig 10.2)
def kurt_at_lag(returns, lag):
    n = len(returns) // lag
    aggregated = returns[:n*lag].reshape(n, lag).sum(axis=1)
    return stats.kurtosis(aggregated, fisher=False)

print(f"raw,        lag=12: {kurt_at_lag(r, 12):.2f}")
rng = np.random.default_rng(42)
shuffled = []
for _ in range(200):
    s = r.copy(); rng.shuffle(s)
    shuffled.append(kurt_at_lag(s, 12))
print(f"reshuffled, lag=12: {np.mean(shuffled):.2f}  (mean of 200 shuffles)")
```

When you run it, you'll see the raw lag-12 kurtosis stays well above 3, but the reshuffled version drops to about 2.7 &#8212; exactly the volatility-clustering signature this article describes.

Disclosure

Both this article and the Python analysis code were generated by Claude, Anthropic's AI assistant. The data is from SingStat (Singapore's Department of Statistics) and is publicly available. The methodology follows Chapter 10 of Nassim Nicholas Taleb's Statistical Consequences of Fat Tails (2020), particularly Figure 10.2 on the role of volatility clustering in apparent fat-tailedness.

I ran every test described, generated every chart from the actual data, and have full reproducibility. If anything looks wrong, please tell me &#8212; fat-tail analysis is famously easy to get subtly wrong, and a second pair of eyes is welcome.</code></pre></div>]]></content:encoded></item><item><title><![CDATA[The Hidden Interest Rate in Your Insurance Bill]]></title><description><![CDATA[A mental shortcut for comparing payment plans.]]></description><link>https://www.zhizhi-gewu.com/p/the-hidden-interest-rate-in-your</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/the-hidden-interest-rate-in-your</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 19 Apr 2026 02:15:45 GMT</pubDate><content:encoded><![CDATA[<p>You&#8217;re 64 years old, flipping through AIA&#8217;s health insurance brochure. The same basic plan shows four prices for the same coverage:</p><ul><li><p><strong>Annual:</strong> HKD 72,440</p></li><li><p><strong>Semi-annual:</strong> HKD 36,944 &#215; 2 = 73,888</p></li><li><p><strong>Quarterly:</strong> HKD 20,280 &#215; 4 = <strong>81,120</strong></p></li><li><p><strong>Monthly:</strong> HKD 6,400 &#215; 12 = 76,800</p></li></ul><p>Two things jump out. First, monthly is only about 6% more than annual &#8212; tempting for the cash-flow relief. Second, <em>quarterly is the most expensive of the four</em> &#8212; more than monthly. Strange. Is it just a weird pricing quirk, or is the insurer telling you something?</p><p>The 6% on monthly isn&#8217;t a fee &#8212; it&#8217;s <strong>interest</strong>, and you&#8217;re paying it on money still sitting in your pocket. So the right question isn&#8217;t &#8220;how much extra?&#8221; but &#8220;<strong>what interest rate am I paying to delay?</strong>&#8220;</p><h2>The exact answer is IRR. The useful one lives in your head.</h2><p>Let <em>m</em> be the monthly payment and <em>r</em> the monthly rate. For the installment stream to be worth the same as paying <em>P</em> upfront today:</p><blockquote><p><em>P = m + m/(1+r) + m/(1+r)&#178; + &#8943; + m/(1+r)&#185;&#185;</em></p></blockquote><p>Solving this exactly is what IRR does. Instead, use the approximation anyone with a calculus class already knows:</p><blockquote><p><em>1 / (1+r)&#8319; &#8776; 1 &#8722; nr</em> (small <em>r</em>)</p></blockquote><p>Plug it in and the messy geometric series collapses into a clean arithmetic one:</p><blockquote><p><em>P &#8776; m &#183; [ 1 + (1 &#8722; r) + (1 &#8722; 2r) + &#8943; + (1 &#8722; 11r) ] = 12m &#8722; m&#183;r&#183;(1 + 2 + &#8943; + 11)</em></p></blockquote><p>That last sum is <strong>66</strong>. So:</p><blockquote><p><em>P &#8776; 12m &#8722; 66 m r = 12m &#183; (1 &#8722; 5.5 r)</em></p></blockquote><p>Here&#8217;s where the intuition crystallizes. That <strong>5.5</strong> isn&#8217;t arbitrary &#8212; it&#8217;s the <strong>average number of months you delay a payment</strong>. The first payment is on day zero, the last is at month 11; on average, each dollar is delayed 5.5 months.</p><p>Now flip it into years. Let <em>R = 12r</em> be the annual rate, and call <em>d&#772; = 5.5 / 12 &#8776; 0.458</em> the <strong>average delay in years</strong>. Then:</p><blockquote><p><em>P &#8776; A &#183; (1 &#8722; R &#183; d&#772;)</em>, where <em>A = 12m</em> is the total you pay.</p></blockquote><p>One rearrangement gives the rule:</p><blockquote><p><strong>R &#8776; markup / d&#772;</strong>, with <em>markup = (A &#8722; P) / A</em>.</p></blockquote><p><strong>The implied rate is the markup divided by the average delay in years.</strong> Nothing more.</p><h2>The cheat sheet</h2><p>For <em>N</em> equal payments spaced evenly over a year, the average delay is the arithmetic-sum shortcut applied once and converted to years:</p><blockquote><p><em>d&#772; = (0 + 1 + &#8943; + (N&#8722;1)) / N &#215; 1/N yr = (N &#8722; 1) / (2N) yr</em></p></blockquote><ul><li><p><strong>Monthly</strong> (N = 12): d&#772; = 11/24 &#8776; 0.458 yr &#8594; multiplier &#8776; <strong>2.2&#215;</strong> (markup &#247; 0.458)</p></li><li><p><strong>Quarterly</strong> (N = 4): d&#772; = 3/8 = 0.375 yr &#8594; multiplier &#8776; <strong>2.7&#215;</strong> (markup &#247; 0.375)</p></li><li><p><strong>Semi-annual</strong> (N = 2): d&#772; = 1/4 = 0.25 yr &#8594; multiplier = <strong>4&#215;</strong> (markup &#247; 0.25)</p></li></ul><p>Notice the pattern: fewer payments &#8594; shorter average delay &#8594; a given markup implies a <em>higher</em> rate. A 3% markup on a semi-annual plan is expensive in rate terms (~12%); the same 3% on a monthly plan is ~6.5%.</p><h2>Back to the AIA plan</h2><p>Apply the rule <em>R &#8776; markup / d&#772;</em>:</p><ul><li><p><strong>Semi-annual:</strong> markup 2.0% &#215; 4 = <strong>~8% per year</strong></p></li><li><p><strong>Monthly:</strong> markup 5.7% &#215; 2.2 = <strong>~13% per year</strong></p></li><li><p><strong>Quarterly:</strong> markup 10.7% &#215; 2.7 = <strong>~29% per year</strong></p></li></ul><p>Suddenly the strange quarterly pricing makes sense. The insurer isn&#8217;t making a mistake &#8212; they&#8217;re quietly charging you roughly <strong>30% per year</strong> to stretch payments over nine months. The monthly plan is a far better deal (and annual is better still if you have the cash).</p><p>Now you can actually choose. If your alternative is a credit card at 24%, the monthly plan&#8217;s ~13% is cheaper money &#8212; take it. If your cash is sitting in a HK deposit at 4%, pay annual and pocket the 9-point spread. And whatever you do, <strong>don&#8217;t pick quarterly</strong> unless you have no other option, because you&#8217;re borrowing at subprime rates for the privilege.</p><h2>When it breaks</h2><p>The approximation is tight when <em>nr</em> is small. For semi-annual and monthly above, it matches the exact IRR within a percentage point &#8212; 7.8% vs 8.3%, 12.4% vs 13.8%. For quarterly, where the true rate is ~37%, the linearization starts to fail and our shortcut underestimates by about 8 points. The rule of thumb is: trust the shortcut up to ~20% annualized; beyond that, it still tells you the <em>direction</em> (&#8221;this is expensive&#8221;) but you&#8217;ll want a spreadsheet for the exact number.</p><p>It also breaks when the schedule isn&#8217;t evenly spaced &#8212; a big deposit plus small monthlies, or a ballooning final payment. Same fix: spreadsheet.</p><p>But for the ordinary case of comparing payment frequencies on an insurance quote: <strong>divide the markup by the average delay in years, and you have your interest rate.</strong> It takes five seconds and it&#8217;s usually the difference between a good deal and a quietly terrible one.</p>]]></content:encoded></item><item><title><![CDATA[When to Use a Blacklist, and When to Use a Rule]]></title><description><![CDATA[After joining the anti-fraud team, I noticed that blacklists are used far more extensively than they were in credit underwriting.]]></description><link>https://www.zhizhi-gewu.com/p/when-to-use-a-blacklist-and-when</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/when-to-use-a-blacklist-and-when</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 04 Apr 2026 04:16:20 GMT</pubDate><content:encoded><![CDATA[<p>After joining the anti-fraud team, I noticed that blacklists are used far more extensively than they were in credit underwriting. That observation led me to reflect on the difference between a blacklist and a rule.</p><p>For readers outside the risk management field, a blacklist is a finite set of objects, such as a user ID, device fingerprint, phone number, or identity card number. Entities on the blacklist are not allowed to use the product. A rule, by contrast, is a decision function: it takes an input and produces an outcome, such as pass, reject, or review.</p><p>Mathematically, both can be expressed as indicator functions:</p><p>R(x) = 1[C(x)]</p><p>where C(x) is some condition on x, and</p><p>R(x)=1[X in B]</p><p>where B is a list.</p><p>In this sense, a blacklist is simply a special case of a general rule in which the condition is set membership. Mathematically, both are the same type of object: they map an input to a decision. In practice, however, the distinction remains useful. The term <em>rule</em> usually refers to non-list-like logic, while <em>blacklist</em> refers to list-like logic based on explicit membership. The real question, then, is not whether a blacklist is a rule, but when we should rely on list-like rules and when non-list-like rules are more appropriate.</p><p>A blacklist is like a memory of bad actors. Once a bad actor is identified, the goal is to prevent them from exploiting the product again. But this only works under two conditions: first, we can identify them reliably; second, the identifier is not easy to replace or rotate. For that reason, blacklists are most suitable for high-confidence, confirmed cases tied to relatively durable identifiers. If our confidence is low, or if the identifier can be changed easily, a blacklist may do more harm than good by blocking legitimate users while doing little to stop the bad actor in the long run.</p><p>A non-list-like rule, by contrast, captures a more general pattern of risk. Someone rejected by the rule today may not be rejected tomorrow, because the decision depends on current attributes or behavior rather than fixed membership in a list. This makes rules more suitable when the signal is weaker, more probabilistic, or tied to identifiers that can be changed easily.</p><p>In practice, however, teams often blur the boundary between the two. Low-confidence signals or easily rotated identifiers are sometimes added to blacklists, which can create high false-positive rates. Conversely, even high-confidence bad actors are sometimes handled only through dynamic rules, leaving room for repeated breaches once the pattern changes or the rule is circumvented.</p><p>The key is to match the tool to the nature of the signal. A blacklist works best when the signal is strong and the identity is durable. A rule works better when the signal is less certain or when the adversary can easily change identifiers. Although the two are mathematically similar, they play different operational roles: a blacklist acts as memory, while a rule acts as generalized reasoning. Confusing the two can either block too many good users or allow known bad actors to return.</p>]]></content:encoded></item><item><title><![CDATA[If You Cannot Create It, You Don't Understand It — Even with AI"]]></title><description><![CDATA[I recently built a tiny autograd engine from scratch &#8212; just a hundred lines of Python that can compute gradients through a computation graph.]]></description><link>https://www.zhizhi-gewu.com/p/if-you-cannot-create-it-you-dont</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/if-you-cannot-create-it-you-dont</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 29 Mar 2026 04:00:14 GMT</pubDate><content:encoded><![CDATA[<p>I recently built a tiny autograd engine from scratch &#8212; just a hundred lines of Python that can compute gradients through a computation graph. The kind of thing an LLM could spit out in seconds. But I didn&#8217;t let it. I wrote every line myself, hit bugs, traced gradients by hand on paper, and asked the AI to check my work &#8212; not to do it for me. And honestly? It was the most satisfying learning experience I&#8217;ve had in a while.</p><h2>Reflections on Learning with AI</h2><p><strong>LLMs can hinder learning if you let them: </strong>Learning is fundamentally hard because it requires working your brain. An LLM can provide solutions instantly, but that bypasses the struggle where real understanding is built. Think of the LLM as a teacher sitting beside you &#8212; the teacher cannot complete the task for the student. You still have to do the thinking.</p><p><strong>LLMs can act as a teacher, but only if you drive the conversation:</strong> They can verify your understanding, answer questions, and generate practice problems. However, current LLMs are not proactive teachers &#8212; they respond, they don&#8217;t lead. It takes skill to prompt the LLM effectively, which is challenging, especially for younger students who may not yet be comfortable with self-directed learning.</p><p><strong>LLMs are better at details than intuition:</strong> They tend to explain step-by-step solutions well but struggle to convey the big picture and the intuition behind the concepts. For example, Section 1 (The Big Picture) of these notes was my own mental model, polished by the LLM &#8212; not generated by it.</p><p><strong>Writing code by hand is irreplaceable:</strong> Having the opportunity to implement something yourself is deeply satisfying and solidifies understanding in a way that reading or prompting never can. As the saying goes, <strong>&#8220;What I cannot create, I do not understand.&#8221;</strong> (Richard Feynman). The bugs you hit, the edge cases you miss, the moments where it finally clicks &#8212; that&#8217;s where the real learning happens.</p><p>Attached the study note on autogradiant decent prepared together with LLM:</p><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Study Notes</div><div class="file-embed-details-h2">137KB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://www.zhizhi-gewu.com/api/v1/file/ad05faf1-1534-467d-bf0e-a0f3c01cfabe.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://www.zhizhi-gewu.com/api/v1/file/ad05faf1-1534-467d-bf0e-a0f3c01cfabe.pdf"><span class="file-embed-button-text">Download</span></a></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Can AI Agents Win a Modeling Challenge? A Replicable Experiment]]></title><description><![CDATA[To get the most out of AI agents, we need to remove human bottlenecks and increase leverage.]]></description><link>https://www.zhizhi-gewu.com/p/can-ai-agents-win-a-modeling-challenge</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/can-ai-agents-win-a-modeling-challenge</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Wed, 25 Mar 2026 15:21:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!R4ip!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="pullquote"><p>To get the most out of AI agents, we need to remove human bottlenecks and increase leverage.</p></div><p><strong>If you want to jump straight to the implementation, the full code is here:</strong> <a href="https://github.com/kychanbp/monee_auto_ml_comp_2026_demo">Github</a>.</p><h2>What Was Really Being Tested</h2><p>My company recently held a modeling competition in which participants were allowed to use only AI tools, without writing any code themselves. The objective was straightforward: maximize the AUC of a supervised learning task.</p><p>In tabular supervised learning, the practical toolkit is already quite mature. Gradient boosting models remain the dominant high-performance baseline, and in real-world applications the largest gains often come not from changing the model class, but from accumulating more data, engineering better features, and defining targets that better reflect business objectives. Model fine-tuning can help at the margin, but it rarely produces step-change improvements. Even in sequential modeling, much of the value can be understood as learning richer representations of the underlying behavior and structure.</p><p>For that reason, the most interesting question was not whether an LLM could write code to implement ideas that human practitioners had already provided. If humans specify the feature candidates, the modeling tricks, and the overall direction, then the exercise becomes largely a test of coding and execution. That is useful, but not especially interesting. A more meaningful test is whether an LLM can autonomously discover promising ideas for itself: what features to construct, which techniques are worth trying, how to interpret intermediate results, and how to iterate toward better performance under a clear objective.</p><p>Seen from this perspective, the purpose of the competition, in my own opinion, was not to discover a fundamentally new classification algorithm nor implementing solutions based on human insights. It was to test whether an LLM could independently reproduce the practical workflow behind strong credit risk modeling: generating hypotheses, engineering useful features, running experiments, learning from feedback, and improving performance through iteration. Put differently, if the playbook used by experienced practitioners is only partially specified, how much of it can an LLM recover on its own?</p><p>This also made the exercise a test of instruction design. Beyond model performance, it offered a way to understand how tasks should be framed so that an LLM can explore the solution space productively rather than simply execute a predefined recipe.</p><h2>The Experiment Setup</h2><p>At first glance, the objective may seem clear enough: give the LLM a target metric and ask it to iterate on its own. But things work more smoothly in human teams because people already share a large amount of tacit context: what counts as a valid experiment, what shortcuts are unacceptable, how performance should be evaluated, and when a result is worth keeping. For an LLM, many of these assumptions have to be made explicit.</p><p>To make the exercise meaningful, we needed to specify a set of operating rules that human teams would often leave implicit.</p><p>The experimental setup was not fully specified from the start; as I observed the agent&#8217;s behavior, I iteratively refined and steered it. For example, in the later stages, I found that the acceptance threshold had become too strict.</p><p><strong>Scope.</strong> We had to define what the agent was allowed to modify, what it was not allowed to touch, and what kinds of actions were permitted or prohibited.</p><p><strong>Integrity.</strong> The agent could use only data available before the application date. It also had to check for leakage before proceeding, especially when a single feature appeared to perform suspiciously well.</p><p><strong>Evaluation.</strong> The evaluation methodology had to be fixed in advance. Otherwise, the LLM could improve reported scores simply by changing the validation setup rather than improving the model itself.</p><p><strong>Promotion criteria.</strong> We needed to define what magnitude of improvement was sufficient for a new approach to be accepted and carried forward.</p><p><strong>Logging.</strong> Like humans, LLMs do not naturally produce good documentation unless asked to do so. To make their work inspectable&#8212;and to give the agent a usable record of its own progress&#8212;we had to explicitly require logging.</p><p><strong>Resource constraints.</strong> The agent needed rules for efficient experimentation: cache generated features for reuse, avoid recomputing unnecessarily, explore ideas in parallel where possible, and operate within limits on the number and scale of experiments.</p><p><strong>Simplicity.</strong> Some human judgment still had to be encoded into the setup. In general, we preferred simpler models when they delivered performance comparable to more complex alternatives.</p><p><strong>Stopping criteria.</strong> We also had to define when the agent should stop iterating, rather than continuing to search indefinitely for marginal gains.</p><p>These rules were not just administrative details. They were part of the experiment itself. If the goal was to test whether an LLM could behave like a disciplined modeler, then the environment had to specify the constraints under which disciplined modeling takes place.</p><p>You can find the full instruction to the LLM <a href="https://github.com/kychanbp/monee_auto_ml_comp_2026_demo/blob/main/program.md">here</a>.</p><h2>How the LLM Iterated</h2><p>The LLM spent around 48 hours to reach the final result. In the first 24 hours, I did not prompt the LLM anything and it reached 0.793424 AUC at round 31. The, the LLM stucked for consecutive 12 rounds. I prompted the LLM to leverage websearch to search for ideas, although I already stated that in the original prompt and it preferred relying on its internal knowledge.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R4ip!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R4ip!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 424w, https://substackcdn.com/image/fetch/$s_!R4ip!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 848w, https://substackcdn.com/image/fetch/$s_!R4ip!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 1272w, https://substackcdn.com/image/fetch/$s_!R4ip!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R4ip!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png" width="1456" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Experiment Progress&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Experiment Progress" title="Experiment Progress" srcset="https://substackcdn.com/image/fetch/$s_!R4ip!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 424w, https://substackcdn.com/image/fetch/$s_!R4ip!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 848w, https://substackcdn.com/image/fetch/$s_!R4ip!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 1272w, https://substackcdn.com/image/fetch/$s_!R4ip!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa22e0a-19b9-4404-ab2a-68a0310973b0_1800x750.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Thoughts</h2><p>With clearly defined objectives and boundaries, an LLM can iterate on its own and uncover most of the early low-hanging fruit much faster than a human can. However, more distant ideas&#8212;such as KNN-based approaches or sequential modeling, which require a more creative leap&#8212;seem harder for it to discover. That said, the LLM only spent two days exploring the solution space. Without internet access, it is not obvious that a human could have found those ideas within the same time frame either. My guess is that the top-performing team spent far more than two days to achieve its breakthrough, and likely involved multiple team members.</p><p>What stands out most is how much cheaper it becomes to try new ideas. The cost of experimentation drops sharply in terms of both time and human effort. With its coding ability, the LLM can translate modeling ideas into working code extremely quickly. Generating roughly 300 additional features, running hyperparameter tuning, stacking multiple models, and building sequential representations of the data&#8212;all within two days&#8212;would be expensive for a human team. To do the same amount of work, we might need five to ten experienced practitioners. In my case, I did not even fully use the token budget of Claude&#8217;s USD 100 max plan.</p><p>Will humans lose their jobs? Partly, yes. LLMs will increasingly replace a large share of the coding, pipeline construction, and some feature engineering work. But they still need humans to provide direction. The human role will shift toward defining the scope, constraints, and objectives of the task, and then validating the outputs. At least for now, humans also seem to have better taste&#8212;in the sense of searching the solution space more efficiently and recognizing which directions are truly promising. We sometimes hear stories of LLMs surfacing obscure old papers that solve a problem outright, which suggests that this advantage may not always hold. But on average, I still think humans with domain knowledge retain an edge.</p><p>Will that remain true as we use LLMs more and progressively transfer domain knowledge into them? I do not know. If forced to give a rough estimate, I would guess that within five to ten years, LLMs may surpass humans even in the discovery of previously unknown solutions.</p><p>What seems much clearer is that, over the next five years, human-plus-AI will dominate human-alone workflows. The productivity gain is easily an order of magnitude for many existing tasks, and in some cases effectively unbounded because AI enables work that would not have been attempted otherwise. If you are not using AI every day to learn new things, you are likely falling behind. If you are not using it to accelerate coding-related work, you are almost certainly moving much more slowly. And if you are not using it to help generate new ideas and solutions, you may already be at a meaningful disadvantage. Once these effects compound, the gap between human-only and human-plus-AI workflows becomes very large.</p><h2>Next Step</h2><p>If the current limitation is the LLM&#8217;s &#8220;taste,&#8221; then the next frontier is not just better models, but better ways of searching the solution space. The key challenge is to help the LLM explore more intelligently, so that it can identify promising directions earlier instead of relying mainly on broad trial and error. Early thoughts including the use of tree-of-thoughts and Monte Carlo Tree Search (MCTS) which I will try to implement in next week.</p><p>I also need to better maximize the available token budget. During this project, the agent never came close to exhausting the token allowance of the Claude Max ($100/month) plan. In practice, a considerable amount of time was spent waiting for model runs to finish while the agent was otherwise idle, suggesting that I did not make full use of the available capacity.</p><h2>Recommend Readings</h2><iframe class="spotify-wrap podcast" data-attrs="{&quot;image&quot;:&quot;https://i.scdn.co/image/ab6765630000ba8a5f26cc6401340f282d22e1dd&quot;,&quot;title&quot;:&quot;Andrej Karpathy on Code Agents, AutoResearch, and the Loopy Era of AI&quot;,&quot;subtitle&quot;:&quot;Conviction&quot;,&quot;description&quot;:&quot;Episode&quot;,&quot;url&quot;:&quot;https://open.spotify.com/episode/3u1XHEsAuK1VXRr81RzQDV&quot;,&quot;belowTheFold&quot;:true,&quot;noScroll&quot;:false}" src="https://open.spotify.com/embed/episode/3u1XHEsAuK1VXRr81RzQDV" frameborder="0" gesture="media" allowfullscreen="true" allow="encrypted-media" loading="lazy" data-component-name="Spotify2ToDOM"></iframe>]]></content:encoded></item><item><title><![CDATA[How Singapore Savings Bonds Work: A Financial Engineering Perspective]]></title><description><![CDATA[Not investment advice]]></description><link>https://www.zhizhi-gewu.com/p/how-singapore-savings-bonds-work</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/how-singapore-savings-bonds-work</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 15 Mar 2026 09:07:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BuIF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Crossing the 30s mark, financial life has changed significantly, mainly due to increased liabilities. While human capital remains the primary cash-generating asset, as was common in the early 30s, it is important to build greater robustness into the system because all income depends on continuous production by the brain and body. Robustness can be achieved through downside volatility mitigation and diversification. Insurance and cash reserves can help cap the downside, and financial capital diversifies human capital risk to some extent. This article discusses building cash reserves and, in particular, the characteristics of the Singapore Savings Bonds (SSB).</p><h2>Cash is &#8220;Still&#8221; the King</h2><p>Many financial advisors say cash is trash because the government can &#8220;print&#8221; money and that cash will depreciate relative to other assets. Therefore, cash is a poor asset to hold. However, it is not always the case. If equity goes down by 18%, the same amount of cash can now buy 22% more equity (1/(1-18%) - 1) &#8212; cash gains in <em>relative</em> purchasing power even though its nominal value stays the same. In particular, during monetary tightening, bond prices fall while cash yields rise, making cash an attractive asset to hold. More generally, cash earns interest when treated as an investable asset rather than a medium of exchange, and its yield roughly follows the short end of the yield curve.</p><h2>Singapore Savings Bonds</h2><p>SSB is a 10-year government bond issued monthly by MAS, available to individuals in multiples of $500. What makes it unique is its structure: SSB is a single instrument with a guaranteed principal that replicates the SGS yield curve at every holding period &#8212; prioritising the step-up feature when the curve shape doesn&#8217;t naturally allow it. To my knowledge, this structure offers a few advantages:</p><ul><li><p><strong>Flexibility in holding period:</strong> Investors do not have to commit to a fixed holding period, unlike with fixed deposits and bonds. SSBs can be redeemed at par in any given month with no penalty.</p></li><li><p><strong>No price risk:</strong> Unlike a bond, which trades on the secondary market and whose price can drop below par value, the SSB principal is guaranteed.</p></li><li><p><strong>No reinvestment risk:</strong> The coupons are guaranteed and locked in at issuance. For 6-month or 1-year T-bills, when they mature, investors must reinvest at the then-available rate, which may be lower.</p></li></ul><p>Effectively, SSB offers two embedded protections. The first is a free put option at par, allowing the investor to &#8220;sell&#8221; it back to the government at face value at any time &#8212; this protects when rates rise. The second is a free forward rate agreement on the full yield curve, locking in returns for up to 10 years &#8212; this protects when rates fall. The limitation, however, is the investment cap ($50,000 per issue and $200,000 per individual).</p><h2>How SSB Rates Reflect the Yield Curve</h2><p>To understand how SSB coupons are derived, it helps to look at the underlying SGS yield curve across different rate environments. The chart below shows three panels: the SGS benchmark yields (input), the resulting SSB coupon rates (output), and the average returns investors actually earn.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BuIF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BuIF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 424w, https://substackcdn.com/image/fetch/$s_!BuIF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 848w, https://substackcdn.com/image/fetch/$s_!BuIF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 1272w, https://substackcdn.com/image/fetch/$s_!BuIF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BuIF!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png" width="1200" height="1502.4725274725274" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1823,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1411031,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.zhizhi-gewu.com/i/191002074?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BuIF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 424w, https://substackcdn.com/image/fetch/$s_!BuIF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 848w, https://substackcdn.com/image/fetch/$s_!BuIF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 1272w, https://substackcdn.com/image/fetch/$s_!BuIF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F732f5cc1-ec16-42f0-9f4c-f6ebb572223b_1782x2231.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Panel 1 &#8212; SGS Yield Curves (the input)</strong></p><p>The yield curve shape has changed dramatically across eras</p><ul><li><p>2015-2017 (purple): Steep upward slope &#8212; short rates ~1%, long rates ~2.2%</p></li><li><p>2020-2021 (teal): Lowest across all tenors &#8212; near-zero short rates from COVID easing</p></li><li><p>2022-2024 (red): Inverted &#8212; short rates (~3.5%) are higher than long rates (~3%) from aggressive rate hikes</p></li><li><p>2025-2026 (orange): Re-steepening as short rates fall faster than long rates</p></li></ul><p>Notably, all eras converge to a narrower range at 10Y (~1.4-3%) than at 1Y (~0.3-3.5%), reflecting the fact that long-term rate expectations are more stable than short-term policy rates.</p><p> <strong>Panel 2 &#8212; SSB Coupons (the output)</strong></p><ul><li><p>The step-up mechanism transforms the yield curve shape into a monotonically increasing schedule</p></li><li><p>Inverted curves (red) become flat coupons &#8212; the monotonicity adjustment compresses everything to the 10Y level</p></li><li><p>Steep curves (purple) produce dramatic step-ups &#8212; from ~1% to ~3.5%</p></li><li><p>The Y10 coupon is always higher than the Y10 yield because it compensates for the lower early coupons</p></li></ul><p> <strong>Panel 3 &#8212; Average Returns (what investors actually earn)</strong></p><ul><li><p>This is the smoothed version of the coupon schedule &#8212; always monotonically increasing and more gradual</p></li><li><p>At 10Y, it matches the SGS yield in Panel 1 &#8212; confirming the design works</p></li><li><p>At shorter tenors, it matches SGS yields only when no monotonicity adjustment is needed</p></li></ul><p> <strong>Key takeaway</strong></p><p>The three panels tell the story of a single financial engineering pipeline: a market yield curve (which can be any shape &#8212; steep, flat, inverted) gets transformed into an investor-friendly product that always steps up, always returns principal, and always delivers the 10Y market return. The price of this guarantee is visible in the red era &#8212; when the curve inverted, short-term SSB holders earned well below the prevailing T-bill rate because the step-up constraint sacrificed short-tenor returns to preserve the monotonicity feature.</p><h2>Methodology</h2><p>For those interested in the methodology for deriving the SSB coupon from SGS benchmark rates, you may refer to this <a href="https://www.mas.gov.sg/-/media/mas/sgs/sgs-announcements-pdf/ssb-pdf/ssb-technical-specifications/ssb-technical-specifications.pdf?utm_source=chatgpt.com">MAS paper.</a> Here is a high-level skeleton: </p><p><strong>Step 1 &#8212; Get Benchmark Yields</strong></p><ul><li><p>Download daily SGS benchmark yields from <a href="https://eservices.mas.gov.sg/statistics/fdanet/BenchmarkPricesAndYields.aspx">MAS Benchmark Prices and Yields</a></p></li><li><p>Specifically: 1-Year T-Bill Yield, 2-Year Bond Yield, 5-Year Bond Yield, 10-Year Bond Yield</p></li><li><p>Compute the simple average of all trading days from month M-2 (two months before the SSB issue month)</p></li></ul><p><strong>Step 2 &#8212; Interpolate the Full Yield Curve</strong></p><ul><li><p>Use a hermite spline to fill in the missing tenors (3Y, 4Y, 6Y, 7Y, 8Y, 9Y)</p></li><li><p>Now you have 10 par yields: Y1, Y2, ..., Y10</p></li></ul><p><strong>Step 3 &#8212; Bootstrap Discount Factors</strong></p><ul><li><p>Convert par yields into discount factors (the present value of $1 received at each future year)</p></li><li><p>DF1 = 1/(1+Y1), then each subsequent DF is solved from the no-arbitrage pricing equation</p></li></ul><p><strong>Step 4 &#8212; Solve for Step-Up Coupons</strong></p><ul><li><p>Find coupon rates C1, C2, ..., C10 such that the bond prices are at par for every holding period</p></li><li><p>This means: an investor who holds for N years and redeems at par earns the same return as an N-year SGS bond</p></li><li><p>C1 = Y1, then each subsequent coupon is solved forward using the discount factors</p></li></ul><p><strong>Step 5 &#8212; Enforce Monotonicity</strong></p><ul><li><p>If the raw coupons don&#8217;t step up (e.g. inverted yield curve), adjust them:</p><ul><li><p>Minimize the pricing error across all tenors</p></li><li><p>Subject to: coupons never decrease, 10Y return is preserved exactly</p></li></ul></li><li><p>Round to 2 decimal places</p></li></ul><p><strong>Data Sources</strong></p><ul><li><p><strong>SGS benchmark yields</strong>: <a href="https://eservices.mas.gov.sg/statistics/fdanet/BenchmarkPricesAndYields.aspx">MAS Benchmark Prices and Yields</a></p></li><li><p><strong>Published SSB rates</strong>: <a href="https://eservices.mas.gov.sg/statistics/fdanet/StepUpInterest.aspx">MAS Step-Up Interest Rates</a> (127 bonds, Oct 2015 &#8211; Apr 2026)</p></li><li><p><strong>Technical specification</strong>: <a href="https://www.mas.gov.sg/bonds-and-bills/Singapore-Savings-Bonds">SSB Technical Specifications</a>, Section 4</p></li></ul><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Fraud Management and the Pricing of Tail Risk]]></title><description><![CDATA[Why tail risk is often underpriced and common fraud metrics can be misleading]]></description><link>https://www.zhizhi-gewu.com/p/fraud-management-and-the-pricing</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/fraud-management-and-the-pricing</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 08 Mar 2026 00:59:43 GMT</pubDate><content:encoded><![CDATA[<p>As a new fraud manager, I started reflecting on the fundamental principles of fraud management. I began by looking into the metrics commonly used by fraud teams, such as precision, FPR, and recall. However, I soon realized how difficult it is to set KPIs in fraud because of a core paradox: <strong>a<a href="https://www.zhizhi-gewu.com/p/absence-of-evidence-evidence-of-absence">bsence of evidence is not evidence of absence</a></strong><a href="https://www.zhizhi-gewu.com/p/absence-of-evidence-evidence-of-absence">.</a></p><p>In this blog post, I want to go one step further and discuss the causes of this paradox, and why I believe it is extremely dangerous to optimize fraud management primarily around <a href="https://www.zhizhi-gewu.com/p/auc-ks-precision-and-recall-a-risk">precision, FPR, and recall</a>.</p><p><strong>Fraud events often have the following characteristics:</strong></p><ul><li><p>The frequency of fraud is small relative to the total number of events. At least, if the business is still surviving.</p></li><li><p>The loss from fraud is often many orders of magnitude larger than other types of loss, such as credit loss per event. A single fraud incident can wipe out a month&#8217;s profit.</p></li><li><p>The real damage usually comes from previously unknown attack vectors.</p></li><li><p>Fraud events happen more often than we think. When we say &#8220;low frequency,&#8221; we may imagine once every few years. In reality, they may happen much more often.</p></li></ul><p>The consequences are as follows.</p><p><strong>First Order Consequences</strong></p><ul><li><p>A 99.99% recall may still not be enough, because the remaining 0.01% of uncaptured fraud can still put the business at risk.</p></li><li><p>The absence of observed fraud events can create a false sense of security, even though only one disconfirming event is needed to reject the proposition that we are safe.</p></li><li><p>Thinking only in terms of frequency can put us in grave danger. First, we may assume fraud events are like 4-sigma events&#8212;rare and exceptional&#8212;when in fact they happen far more often, perhaps even every day. Second, even a truly rare event can still hurt the business badly because of the scale of exposure.</p></li><li><p>Fraud events cannot be treated in isolation, because the business may never fully recover from a large incident. There is path dependency.</p></li></ul><p><strong>Second Order Consequences</strong></p><ul><li><p>Focusing too much on precision, FPR, and recall is dangerous. These metrics can miss the small fraction of unknown risks that may cause losses 1000 times larger than what we are prepared for.</p></li><li><p>Similarly, machine learning models are not sufficient for dealing with extreme unknowns. Over-focusing on deploying more advanced models can shift attention away from what truly matters.</p></li><li><p>Average risk can be highly misleading. It changes dramatically once an outlier occurs, and those outliers are often both more frequent and much larger than people expect. Monitoring average risk alone can create a false sense of security.</p></li><li><p>Frequency-based caps, such as the number of transactions allowed, are conceptually incomplete because a single event can still wipe you out.</p></li></ul><p><strong>Strategy</strong></p><p>Are we doomed then? Not necessarily.</p><p>Although it is nearly impossible to predict or control exactly when fraud will happen, exposure can still be controlled. I would argue that the primary responsibility of a fraud team is to <strong>cap the downside of the business</strong>. In the process, the team should also aim to reduce the premium paid for that protection&#8212;for example, by lowering false positives. But uncapped exposure is non-negotiable.</p><p>A cap may look too simple, but we are not here to impress our peers.</p><p>Finally, fraud risk practitioners deserve more respect. In many ways, the fraud team is effectively the seller of a put option, while the business is the buyer. The fraud team&#8217;s upside is capped. The best outcome it can possibly deliver is close to 100% recall, which is impossible to achieve in practice. Yet its downside is theoretically uncapped. When an extreme event happens, the fraud team is often the one blamed.</p><p>At the same time, the premium paid for this protection&#8212;not in salary, but in lost business opportunities&#8212;is often mispriced relative to the guarantee the fraud team is expected to provide. The business tends to focus on frequency: fraud is unlikely, so why sacrifice business volume to manage a &#8220;long-tail&#8221; event? Fraud teams, by contrast, have to focus on magnitude.</p>]]></content:encoded></item><item><title><![CDATA[AI in My Daily Workflow: Use Cases and Reflections]]></title><description><![CDATA[The Last Human Job: Deciding What We Want]]></description><link>https://www.zhizhi-gewu.com/p/ai-in-my-daily-workflow-use-cases</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/ai-in-my-daily-workflow-use-cases</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Fri, 20 Feb 2026 06:17:33 GMT</pubDate><content:encoded><![CDATA[<p><strong>Use Cases</strong></p><p><strong>Understand a codebase quickly.</strong> <a href="https://deepwiki.com/">DeepWiki</a> provides a high-level, human-readable overview of any codebase &#8212; far more efficient than reading raw code. You can also ask questions directly about the project structure and logic.</p><p><strong>Automate workflows and solve problems computationally.</strong> Claude Code is my go-to here.</p><ul><li><p>Recently, I used Claude Code (with Agent Teams) to generate personal financial statements from bank, broker, and credit card statements. The result was surprisingly good. I spent about one working day polishing the output &#8212; something that would have taken me weeks to build from scratch.</p></li><li><p>Previously, I built a property listing monitor that pulls new listings from targeted developments and sends updates via Telegram. (<a href="https://github.com/kychanbp/propertyguru_listing_monitor">Github</a>)</p></li></ul><p><strong>Q&amp;A &#8212; information retrieval and brainstorming.</strong> I use Gemini across the board: text, images, and video (directly on YouTube). Frontier models are converging in capability, and Gemini offers better value for money. The bundled 2TB Google Drive is a bonus I always end up using anyway.</p><p><strong>Understand a new subject quickly.</strong> My workflow:</p><ol><li><p>Find resources via <a href="https://scholar.google.com/scholar_labs/search">Google Scholar Labs</a>, Gemini Deep Research, or Zhihu AI Chat (<a href="https://zhida.zhihu.com/">&#30693;&#20046;&#30452;&#31572;</a>).</p></li><li><p>Upload selected resources to NotebookLM and start asking questions.</p></li></ol><p>I recently used this to get up to speed on <a href="https://notebooklm.google.com/notebook/6c533dba-5d01-4c8c-a76e-193dc6e94e05">group fraud detection</a> as part of a push to take on broader antifraud responsibilities &#8212; what might have taken weeks of reading compressed into a few focused sessions.</p><p><strong>Build a personal knowledge base.</strong> I store personal notes in Markdown, use Claude Code to build a RAG server on top of them, and pipe in RSS feeds converted to Markdown. Everything becomes searchable and queryable.</p><div><hr></div><p><strong>Reflections</strong></p><p>AI has fundamentally changed how I learn. I spend far less time on Google Search &#8212; only when I need to trace a primary source. The structured summaries AI produces help me grasp the shape of a subject much faster than stitching together search results ever did.</p><p>AI is already very capable at coding. More importantly, coding is just a means to an end &#8212; a method for solving problems. AI lowers the barrier between having a problem and implementing a computational solution. If something <em>can</em> be solved programmatically, AI dramatically accelerates getting there.</p><p>One thing I&#8217;ve noticed with Claude Code specifically: memory and context have improved significantly. Previously, every new session started from scratch with no accumulated context. Now, the agent can automatically build a memory file or be explicitly prompted to maintain one across sessions. It feels more like working with a collaborator than restarting a tool.</p><p>That said, QA and critical thinking remain distinctly human strengths. AI output still needs to be sense-checked. Common sense matters &#8212; the ability to notice when something doesn&#8217;t add up, when a result feels off, when the logic is internally consistent but wrong in practice. Judgment about <em>what&#8217;s right</em> is still something humans do better.</p><p>Looking further ahead, once AI reliably solves coding, it gains the ability to improve itself. At that point, the barrier to completing any cognitive task effectively disappears. The human role shifts toward defining objectives &#8212; what we want the world to look like, what problems are worth solving, and what the company should be doing. AI handles the execution.</p><p>But there&#8217;s a second, subtler role: QA at the objective level. We&#8217;ll need to examine AI-proposed solutions and verify they actually align with what we intended &#8212; until AI develops its own objectives, and becomes sophisticated enough that the misalignment isn&#8217;t obvious. That&#8217;s the part worth thinking carefully about now.</p>]]></content:encoded></item><item><title><![CDATA[The Risk Metric Translation Layer: Why Precision and FPR Aren't Mirror Images]]></title><description><![CDATA[Sometimes, I found it inefficient to communicate the antifraud team's performance metrics, such as precision and false positive rate, to other teams or even within the antifraud team.]]></description><link>https://www.zhizhi-gewu.com/p/the-risk-metric-translation-layer</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/the-risk-metric-translation-layer</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 07 Feb 2026 08:10:00 GMT</pubDate><content:encoded><![CDATA[<p>Sometimes, I found it inefficient to communicate the antifraud team's performance metrics, such as precision and false positive rate, to other teams or even within the antifraud team. It is because those words are also used loosely in everyday communication. I said, &#8220;Our policy precision is 90%.&#8221; The counterpart replied, &#8220;10% false positive rate is too high to accept.&#8221; You know there is a gap in the understanding of precision and false positive rate (FPR). </p><p>On the business side, when they say &#8220;10% FPR is too high,&#8221; they mean: out of all the good users, how many are falsely labelled as bad? They worried about user experience. On the risk side, when we say &#8220;precision is 90%&#8221;, we mean: out of all the users who are being labelled as bad, how many are actually bad?. We are concerned about the performance of the models/rules themselves. By deriving the relationship, we can bridge the gaps between two metrics.</p><p>Let&#8217;s revisit the <a href="https://www.zhizhi-gewu.com/p/auc-ks-precision-and-recall-a-risk">two-room analogy</a> for intuitive understanding. FPR only cares about the good users&#8217; room (N). If FPR increases, the absolute number of false positives increases. Precision [TP/(TP+FP)]will drop as well because the denominator became larger. But let&#8217;s keep precision constant since we want to find the relationship between the two. This forces the model to capture more TP. In other words, recall (TP/P) must increase. Finally, since FPR and recall are just ratios, the relative number of N and P also affects the relationship, a.ka. prevalence [P/(P+N)].  To summarize, precision and FPR are connected by recall and prevalence. 90% precision does not imply an FPR of 10%, because we must also account for recall and the natural fraud rate (prevalence). </p><p>Mathematically, we can derive the relationship as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{align}\nprecision &amp;= \\frac{TP}{TP + FP} \\\\\n&amp;= \\frac{TP}{TP + N \\cdot FPR} \\\\\n&amp;= \\frac{P \\cdot Recall}{P \\cdot Recall + N \\cdot FPR} \\\\\n&amp;= \\frac{\\theta \\cdot Recall}{\\theta \\cdot Recall + (1-\\theta )\\cdot FPR}\n\\end{align}&quot;,&quot;id&quot;:&quot;GLFYRUITGP&quot;}" data-component-name="LatexBlockToDOM"></div><p>where theta is the prevalence.</p><p>Consider the following scenarios to consolidate the intuition, assuming the model is reasonably good (both precision and recall are high):</p><ul><li><p>FPR is low, and precision is high when prevalence is low.</p></li><li><p>FPR and precision are high when prevalence is high.</p></li></ul><p>You may already notice that even if model performance is poor, FPR can be low if prevalence is very low. For example, if the prevalence is 0.1%, the recall is 10%, the precision is 20%, and the FPR is only 0.04%.</p><p>Finally, the question is which number should we use. I think both should be reported separately, as the audiences are different. For the risk team, we should focus on precision and recall, as they are directly linked to model/policy performance. However, we should also check FPR as it affects the user experience.</p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Absence of Evidence ≠ Evidence of Absence: Rethinking Fraud Prevention KPIs]]></title><description><![CDATA[The Zero-Incident Paradox: Why Silence Isn't Always Golden in Fraud Prevention Using Bayesian reasoning to measure what you cannot directly observe]]></description><link>https://www.zhizhi-gewu.com/p/absence-of-evidence-evidence-of-absence</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/absence-of-evidence-evidence-of-absence</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 24 Jan 2026 02:00:54 GMT</pubDate><content:encoded><![CDATA[<h2><strong>The Core Paradox</strong></h2><p>The Fundamental Challenge: Low incident rates in fraud prevention could indicate either:</p><ul><li><p><strong>Strong defenses</strong> - The system is working effectively</p></li><li><p><strong>Lack of attempts</strong> - There simply haven&#8217;t been many fraud attempts</p></li></ul><p>This ambiguity makes it difficult to assess the true effectiveness of fraud prevention measures.</p><p><strong>The Unknown Unknowns Problem</strong></p><p>Beyond measuring what we know, there&#8217;s a deeper challenge: <strong>you cannot directly measure what you are unaware of</strong>. This is the classic &#8220;unknown unknowns&#8221; dilemma in security and fraud prevention.</p><h2><strong>Bayesian Framework</strong></h2><p>To address these challenges, we can apply Bayesian reasoning:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;P(\\text{Fraud}|\\text{No Incident}) = \\frac{P(\\text{No Incident}|\\text{Fraud}) \\cdot P(\\text{Fraud})}{P(\\text{No Incident})}&quot;,&quot;id&quot;:&quot;JDKNFZKVVU&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Interpreting the Evidence</strong></p><ul><li><p><strong>When the likelihood is high</strong>: <em>P</em>(No Incident&#8739;Fraud) is high</p><ul><li><p>The absence of fraud incidents strongly indicates effective fraud prevention</p></li></ul></li><li><p><strong>When the likelihood is low</strong>: <em>P</em>(No Incident&#8739;Fraud) is low</p><ul><li><p>The Absence of incidents provides <strong>little information</strong> about fraud prevention effectiveness</p></li></ul></li></ul><h2><strong>Measuring the Unmeasurable</strong></h2><h3><strong>The Challenge</strong></h3><p>Evaluating the likelihoods requires:</p><ul><li><p>Understanding the threat landscape</p></li><li><p>Assessing coverage of known fraud methods</p></li><li><p>Accounting for unknown fraud methods (which cannot be directly measured)</p></li></ul><h3><strong>Indirect Measurement Methods</strong></h3><p><strong>Approaches to Discover Unknown Unknowns</strong></p><p>When direct measurement is impossible, we can use these indirect methods:</p><ol><li><p><strong>Red Teaming</strong> - Simulate adversarial attacks to find vulnerabilities</p></li><li><p><strong>External Threat Intelligence</strong> - Learn from industry patterns and breaches</p></li><li><p><strong>Post-Mortem Analysis</strong> - Learn from failures when they occur</p></li><li><p><strong>First Principles Decomposition</strong> - Break down attack surfaces systematically</p></li></ol><h2><strong>Practical Framework</strong></h2><p>Measuring Fraud Prevention Success: To measure the success of fraud prevention in low incident scenarios, focus on:</p><ol><li><p><strong>Coverage Metrics</strong></p><ol><li><p>Percentage of known fraud methods with active defenses</p></li><li><p>Depth and breadth of detection capabilities (Cost of attack)</p></li></ol></li><li><p><strong>Discovery Rate</strong></p><ol><li><p>Incremental discovery of new fraud methods over time</p></li><li><p>Rate of vulnerability identification and remediation</p></li></ol></li><li><p><strong>Process Interception Metrics</strong></p><ol><li><p>Trigger rate: Proportion of applications/transactions flagged by rules</p></li><li><p>Hit rate: Proportion of triggered cases with confirmed abnormal behaviors (grey areas go to manual review)</p></li></ol></li></ol><p>This approach helps infer effectiveness even when direct incident data is sparse.</p>]]></content:encoded></item><item><title><![CDATA[Can You Solve This? Calculating KS from a Quarter-Circle ROC Curve]]></title><description><![CDATA[I recently came across an interesting interview question that bridges geometry and risk modeling: &#8220;If a model&#8217;s ROC curve is a perfect concave down quarter-circle, what is its KS value?&#8221;]]></description><link>https://www.zhizhi-gewu.com/p/can-you-solve-this-calculating-ks</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/can-you-solve-this-calculating-ks</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 11 Jan 2026 11:06:47 GMT</pubDate><content:encoded><![CDATA[<p>I recently came across an <a href="https://zhuanlan.zhihu.com/p/367772812">interesting interview question</a> that bridges geometry and risk modeling: &#8220;If a model&#8217;s ROC curve is a perfect concave down quarter-circle, what is its KS value?&#8221;</p><p>In my <a href="https://www.zhizhi-gewu.com/p/auc-ks-precision-and-recall-a-risk">last post</a>, I discussed the relationship between AUC and KS. Let&#8217;s build on that foundation to tackle this new question. Let&#8217;s recap the key concepts:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ul><li><p><strong>ROC Curve</strong>: A plot of the true positive rate against the false positive rate. A good model&#8217;s ROC curve bows towards the top-left corner because it maximizes true positives while minimizing false positives.</p></li><li><p><strong>KS Statistic</strong>: The maximum vertical distance between the cumulative distribution functions (CDFs) of the positive and negative classes. It quantifies how well the model separates the two classes.</p></li></ul><p>The <strong>key insight</strong> to solving this problem lies in the fact that the true positive rate is the same as the cumulative distribution function (CDF) for the positive class, and the false positive rate is the CDF for the negative class. Therefore, the KS statistic can be expressed as: <em>KS</em>=max(<em>TPR</em>&#8722;<em>FPR</em>). (Please refer to <a href="https://www.zhizhi-gewu.com/p/auc-ks-precision-and-recall-a-risk">my previous post</a> for a detailed explanation of this relationship.)</p><p>The equation for a circle centered at (<em>h</em>,<em>k</em>) with radius <em>r</em> is: (x-h)^2 + (y-k)^2 = r^2. For a quarter-circle ROC curve centered at (1,0) with radius 1, the equation simplifies to: (x-1)^2 + y^2 = 1. Rearranging gives us: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;y = \\sqrt{1 - (x - 1)^2} = \\sqrt{2x - x^2}&quot;,&quot;id&quot;:&quot;JLJEUQVHYW&quot;}" data-component-name="LatexBlockToDOM"></div><p>To find the KS value, we need to maximize y&#8722;x<em>y</em>&#8722;<em>x</em> over the interval [0,1]. This leads us to the function: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;f(x) = \\sqrt{2x - x^2} - x&quot;,&quot;id&quot;:&quot;OKEODIUJBK&quot;}" data-component-name="LatexBlockToDOM"></div><p>. Taking the derivative and setting it to zero gives us the critical points:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;f'(x) = \\frac{1 - x}{\\sqrt{2x - x^2}} - 1 = 0&quot;,&quot;id&quot;:&quot;JHZDOPPKWI&quot;}" data-component-name="LatexBlockToDOM"></div><p>Solving this equation, we find: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;x = 1 - \\frac{\\sqrt{2}}{2}&quot;,&quot;id&quot;:&quot;CAIQSYZYXC&quot;}" data-component-name="LatexBlockToDOM"></div><p> (Discarded another solution because the range of <em>x</em> is [0,1]).</p><p>Plugging this back into our function, we calculate:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;KS = f(x) = \\frac{\\sqrt{2}}{2} - \\left(1 - \\frac{\\sqrt{2}}{2}\\right) = \\sqrt{2} - 1 \\approx 0.414&quot;,&quot;id&quot;:&quot;RDZCJGMQDN&quot;}" data-component-name="LatexBlockToDOM"></div><p>If you can follow this reasoning and arrive at the same conclusion, congratulations! You&#8217;ve successfully connected geometric intuition with statistical modeling. The key learning of the question is that KS alone does not fully capture model performance. It only anchors the shape of the ROC curve.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Same Math, Different Enemies: The Unit Economics of Credit and Fraud Risk]]></title><description><![CDATA[In my previous post, I discussed standard risk metrics like AUC, KS, Precision, and Recall.]]></description><link>https://www.zhizhi-gewu.com/p/same-math-different-enemies-the-unit</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/same-math-different-enemies-the-unit</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sun, 28 Dec 2025 02:00:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ac3950d7-3d29-4728-ad21-9893f94e9745_2816x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my previous post, I discussed standard risk metrics like AUC, KS, Precision, and Recall. But metrics are just a means to an end. How do the actual <em>decision-making</em> processes differ between Credit Risk and Anti-Fraud?</p><p>Having recently rotated from the Credit Risk team to the Anti-Fraud team, I&#8217;ve realized that while the terminology differs, the fundamental logic&#8212;derived from the first principle of <strong>Maximizing Profit</strong>&#8212;is remarkably similar. Below, I demonstrate the mathematical unity of the two domains and highlight the key strategic differences.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>The Shared Math: Maximizing Incremental Value</strong></h2><p>Let&#8217;s start with the basics. Total profit equals the revenue from good users minus the losses from bad users.</p><ul><li><p><strong>True Negatives (TN):</strong> Approved users who pay (Profit).</p></li><li><p><strong>False Negatives (FN):</strong> Approved users who default/fraud (Loss).</p></li></ul><p>Define positive as &#8220;we block/reject because predicted bad,&#8221; and negative as &#8220;we approve.</p><p>We can express this as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Total\\ Profit = TN \\cdot R - FN \\cdot L&quot;,&quot;id&quot;:&quot;VVSJKWWODH&quot;}" data-component-name="LatexBlockToDOM"></div><p><em>Where </em>R<em> is Revenue per Good User and </em>L<em> is Loss per Bad User.</em></p><p>However, this equation is too static for strategy design. To decide whether to approve or reject a specific segment, we need to look at the <strong>Counterfactual</strong>: <em>What is the value added by our decision model compared to doing nothing?</em></p><p>If we compare our strategy against a baseline of &#8220;Approving Everyone,&#8221; the Total Profit can be rewritten as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{align*}\nTotal\\ Profit &amp;= TN \\cdot R - FN \\cdot L \\\\\n&amp;= (N_{good} - FP) \\cdot R - (N_{bad} - TP) \\cdot L \\\\\n&amp;= \\underbrace{(N_{good} \\cdot R - N_{bad} \\cdot L)}_{\\text{Baseline Profit}} + \\underbrace{(TP \\cdot L - FP \\cdot R)}_{\\text{Incremental Strategy Value}}\n\\end{align*}&quot;,&quot;id&quot;:&quot;NEVNJOAVAL&quot;}" data-component-name="LatexBlockToDOM"></div><ul><li><p>N_good,N_bad: Total population of good and bad users (Constant).</p></li><li><p>TP&#8901;L: The loss <strong>saved</strong> by correctly blocking bad users.</p></li><li><p>FP&#8901;R: The revenue <strong>sacrificed</strong> by incorrectly blocking good users.</p></li></ul><p>Since the Baseline Profit is constant (determined by the population), maximizing Total Profit is mathematically identical to maximizing <strong>Incremental Strategy Value</strong>.</p><h2><strong>The Optimality Condition: MC = MB</strong></h2><p>Economics teaches us that profit is maximized when <strong>Marginal Cost equals Marginal Benefit</strong>.</p><p>Let p^ be the probability that the next user we assess is &#8220;Bad.&#8221;</p><ul><li><p><strong>Marginal Benefit:</strong> If we block them and they are Bad, we save L.</p></li><li><p><strong>Marginal Cost:</strong> If we block them and they are Good, we lose R.</p></li></ul><p>We should stop blocking exactly when the cost outweighs the benefit:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{align*}\nMarginal\\ Benefit - Marginal\\ Cost &amp;= 0 \\\\\n\\hat{p} \\cdot L - (1 - \\hat{p}) \\cdot R &amp;= 0 \\\\\n\\hat{p} \\cdot (L + R) &amp;= R \\\\\n\\hat{p} &amp;= \\frac{R}{L + R}\n\\end{align*}&quot;,&quot;id&quot;:&quot;ERVYDVPNMQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>&#8203;&#8203;</p><p>This simple ratio, R/(L+R)&#8203;, is the &#8220;Universal Cutoff.&#8221; But the two teams view it from opposite ends.</p><h3><strong>1. The Anti-Fraud View (The Defender)</strong></h3><p>In fraud, we &#8220;block&#8221; transactions.</p><ul><li><p><strong>Revenue (R):</strong> The cost of insulting a good customer (C_insult), which is the interest loss.</p></li><li><p><strong>Loss (L):</strong> The fraud loss saved (C_fraud), which is the principal loss.</p></li></ul><p>The probability p^here represents the probability of fraud&#8212;which is exactly the definition of <strong>Precision</strong> for that marginal alert.</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Precision_{min} = \\frac{C_{insult}}{C_{insult} + C_{fraud}} = \\frac{Interest}{Interest + Principal}&quot;,&quot;id&quot;:&quot;VCXMFWSFNC&quot;}" data-component-name="LatexBlockToDOM"></div><p>&#8203;&#8203;If the model&#8217;s precision drops below this threshold, the cost of insulting good customers exceeds the value of the fraud we stop.</p><h4><strong>A Note on Terminology: Why does Probability (p^) equal Precision?</strong></h4><p>It might seem confusing to equate a single user&#8217;s probability score (p^&#8203;) with a group statistic like Precision. However, at the margin, they are identical. Simply apply the Law of Large Numbers:</p><p>Imagine our model assigns a risk score of <strong>0.20</strong> to a specific transaction. This prediction literally means: <em>&#8220;There is a 20% chance this specific transaction is fraud.&#8221;</em> , assuming the model is well-calibrated.</p><p>Now, imagine we gather <strong>100 transactions</strong> that all share this exact risk score of 0.20.</p><ul><li><p><strong>Expected Fraud (TP):</strong> 20</p></li><li><p><strong>Expected Legitimate (FP):</strong> 80</p></li></ul><p>If we block this specific bucket of users, the Precision is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Precision = \\frac{TP}{TP + FP} = \\frac{20}{20 + 80} = 20\\%&quot;,&quot;id&quot;:&quot;UNRJTFTAVO&quot;}" data-component-name="LatexBlockToDOM"></div><p>Thus, the <strong>Probability Score</strong> at the cutoff is simply the <strong>Marginal Precision</strong> of the decision at that cutoff.</p><h3><strong>2. The Credit Risk View (The Attacker)</strong></h3><p>In credit, we &#8220;approve&#8221; loans.</p><ul><li><p><strong>Revenue (R):</strong> Interest Income.</p></li><li><p><strong>Loss (L):</strong> Principal Loss.</p></li></ul><p>The probability p^&#8203; here represents the <strong>Probability of Default (PD)</strong>.</p><p></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;PD_{max} = \\frac{Interest}{Principal + Interest}&quot;,&quot;id&quot;:&quot;YZKABCXXAE&quot;}" data-component-name="LatexBlockToDOM"></div><p>&#8203;If the user&#8217;s PD rises above this threshold, the expected principal loss exceeds the potential interest income. (Of course, this is a simplified way to calculate revenue and cost but it captures the essence.)</p><h3><strong>Conclusion: Same Line, Opposite Directions</strong></h3><p>Mathematically, <strong>Minimum Precision</strong> and <strong>Maximum PD</strong> are the exact same number.</p><ul><li><p>The <strong>Anti-Fraud</strong> team defends the gate, blocking bad actors until the precision drops to the cutoff.</p></li><li><p>The <strong>Credit Risk</strong> team expands the market, approving users until the risk rises to the cutoff.</p></li></ul><h2><strong>The Strategic Divergence: Different Enemies</strong></h2><p>If the math is identical, why do the jobs feel so different? Because while the <em>equation</em> is the same, the <em>enemy</em> is not.</p><h3><strong>1. Definition of &#8220;Bad&#8221;</strong></h3><ul><li><p><strong>Credit Risk:</strong> &#8220;Bad&#8221; is defined solely by default.</p></li><li><p><strong>Anti-Fraud:</strong> &#8220;Bad&#8221; is defined by intent (Deception). Thus, default alone is not sufficient. The team will also look for suspicous patterns.</p></li></ul><h3><strong>2. Static vs. Dynamic (Game Theory)</strong></h3><p>This is the most critical difference.</p><ul><li><p><strong>Credit Risk mostly plays against Nature.</strong> Borrowers are relatively stable. A user with a 620 credit score today behaves similarly to a 620 user yesterday. Historical &#8220;Vintage&#8221; data is highly predictive of the future.</p></li><li><p><strong>Anti-Fraud plays against an Adversary.</strong> Fraudsters are intelligent, coordinated, and reactive. If you set a static rule to block X, they immediately shift to Y.</p><ul><li><p><em>Implication:</em> Credit teams optimize for <strong>Efficiency</strong> (Calibration). Fraud teams must optimize for <strong>Adaptability</strong> (Exploration).</p></li></ul></li></ul><h3><strong>3. The Action Space</strong></h3><ul><li><p><strong>Anti-Fraud:</strong> The decision is usually binary (Approve vs. Reject) or friction-based (Step-up Verification).</p></li><li><p><strong>Credit Risk:</strong> The decision is multi-dimensional. We can manage risk not just by rejecting, but by adjusting the <strong>Credit Limit</strong>, <strong>Tenure</strong>, or <strong>Pricing (EIR)</strong>. We have more levers to force the unit economics to work.</p></li></ul><h3><strong>4. The Entity Dimension (Multi-Modal Risk)</strong></h3><ul><li><p><strong>Credit Risk</strong> is almost exclusively <strong>User-Centric</strong>. We underwrite the person (or the business entity) applying for funds. The unit of analysis is stable.</p></li><li><p><strong>Anti-Fraud</strong> is <strong>Multi-Modal</strong>. We don&#8217;t just assess the User; we assess the Device, the IP address, the Credit Card, and the Merchant.</p></li></ul><p><strong>The Economic Implication:</strong> The variables in our profit equation (C_insult&#8203; and C_fraud&#8203;) shift drastically depending on <em>what</em> we are blocking.</p><ul><li><p><strong>Blocking a Device:</strong> If I block a suspicious device ID, the Cinsult<em>Cinsult</em>&#8203; might be low (the user is annoyed but can switch devices). <em>Result:</em> I can afford a <strong>lower precision</strong> threshold.</p></li><li><p><strong>Blocking a Merchant:</strong> If I block a merchant in a marketplace, I cut off revenue from <em>all</em> their customers. The C_insult(Lost Revenue) is massive. <em>Result:</em> I need an extremely <strong>high precision</strong> threshold&#8212;often requiring manual review&#8212;before pulling the trigger.</p></li></ul><p>While Credit Risk optimizes one curve (User Risk), Anti-Fraud constantly juggles multiple curves with different breakeven points.</p><h2>Closing Thoughts</h2><p>I am still very much a learner in this space, but realizing that Credit Risk and Anti-Fraud share the same Unit Economics has been a helpful anchor for me. </p><p>It means that while I am learning new tactics (Game Theory, Pattern Recognition), the underlying grammar of <strong>Profit Maximization</strong> remains the same. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[AUC, KS, Precision, and Recall: A Risk Analyst’s Guide]]></title><description><![CDATA[As analysts in the risk management industry, we live and breathe acronyms like AUC (Area Under the Curve) and KS (Kolmogorov-Smirnov).]]></description><link>https://www.zhizhi-gewu.com/p/auc-ks-precision-and-recall-a-risk</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/auc-ks-precision-and-recall-a-risk</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 20 Dec 2025 02:00:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/00e17c0d-0bf8-43e5-8b80-eef275a74334_1174x781.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As analysts in the risk management industry, we live and breathe acronyms like <strong>AUC</strong> (Area Under the Curve) and <strong>KS</strong> (Kolmogorov-Smirnov). These are the gold standards for evaluating classification models in credit risk. However, if you&#8212;like me&#8212;have recently rotated into an <strong>Anti-Fraud</strong> team from credit risk team, you&#8217;ve likely encountered two different metric kings: <strong>Precision</strong> and <strong>Recall</strong>.</p><p>How do these metrics relate to each other?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>The &#8220;Two Rooms&#8221; Analogy</strong></h2><p>The standard confusion matrix terms&#8212;True Positive (TP), False Positive (FP), True Negative (TN), and False Negative (FN)&#8212;can be headache-inducing to communicate in practice.</p><p>To simplify this, imagine two separate rooms:</p><ol><li><p><strong>The Bad Room:</strong> Contains <em>only</em> actual bad actors (Total Bad = TP + FN).</p></li><li><p><strong>The Good Room:</strong> Contains <em>only</em> actual good users (Total Good = TN + FP).</p></li></ol><p>Your model is a gatekeeper. You walk into each room with your classifier.</p><ul><li><p>In the <strong>Bad Room</strong>, you want the model to identify everyone as bad. The percentage of people you successfully catch here is your <strong>True Positive Rate (TPR)</strong>.</p></li><li><p>In the <strong>Good Room</strong>, you want the model to identify no one as bad. The percentage of people you mistakenly flag as bad here is your <strong>False Positive Rate (FPR)</strong>.</p></li></ul><p>A perfect model has a TPR of 100% (catches everyone in the Bad Room) and an FPR of 0% (flags no one in the Good Room).</p><h3><strong>Visualizing AUC and KS</strong></h3><p>Realistically, models output a <em>probability</em> (e.g., &#8220;There is a 90% chance this user is a fraud&#8221;). We use a &#8220;threshold dial&#8221; to decide who to flag.</p><p>Imagine turning this dial:</p><ul><li><p><strong>Threshold 100% (Strict):</strong> You only flag apparent fraud. You catch almost no one in the Bad Room (TPR&#8776;0), but you also annoy no one in the Good Room (FPR&#8776;0). This is the point (0,0)on the ROC plot.</p></li><li><p><strong>Threshold 0% (Loose):</strong> You flag everyone. You catch every fraudster (TPR=100%), but you also falsely flag every good user (FPR=100%). This is the point (1,1).</p></li></ul><p><strong>AUC (Area Under the Curve)</strong> measures the model&#8217;s performance across <em>all possible settings</em> of this dial. It plots TPR against FPR. A random guess gives you a straight diagonal line (AUC 0.5), meaning for every extra bad person you catch, you annoy a proportional number of good people. A good model bows upward, maximizing the gap between the TPR and FPR.</p><p><strong>KS (Kolmogorov-Smirnov)</strong> focuses on the single best point on that curve. It is simply the <strong>maximum difference</strong> between TPR and FPR. While AUC looks at the whole story, KS asks: &#8220;At the single best setting of the dial, how much separation can we get between the good and bad populations?&#8221; </p><h4>Deep Dive: Why KS is Cumulative</h4><p>KS is usually plotted using the cumulative number of bads and goods. The maximum difference between the two curves is the KS.</p><p>Imagine taking all the people from <em>both</em> rooms and lining them up in a single queue, sorted by their model score from <strong>Highest (Most Suspicious)</strong> to <strong>Lowest (Least Suspicious)</strong>.</p><p>Now, imagine walking down this line from the start. This is equivalent to lowering your threshold.</p><ol><li><p><strong>Cumulative Bad (TPR):</strong> Every time you walk past a <em>Bad Person</em>, you add them to your count. If there are 100 Bad People total, and you have passed 50 of them, your &#8220;Cumulative Bad %&#8221; is 50%. <strong>This is exactly the True Positive Rate (TPR).</strong></p></li><li><p><strong>Cumulative Good (FPR):</strong> Every time you walk past a <em>Good Person</em>, you add them to your count. If there are 1,000 Good People total, and you have passed 100 of them, your &#8220;Cumulative Good %&#8221; is 10%. <strong>This is exactly the False Positive Rate (FPR)</strong></p></li></ol><p>Below is a simulation dashboard I asked Gemini to build to visualize these concepts: <a href="https://gemini.google.com/share/0ade30dc6f52">Link</a></p><h3><strong>Why Anti-Fraud Cares: Precision and Recall</strong></h3><p>In the anti-fraud world, <strong>Recall</strong> is just another word for TPR (Bad Room coverage). But <strong>Precision</strong> is different.</p><p>While TPR and FPR require you to look at the rooms separately, <strong>Precision</strong> requires you to look at <em>who the model flagged</em> from both rooms combined. It asks: <em>&#8220;Of all the people the model claimed were bad, how many were actually bad?&#8221;</em></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;Precision = \\frac{TP}{TP + FP}&quot;,&quot;id&quot;:&quot;UMOMZMZBHF&quot;}" data-component-name="LatexBlockToDOM"></div><p>&#8203;Why is this preferred over FPR in fraud operations?</p><ol><li><p><strong>Operational Reality:</strong> In fraud, a positive flag usually triggers a manual review, an SMS alert, or a transaction block. These actions have direct costs (agent time) and customer friction (insult). Precision measures the &#8220;purity&#8221; of the alert queue.</p></li><li><p><strong>The Class Imbalance Problem:</strong> This is the key differentiator. Precision is highly sensitive to the <strong>Bad Rate</strong>.</p><ul><li><p><strong>FPR</strong> is calculated <em>only</em> inside the Good Room. If you double the number of good customers, the FPR remains stable.</p></li><li><p><strong>Precision</strong> depends on the ratio of good to bad. If the number of good customers explodes while the number of fraudsters stays the same, your Precision will plummet because the &#8220;noise&#8221; (False Positives) drowns out the signal.</p></li></ul></li></ol><p>In summary, the Modeling Team often focuses on <strong>AUC/KS</strong> because they measure the model&#8217;s pure ability to rank order, independent of the portfolio&#8217;s bad rate. Anti-Fraud focuses on <strong>Precision</strong> because it reflects the actual operational pain of sifting through false alarms in a sea of good transactions.</p><h2><strong>Why does the credit risk team seldom look into Precision and Recall?</strong></h2><p>This question deserves a dedicated deep dive in the next post.</p><p>Fundamentally, both Underwriting and Anti-Fraud teams share the exact same goal: maximizing profit. They simply approach the P&amp;L equation from opposite ends (Alert: Fraud is a dynamic process. The environment is not stable because the fraudster evolves.):</p><ul><li><p>Underwriting aims to maximize disbursements. They expand approvals until the marginal cost of defaults outweighs the marginal revenue from interest. Their constraint is the Breakeven Cost of Risk.</p></li><li><p>Anti-Fraud aims to minimize losses. They expand fraud detection until the cost of friction (insulting good customers) outweighs the savings from stopping fraud. Their constraint is Breakeven Precision.</p></li></ul><p>In my following note, I plan to demonstrate mathematically that these two concepts are identical: the Breakeven Cost of Risk in lending equals the Breakeven Precision in fraud prevention.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[It’s Not the Chaos, It’s the Expectation: A Framework for Deconstructing Anxiety]]></title><description><![CDATA[Recently, I&#8217;ve spent time reflecting on the problem of anxiety.]]></description><link>https://www.zhizhi-gewu.com/p/its-not-the-chaos-its-the-expectation</link><guid isPermaLink="false">https://www.zhizhi-gewu.com/p/its-not-the-chaos-its-the-expectation</guid><dc:creator><![CDATA[KY John]]></dc:creator><pubDate>Sat, 13 Dec 2025 01:00:45 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8e786947-04d0-404c-a2e0-3dc55eaf7803_2816x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I&#8217;ve spent time reflecting on the problem of anxiety. It&#8217;s something many of us deal with, often feeling like a vague, overwhelming fog.</p><p>But when I sat down to analyze my own experiences&#8212;trying to pinpoint exactly <em>where</em> that feeling comes from&#8212;I realized that anxiety isn&#8217;t usually random chaos. It&#8217;s almost always structural. It stems from specific flaws in the mental models I use to navigate the world.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>If we can identify the roots of the anxiety, we can build a framework to handle it.</p><p>Through my reflection, I identified three main sources of anxiety, how they interconnect, and a two-step approach to regain our footing.</p><h2>Part 1: The Diagnosis (Where Anxiety Comes From)</h2><p>In my experience, anxiety isn&#8217;t usually caused by the event itself, but rather my <em>relationship</em> to the event. It almost always stems from one of these three situations:</p><h3>1. The Expectation Gap (The Illusion of Control)</h3><p>We often hear that anxiety comes from a &#8220;loss of control.&#8221; But that&#8217;s only half the story. Before you can lose control, there must be an <strong>expectation</strong> that you had it in the first place.</p><p>Anxiety thrives in the gap between our expectations and reality. Sometimes, we set the bar impossibly high, wanting control over things that are inherently uncontrollable. When we expect to be in the driver&#8217;s seat and reality suddenly grabs the wheel, panic sets in. The anxiety isn&#8217;t just about what is happening; it is the friction caused by our resistance to reality.</p><h3>2. The Value Vacuum (Not Knowing What You Want)</h3><p>If you don&#8217;t have a clear internal hierarchy of values&#8212;knowing exactly what you want and what is truly important to you&#8212;you become a reactive vessel for external pressures.</p><p>We live in an era of massive information flow and endless options. Without a strong internal compass, everything feels equally important. The urgent drowns out the important. This leads to chronic overwhelm as we try to juggle conflicting priorities that we didn&#8217;t even choose for ourselves.</p><h3>3. The Social Mirror (External Validation)</h3><p>This is perhaps the most paralyzing source: putting too much emphasis on other people&#8217;s views of us.</p><p>When we don&#8217;t know our own value (point #2), we outsource the measuring of it to society. We look into the &#8220;social mirror&#8221; to see if we are okay. The problem is that the mirror is constantly changing, and we have zero control over what others think. Basing your stability on something unstable is a recipe for constant anxiety.</p><h2>Part 2: The Prescription (How to Handle It)</h2><p>So, how do we break this cycle? The solution requires flipping our operating system. Instead of looking outward for cues, we must start inward.</p><h3>Step 1: Define Your &#8220;Self&#8221; First (First Principles Thinking)</h3><p>To handle anxiety, you must make your own self the priority. Before you look at the world, you have to look in the mirror and ask: <em>What do I actually want? What is genuinely important to me?</em></p><p>This is difficult. We are conditioned by social norms and existing incentive systems. We often treat these systems (like career ladders or social expectations) as rigid boundaries.</p><p>But we must remember that these systems were designed by <em>other people</em> to put constraints on behavior. Don&#8217;t treat them as immutable laws of physics before you even know what you want.</p><p><strong>The shift:</strong> Figure out what you value first. <em>After</em> you know what you want, then you can look at the societal constraints and decide consciously whether you want to work within them or break them. You become the actor, not the reactor.</p><h3>Step 2: The Spheres of Control</h3><p>Once you know what you want, the final step is calibrating your effort. While the Stoics famously spoke of the &#8220;dichotomy of control&#8221; (what is yours vs. what isn&#8217;t), I find it more useful to split control into three distinct rings.</p><p>Ring 1: Direct Control</p><p>This is your internal territory. It includes your actions, your effort, what you say, and how you allocate your time. This is the only place where you have total agency.</p><p>Ring 2: Influence</p><p>This is the gray area where most anxiety lives. It includes relationships, team decisions, negotiations, and probabilities. You can affect the outcome here, but you cannot dictate it.</p><p>Ring 3: No Control</p><p>This is the environment. It includes the macro economy, the weather, the past, and other people&#8217;s internal states.</p><p>How this fixes anxiety:</p><p>Anxiety is usually a result of trying to apply Ring 1 energy to a Ring 3 problem. To reduce it, you must map your worries to the correct ring:</p><ul><li><p><strong>In Ring 1:</strong> Act strongly. Focus on high-quality input and effort.</p></li><li><p><strong>In Ring 2:</strong> Experiment and negotiate. Influence the odds, but detach from the guarantee.</p></li><li><p><strong>In Ring 3:</strong> Practice acceptance. Observe it without trying to change it.</p></li></ul><h2>Moving Forward</h2><p>Anxiety often feels like a defect, but I&#8217;m starting to view it as a signal.</p><p>It&#8217;s a signal that my expectations are out of alignment with reality, or that I&#8217;m trying to control the uncontrollable. By using this framework to diagnose the source and placing my efforts in the correct ring, the fog begins to lift.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.zhizhi-gewu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading ZhiZhi Gewu! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>