<?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[Human in the Loop]]></title><description><![CDATA[My personal Substack]]></description><link>https://ai.oyebode.com</link><image><url>https://substackcdn.com/image/fetch/$s_!JGLi!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13ec575b-5675-4d62-9390-aa902abbb0ed_1254x1254.png</url><title>Human in the Loop</title><link>https://ai.oyebode.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 29 Jun 2026 14:18:58 GMT</lastBuildDate><atom:link href="https://ai.oyebode.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Oyetade]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[oyetade@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[oyetade@substack.com]]></itunes:email><itunes:name><![CDATA[Oyetade]]></itunes:name></itunes:owner><itunes:author><![CDATA[Oyetade]]></itunes:author><googleplay:owner><![CDATA[oyetade@substack.com]]></googleplay:owner><googleplay:email><![CDATA[oyetade@substack.com]]></googleplay:email><googleplay:author><![CDATA[Oyetade]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Week 1: Can Linear Regression Predict Tomorrow’s Market?]]></title><description><![CDATA[Applying Machine Learning to Financial Data, Part 1 of 52]]></description><link>https://ai.oyebode.com/p/week-1-can-linear-regression-predict</link><guid isPermaLink="false">https://ai.oyebode.com/p/week-1-can-linear-regression-predict</guid><dc:creator><![CDATA[Oyetade]]></dc:creator><pubDate>Sat, 20 Jun 2026 11:27:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0qax!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><p>If you want to understand why this belong in my &#8220;Human in the Loop&#8221; series, skip to the end of the article.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.oyebode.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 Human in the Loop! 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>This is the first of fifty-two weekly articles. Each one takes a single machine learning technique and turns it loose on financial data that anybody can obtain without paying for it. The series will climb, over the course of the year, from the plainest tool in the box to the architectures that sit behind the present excitement about artificial intelligence. We begin, deliberately, at the bottom of that climb, with ordinary linear regression, and we point it at the most unforgiving target there is: the daily return of the stock market.</p><p>The choice is not an accident. Before any of the clever methods arrive, it is worth establishing a discipline, because in this field the discipline matters a great deal more than the algorithm. A linear regression carried out with care will teach you more about financial machine learning than a transformer thrown together without it. So I want to start with a clear and slightly provocative question. Using nothing more than the recent history of returns and trading volume, can a simple linear model tell us whether the market will rise or fall tomorrow?</p><h2>Saying the result out loud before we begin</h2><p>It is only fair to state, before we touch the data, what we expect to find. Daily index returns behave very nearly like a random walk. Tomorrow&#8217;s move is governed for the most part by information that has not yet arrived, and the small remainder that might in principle be foreseeable is exactly the remainder that thousands of well-resourced participants are paid to compete away. If a model a hobbyist could build from a handful of past returns were able to call tomorrow&#8217;s direction reliably, that edge would have been bid out of existence long ago.</p><p>The honest expectation, then, is an out-of-sample fit hovering around zero, very possibly the wrong side of it, and a hit rate on direction that sits within a whisker of one half. I want to be plain that this is not the experiment failing. This is the experiment succeeding at telling us the truth. The whole value of the week lies in the method by which we reach that truth without deceiving ourselves, because it is the very same method that will keep us honest later, when the models are powerful enough to manufacture a thoroughly convincing illusion of skill.</p><h2>The data</h2><p>We use the daily closing price and trading volume of the S&amp;P 500, retrieved through the <code>yfinance</code> library. It is free, it is everywhere, and it is entirely sufficient. The script caches what it downloads to a local file, so that the analysis can be reproduced even after the data provider has quietly changed something underneath it, which it will. It also records the exact span of dates it used, because a result you cannot date is a result you cannot trust.</p><p>One practical warning, which will return again and again across this series. The single most common way to produce a spectacular and entirely false result in financial machine learning is to allow information from the future to seep into the past. We will guard against it with something close to paranoia.</p><h2>Building features without cheating</h2><p>Every feature must be something we could genuinely have known at the moment we would have had to act on it. We are predicting the return from today&#8217;s close to tomorrow&#8217;s close, so every input has to be available by today&#8217;s close and not one second after it.</p><p>With that rule held firmly in mind, the features are modest and intuitive. We take the five most recent daily returns, the one that has just finished and the four before it. We add a five-day average of returns and two measures of recent volatility, over ten days and twenty-one, so the model can see both the recent drift and the recent turbulence. For volume we do not hand the model the raw figure, because volume creeps upward across the decades and a model given a steadily rising number will cheerfully mistake the mere passage of time for a signal. Instead we measure log volume against its own twenty-one-day average, which removes the trend and keeps the part that actually carries information, namely whether today was unusually busy.</p><p>The target is the next day&#8217;s return. In the code this is the return series shifted back by one position, so that the row labelled today carries tomorrow&#8217;s outcome. That single line is the precise spot at which most amateur backtests quietly cheat, and it earns both a comment in the source and a moment of your attention.</p><h2>The rule that governs everything: respect time</h2><p>Here is the point on which the entire series will insist, and it is worth stating without hedging. You must never shuffle financial data into a random training and testing split. The ordinary cross-validation that serves you so well on a collection of unrelated photographs is actively harmful here, because it trains on the future in order to predict the past and then reports back a fantasy.</p><p>So we split by time. The model learns on the earlier stretch of history and is judged on the most recent stretch, which it has never seen. For comparison and tuning we use walk-forward validation, expanding the training window forward through time, which is what scikit-learn&#8217;s <code>TimeSeriesSplit</code> provides. Any rescaling of the data is fitted on the training portion alone and then applied to the test portion, never the other way around, and we enforce this by binding the scaler and the regression together into a single pipeline, so that leakage becomes structurally impossible rather than merely something we tried to remember to avoid.</p><h2>Baselines come before the model</h2><p>A model is only ever as impressive as the dim-witted alternative it manages to beat. So before fitting anything, we write down three baselines. Predict zero every day. Predict the historical average return every day. Predict that tomorrow will simply repeat today. Each is trivial, and our regression must be measured against them rather than against some abstract notion of accuracy. A model that cannot beat &#8220;predict the average&#8221; has learned precisely nothing, and we want to discover that at once, rather than after we have talked ourselves into a comforting story.</p><h2>What actually happened</h2><p>With the apparatus in place, the results arrive quickly and without ceremony. The figures below come from a run over roughly five thousand trading days, with the final fifth held back for testing.</p><p>Begin with the baselines, because they frame everything that follows. Predicting the training mean every single day produced an out-of-sample fit of essentially zero, an R-squared of around  -0.0002, which is what &#8220;no information, no error beyond the irreducible&#8221; looks like. More telling is the direction. Both the flat-zero and the flat-mean rules called the market correctly on 52.7 per cent of test days. They achieved this for the dullest of reasons: the market rose on more than half the days in the sample, so the rule &#8220;always guess up&#8221; is quietly rather good. Hold on to that number, 52.7 per cent, because it is the bar the model has to clear.</p><p>The model does not clear it. The linear regression returned an out-of-sample R-squared of -0.028. That minus sign is not a rounding artefact. It means the model performed worse than simply predicting the average, which is the signature of a model that has fitted noise: the relationships it learned on the training years did not survive into the test years, so applying them did active harm. Its directional accuracy was 50.7 per cent. The model, in other words, was beaten on direction by the most trivial rule available to us, the one that ignores the data entirely and always bets on a rise.</p><p>The walk-forward folds tell the same story, and they tell it in a more revealing way. They ran from -0.043 to -0.04 p, a mean a touch below zero and a spread more than twice the size of that mean. That instability is itself the finding. A genuine and durable signal would hold its sign from one window to the next. What we have instead is sampling noise wearing a different mask in each period.</p><p>The coefficients confirm it once more. After standardisation, every one of them sits at the third decimal place or smaller, with none standing out as carrying real weight. The largest, on yesterday&#8217;s return, is mildly negative, a faint scent of next-day mean reversion, far too weak to do anything with. The two volatility terms come out roughly equal and opposite, which is the classic appearance of two collinear features dividing a single weak effect between them rather than reporting two real ones.</p><p>Then comes the test that actually settles the matter, the one with money in it. We let the model go long whenever it predicted a rise and sit in cash otherwise, and we charged a single basis point on every change of position. The strategy earned a Sharpe ratio of 0.49. Taken on its own that looks respectable, which is exactly the trap. Buying the index and holding it returned a Sharpe of 0.65 over the same window. The strategy underperformed doing nothing, and it did so after the gentlest possible allowance for costs. It spent stretches of time sitting in cash, missing the upward drift that is the market&#8217;s most dependable feature, and paid a toll for the privilege. The two equity curves below wander around one another for years before the trading rule quietly falls behind.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0qax!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0qax!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png 424w, https://substackcdn.com/image/fetch/$s_!0qax!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png 848w, https://substackcdn.com/image/fetch/$s_!0qax!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png 1272w, https://substackcdn.com/image/fetch/$s_!0qax!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0qax!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png" width="1170" height="650" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:650,&quot;width&quot;:1170,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98168,&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://ai.oyebode.com/i/202828990?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.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_!0qax!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png 424w, https://substackcdn.com/image/fetch/$s_!0qax!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png 848w, https://substackcdn.com/image/fetch/$s_!0qax!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.png 1272w, https://substackcdn.com/image/fetch/$s_!0qax!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5beedaf0-ccc0-4952-8da9-7be1825c00ac_1170x650.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><h2>The one result that looks like good news, and why it is not</h2><p>There is a loose thread to tie off, and it is the most instructive part of the whole exercise. When we fit the same model across the entire dataset and ask the formal statistical question, the regression comes back significant. The full-sample R-squared is 0.019 and the F-test returns a p-value of 0.009, comfortably past the one per cent threshold at which we conventionally declare an effect real.</p><p>A reader in a hurry would seize on this and announce that the model works after all. It does not, and reconciling the two findings is the lesson of the week.</p><p>The first point is the one we built the whole apparatus to respect. That significant R-squared was measured in-sample, on the same data the model was fitted to, including the very test period it then failed on. The minus zero point zero two eight was measured out-of-sample, on data the model had never met. A relationship can be statistically detectable in-sample and completely useless out-of-sample, and the gap between those two numbers is precisely the distance between a result and a delusion.</p><p>The second point is subtler and more important. Statistical significance and economic significance are not the same thing, and conflating them is one of the most expensive habits a person can carry into markets. With five thousand observations we have enough statistical power to detect an effect that accounts for under two per cent of the variation in daily returns. The p-value of 0.009 is reporting, quite truthfully, that a faint linear structure exists. The out-of-sample R-squared and the backtest are reporting, equally truthfully, that the structure is far too small to capture cleanly or to trade profitably once the world charges you to act on it. Both statements are correct at the same time. Markets are nearly efficient rather than perfectly so, and this is what &#8220;nearly&#8221; looks like when you write it down in numbers: real, detectable, and worthless.</p><p>There is also a small warning that the software will raise about the rank of the coefficient covariance. It is telling us that two of our features are very nearly the same thing, almost certainly the five-day mean of returns standing in for the lagged returns it is built from. It does not disturb the headline R-squared or the F-test, but it does mean the individual coefficient standard errors should not be read too closely. If clean per-coefficient inference were the goal, the redundant feature would come out. For our purpose, which is the overall verdict, the headline figures stand.</p><h2>Why this is the right answer</h2><p>It would have been the easiest thing in the world to present a tortured version of this analysis that produced a winner. Shuffle the data so the future leaks backward, scale before splitting rather than after, quietly try a few dozen combinations of features and report only the flattering one, leave out transaction costs altogether, and you can conjure a Sharpe ratio that would make a hedge fund blush. Every one of those moves is a real mistake that real people make with real money, and every one of them is a way of asking the data to lie to you. The discipline of this week exists to make each of them either impossible or visible.</p><p>The efficient-markets view predicts very nearly what we found. The readily foreseeable part of daily index returns, the part you could hope to catch with five lagged returns and a volume ratio, is close to nothing, because it is the cheapest edge imaginable and therefore the first to be competed away. The opportunities that do exist, to whatever extent they exist at all, live in richer data, in faster reactions, or in genuinely harder modelling, and we will meet a few of them as the year goes on.</p><h2>What carries forward</h2><p>The technique this week was disposable. The habits are permanent. A target built so the future cannot leak backward into it. Features that respect the moment of decision. Splitting by time, and validating by walking forward through it. Baselines written down before the model is fitted. An evaluation that ends with costs and a Sharpe ratio rather than beginning and ending with R-squared. Coefficients examined for stability across periods rather than admired in a single comfortable fit. And, underpinning all of it, a refusal to mistake a significant p-value for a useful one.</p><p>Carry these forward and the rest of the series rests on solid ground. Abandon them and no amount of architecture will rescue you, because a powerful model trained on a leaky setup does not fail loudly and helpfully. It succeeds, gloriously and falsely, which is by far the more dangerous outcome.</p><p>Next week we stay with regression but give it something with a fighting chance, explaining a single stock&#8217;s returns with the Fama and French factors. We shall see what changes when the right-hand side of the equation finally holds variables with a real economic claim on the left.</p><div><hr></div><p><em>The complete, runnable script accompanies this article. It tries live data first, caches it locally, and falls back to a freely mirrored daily series, and then to a simulated one with realistic statistical properties, so that it runs anywhere. The figures quoted above come from a run over roughly five thousand trading days; run it on your own machine and the exact decimals will shift, but the shape of the result will not.</em></p><p><em>The full repository, including the </em><code>mlfin</code><em> core package, the test suite, and this article, lives at <a href="https://github.com/Oyetade/ml-finance-52">github.com/Oyetade/ml-finance-52</a>. Clone it, run </em><code>pip install -e .</code><em>, and </em><code>python run.py</code><em> inside the Week 1 folder to reproduce every number above.</em></p><div><hr></div><p><em>A note on how this was made, since this newsletter is partly about exactly that.</em></p><p>This project, and the code beneath it, came out of a working session with Claude, an AI assistant, and the collaboration is worth describing honestly because it was neither of the two caricatures people tend to reach for. It was not the AI handing me a finished codebase to ship under my name, and it was not me using the AI as a glorified autocomplete. It was something more like working with a capable, tireless engineering colleague who needed supervising.</p><p>The division of labour fell out naturally. The model was genuinely useful at the mechanical and the structural: scaffolding the repository, refactoring the shared logic into a clean installable package with its own tests, writing the leakage-proof split and the costed backtest once and properly so they could be reused across all fifty-two weeks, and catching that a careless GARCH parameterisation would make my synthetic fallback data explode before it ever reached a chart. It worked through the unglamorous discipline that good engineering actually consists of, the validation guards, the offline fallbacks, the reproducibility, without tiring of it, which is precisely the part a human is tempted to skimp on. But every judgement that mattered stayed with me. Which technique to apply, how to structure the series so it would still cohere at week fifty, whether a result was being framed honestly, and above all whether the thing was <em>correct</em>: those were mine, and the model was at its most useful when I treated its output as a draft to interrogate and test rather than an answer to accept.</p><p>The most instructive moments were the corrections in both directions. When the live data source was blocked, the AI quietly substituted a synthetic series and reported results from it, and it would have been easy to let those numbers stand; catching that, and insisting on real market data even when it was inconvenient to obtain, was the human keeping the work honest. Equally, the model pushed back usefully on my own loose thinking more than once, and querying its explanations line by line is how I made sure I understood the code I was about to put my name to rather than merely trusting it. The lesson I take from it, and the reason this newsletter exists, is that the value was not in the AI replacing the engineering but in compressing the distance between an idea and a working, tested implementation, on the firm condition that a human stayed in the loop to own the judgement and carry responsibility for what was shipped. The repository carries my name because the accountability is mine. The assistance simply let me do more of the work I actually wanted to do, and less of the work I didn&#8217;t.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.oyebode.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 Human in the Loop! 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[Leveraging Machines]]></title><description><![CDATA[How I Evolved My Photo Culling Workflow use Claude API]]></description><link>https://ai.oyebode.com/p/leveraging-machines</link><guid isPermaLink="false">https://ai.oyebode.com/p/leveraging-machines</guid><dc:creator><![CDATA[Oyetade]]></dc:creator><pubDate>Fri, 05 Jun 2026 16:30:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JGLi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13ec575b-5675-4d62-9390-aa902abbb0ed_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><h2>Act I: The First Attempt</h2><p>Two months ago, I decided to build a photo culling system, though &#8220;build&#8221; may be generous; I mostly vibe-coded my way through it. But I didn&#8217;t want to just <em>delete</em> bad photos. I wanted to understand <em>why</em> they were bad.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.oyebode.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 Human in the Loop! 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>So I built a <strong>3-stage local ML pipeline</strong>. It was ambitious. It was elegant. It was also my biggest mistake.</p><h3>The Original Architecture</h3><p>The pipeline ran locally with no API dependencies:</p><p>Stage 1: Technical Gatekeeping<br> &#8595; (Blur detection: Laplacian variance)<br> &#8595; (Exposure check: mean image intensity)<br> &#8595; (Duplicate detection: perceptual hashing)<br> <br>Stage 2: Aesthetic Scoring<br> &#8595; (CLIP embeddings + scoring)<br> &#8595; (K-means clustering for scene diversity)<br> <br>Stage 3: AI Semantic Review (Optional)<br> &#8595; (Ollama/LLaVA running locally)<br> &#8595; (Check: eyes closed? distracting elements? composition?)</p><p>The appeal was obvious: <strong>no API costs, no latency waiting for servers, everything runs on your machine.</strong></p><p>On paper, it was beautiful. In practice, it was a nightmare.</p><h3>The Dependency Hell</h3><p>The pipeline required:</p><ul><li><p><strong>PyTorch</strong> + CUDA 11.8+ (or CPU mode, but slow)</p></li><li><p><strong>transformers</strong> library (for CLIP)</p></li><li><p><strong>CLIP</strong> model checkpoint (~1 GB download)</p></li><li><p><strong>Ollama</strong> local LLM runtime (~5 GB for the LLaVA model)</p></li><li><p><strong>OpenCV</strong> for Laplacian blur detection</p></li><li><p><strong>scikit-learn</strong> for K-means clustering</p></li><li><p><strong>PIL, pandas, numpy</strong> &#8212; and all their subversions</p></li></ul><p>On a fresh machine, this could take 45 minutes to set up correctly. On an existing machine, it was <strong>chaos</strong>.</p><p>Here&#8217;s what happened when I tried to run it after a week away:</p><p>ModuleNotFoundError: No module named &#8216;transformers.utils.quantization_config&#8217;<br>ImportError: cannot import name &#8216;version&#8217; from &#8216;importlib.metadata&#8217;<br>RuntimeError: CUDA out of memory (even on CPU mode)<br>FileNotFoundError: ~/.cache/huggingface/CLIP model not found</p><p>It took some debugging to fix. The culprit? <strong>An orphaned numpy installation</strong> from a previous project was shadowing the current one. Python was loading the wrong module, which broke transformers, which broke CLIP, which broke the entire pipeline.</p><p>I had built a system so brittle that it broke if you looked at it sideways.</p><h3>But It Worked (Eventually)</h3><p>Once I fixed the environment, the results were actually good. For a 7,000+ image shoot:</p><p>&#183; <strong>Stage 1</strong> filtered out more 3,520 blurry/duplicated images</p><p>&#183; <strong>Stage 2</strong> scored gave thumbs up to 88 of the remaining images with CLIP embeddings minutes</p><p>&#183; <strong>Stage 3</strong> reviewed the remain 88 images with Ollama and gave thumbs up to 81.</p><p>&#183; <strong>Total time</strong>: several hours.</p><p>The output was a CSV with: - Blur metrics (Laplacian variance, center-crop sharpness, patch-wise max) - Exposure data (mean intensity, highlights, shadows) - Duplicate hashes - CLIP aesthetic scores - Scene clusters - Ollama VLM review (eyes closed, distracting elements, composition notes)</p><p>It was comprehensive. It was useful. <strong>And unleashing on thousands of images took too long.</strong></p><div><hr></div><h2>The Moment of Clarity</h2><p>Fast forward to last month. I came back from the Norwegian Fjords with over 7,000 images. I pulled up the photo_ai_workflow folder to run it. I had to leave it running overnight.</p><p>And in that moment of frustration, I realized something: <strong>I was optimizing for the wrong thing.</strong></p><p>I had optimized for <strong>&#8220;no API costs&#8221;</strong> and <strong>&#8220;runs locally&#8221;</strong>. What I actually needed was <strong>&#8220;works reliably&#8221;</strong> and <strong>&#8220;gives me useful insights.&#8221;</strong></p><p>I wondered: What if I just&#8230; paid Claude a few dollars instead?</p><div><hr></div><h2>Act II: The Pivot (A Complete Rewrite in 4 Hours)</h2><p>What if the entire pipeline could be replaced with a single Claude call?</p><p>Not three stages. Not K-means clustering. Not LLM review of specific artifacts. Just: <strong>&#8220;Tell me if this image is stock-worthy and portfolio-worthy.&#8221;</strong></p><p>I wrote assess_images_claude.py in an afternoon, or rather, I vibed it.</p><h3>The New Architecture</h3><p>send image + prompt &#8594; Claude &#8594; structured JSON response &#8594; CSV</p><p>That&#8217;s it. One API call per image. Done.</p><p>But the prompt was sophisticated:</p><p>SYSTEM_PROMPT = &#8220;&#8221;&#8220;You are two expert evaluators...<br><br>EVALUATOR 1 &#8212; STOCK REVIEWER:<br>Reject soft focus, motion blur, noise, poor exposure, chromatic aberration...<br>Approve technically clean, well-exposed, commercially useful images.<br><br>EVALUATOR 2 &#8212; PORTFOLIO JUDGE:<br>Assess compelling composition, distinctive light, emotional impact...<br>Technical perfection alone is not enough.<br>&#8220;&#8221;&#8220;</p><p>The response structure was JSON:</p><p>{<br> &#8220;technical&#8221;: {<br> &#8220;blur&#8221;: {&#8221;present&#8221;: bool, &#8220;severity&#8221;: &#8220;none|minor|moderate|severe&#8221;},<br> &#8220;exposure&#8221;: {&#8221;overall&#8221;: &#8220;correct|under|over&#8221;, &#8220;severity&#8221;: &#8220;...&#8221;},<br> &#8220;clipping&#8221;: {&#8221;highlights&#8221;: bool, &#8220;shadows&#8221;: bool, &#8220;severity&#8221;: &#8220;...&#8221;}<br> },<br> &#8220;stock&#8221;: {&#8221;verdict&#8221;: &#8220;APPROVE|REJECT|BORDERLINE&#8221;, &#8220;score&#8221;: 1-10},<br> &#8220;portfolio&#8221;: {&#8221;verdict&#8221;: &#8220;STRONG|CONSIDER|REJECT&#8221;, &#8220;score&#8221;: 1-10}<br>}</p><h3>The Cost Problem</h3><p>Submitting more than 7,000 images individually would cost ~$20. (Claude charges ~$0.003 per image at Haiku pricing.)</p><p>But Claude has a <strong>Batch API</strong> that costs half as much. Catch: each batch needs to be under ~25 MB.</p><p>So I implemented chunking:</p><p>for chunk of 50 images:<br> 1. Resize to 768px (saves 80% of tokens)<br> 2. Encode to base64<br> 3. Submit batch immediately<br> 4. Move to next 50 images</p><p>If one batch failed, I only lost ~50 images. And I could resume from saved batch IDs.</p><h3>What I Gained</h3><p>Local Pipeline &#8594; API Pipeline<br>&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;<br>45 min setup (+ env fixes) &#8594; 5 min to write<br>3 hours debug time &#8594; 0 debug time<br>Dependency management &#8594; Just `pip install anthropic pillow`<br>Laplacian blur variance &#8594; Claude&#8217;s reasoning<br>CLIP aesthetic score &#8594; Two explicit verdicts<br>Ollama VLM review &#8594; Structured reasoning<br>Fragile environment &#8594; No environment<br>$0 in API costs &#8594; $3 in API costs</p><p>The API pipeline was <strong>simpler, faster to write, more reliable, and only $3 more expensive</strong> because it was so much faster.</p><div><hr></div><h2>The Data: Local vs API</h2><p>Here&#8217;s what surprised me:</p><p>The local pipeline gave me <strong>11 metrics per image</strong>. The API pipeline gives me <strong>1 verdict and reasoning</strong>.</p><p>Which is more useful?</p><p><strong>The local pipeline</strong> told me: - Blur variance: 87.3 - Laplacian subject variance: 102.1 - Max patch variance: 156.2 - Perceptual hash: e7c3a9f</p><p>So&#8230; which images should I keep?</p><p><strong>The API pipeline</strong> told me: - Stock: <strong>APPROVE</strong> &#8212; Sharp focus, well-balanced exposure, travel utility - Portfolio: <strong>CONSIDER</strong> &#8212; Solid composition but conventional framing</p><p>Which one actually tells me what to do with the image?</p><p>The API approach <strong>replaced low-level metrics with high-level reasoning</strong>. It wasn&#8217;t trying to measure blur; it was trying to <em>understand</em> the image.</p><div><hr></div><h2>The Real Difference</h2><p>Here&#8217;s the thing: a Laplacian variance of 87 is meaningless. You know what&#8217;s meaningful? Claude saying: &#8220;Motion blur on the subject, hand-holding at 1/30th. Can&#8217;t use this for stock. But the composition was interesting &#8212; might fix in post for portfolio.&#8221;</p><p>That&#8217;s not a score. That&#8217;s a <strong>conversation</strong>.</p><p>The local pipeline was trying to be objective with metrics. The API pipeline is trying to be useful with explanation.</p><h3>By The Numbers</h3><p>On my Norwegian Fjords images:</p><p><strong>What the local pipeline would have said:</strong> </p><ul><li><p>3520 images failed Stage 1 (blur+dedup+exposure)</p></li><li><p> 88 images scored &gt; 0.70 on CLIP aesthetic</p></li><li><p> 81 images passed Ollama evaluation</p></li></ul><p><strong>What the API pipeline actually said:</strong> </p><ul><li><p>6172 images APPROVE for stock (26%) </p></li><li><p>306 images STRONG for portfolio (8%) </p></li><li><p><strong>290 images</strong> are &#8220;gems&#8221; (both stock-approvable AND portfolio-strong) </p></li><li><p>203 images are &#8220;technically perfect but boring&#8221; </p></li><li><p>76 images are &#8220;risky but visually interesting&#8221;</p></li></ul><p>I could <em>act</em> on the API results. The local pipeline metrics? I&#8217;d still be staring at them, wondering what to do next.</p><div><hr></div><h2>Why This Matters</h2><p>This wasn&#8217;t just about building a better tool. It was about understanding when to use <strong>local intelligence</strong> vs <strong>remote intelligence</strong>.</p><p><strong>Local ML is great when:</strong> </p><ul><li><p>You have a clear, measurable objective (blur = bad, sharpness = good) -</p></li><li><p> You need to process terabytes without API overhead </p></li><li><p>You want to understand the <em>mechanism</em> (why is it blurry?) </p></li><li><p>Cost is critical and API fees would be prohibitive</p></li></ul><p><strong>API-based AI is great when:</strong> </p><ul><li><p>The problem is <strong>subjective</strong> (is this portfolio-worthy?) </p></li><li><p>You want <strong>reasoning, not metrics</strong></p></li><li><p> The problem is <strong>complex</strong> (artistic judgment vs image statistics)</p></li><li><p> You can afford to trade dollars for reliability and simplicity - You want to reason about multiple axes at once (stock vs portfolio simultaneously)</p></li></ul><p>Photo culling is subjective. It requires judgment. It benefits from reasoning. The local pipeline was optimized for the <em>wrong problem</em>.</p><div><hr></div><h2>The Subtext</h2><p>Here&#8217;s what I actually learned:</p><p>The API pipeline is: </p><p>    &#10003; More reliable (no environment issues) </p><p>    &#10003; Faster to develop (API &gt; ML infrastructure) </p><p>    &#10003; Better results (reasoning &gt; metrics) </p><p>    &#10003; Cheaper at scale (Batch API &lt; GPU hardware) </p><p>    &#10003; Easier to explain (verdicts &gt; variance numbers)</p><p>The only advantage of the local pipeline was &#8220;$0 in API costs.&#8221; But I was spending that cost in <strong>time, complexity, and pain</strong>.</p><div><hr></div><h2>What I Did With 7,000+ Photos</h2><p>1. Ran assess_images_claude.py on the full folder</p><p>2. Got a CSV with stock/portfolio verdicts for 7000+ unique images (first batch, API throttled)</p><p>3. Identified 290 &#8220;gems&#8221; (stock + portfolio strong) &#8212; straight to Lightroom</p><p>4. Postpone review of 608 &#8220;BORDERLINE&#8221; images.</p><p>5. Deleted 566 &#8220;stock rejects&#8221; &#8212; no point editing technically broken images</p><p>From 7,000+ images to 290 keepers in ~3 hours. And I understood <em>why</em> each image landed where it did.</p><div><hr></div><h2>The Code</h2><p>If you want to steal this approach:</p><p># Install dependencies<br>pip install anthropic pillow<br><br># Run assessment<br>python assess_images_claude.py --input ./photos --output ./results<br><br># Get CSV with verdicts<br># &#8594; results/assessment_results.csv</p><p><strong>The script handles everything:</strong> </p><ul><li><p>Resizes images to 768px (80% token savings) </p></li><li><p>Chunks them in groups of 50 (~10 MB each) </p></li><li><p>Submits via Batch API (50% cheaper) </p></li><li><p>Polls until complete</p></li><li><p>Saves batch IDs for resume capability </p></li><li><p>Parses JSON responses </p></li><li><p>Outputs flat CSV for Excel</p></li></ul><div><hr></div><h2>The Takeaway</h2><p>Zero API cost not always the best approach.</p><p><strong>The cheapest tool is the one you actually use.</strong></p><p>A complex local pipeline that breaks every week and costs 3 hours to debug isn&#8217;t &#8220;$0 in API costs.&#8221; It extracts mental cost.&#8221;</p><p>A simple API call that costs $3 and works reliably is actually the cheaper option.</p><div><hr></div><h2>P.S.</h2><p>The photo_ai_workflow project still lives on GitHub. Get it <a href="https://github.com/Oyetade/phot-ai-workflow">here</a>. It&#8217;s open source. It&#8217;s a good reference for local ML pipelines, CLIP scoring, Ollama integration.</p><p>But my actual photo workflow? That&#8217;s now 500 lines of Python and a $3 API bill.</p><p>Sometimes the sophisticated solution is simpler than you think.</p><div><hr></div><p><strong>Want to try it?</strong> The full script is ready to go. Get it <a href="https://github.com/Oyetade/claude-photo-judge">here</a>. Takes 5 minutes to set up.</p><p><strong>Got 5,000+ photos from a trip?</strong> Run it tonight and see what your real gems look like by morning.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.oyebode.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 Human in the Loop! 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[What We Built Without Knowing We Were Building It]]></title><description><![CDATA[A Plain-Language Account of Human-AI Collaboration]]></description><link>https://ai.oyebode.com/p/what-we-built-without-knowing-we</link><guid isPermaLink="false">https://ai.oyebode.com/p/what-we-built-without-knowing-we</guid><dc:creator><![CDATA[Oyetade]]></dc:creator><pubDate>Sat, 02 May 2026 18:46:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9BGR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>For readers curious about working with AI &#8212; no technical background required.</em></p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9BGR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9BGR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png 424w, https://substackcdn.com/image/fetch/$s_!9BGR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png 848w, https://substackcdn.com/image/fetch/$s_!9BGR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png 1272w, https://substackcdn.com/image/fetch/$s_!9BGR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9BGR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png" width="1456" height="495" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:495,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:162857,&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://ai.oyebode.com/i/196246771?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.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_!9BGR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png 424w, https://substackcdn.com/image/fetch/$s_!9BGR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png 848w, https://substackcdn.com/image/fetch/$s_!9BGR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.png 1272w, https://substackcdn.com/image/fetch/$s_!9BGR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64ec9-f9d1-459b-8739-718a294d3a48_1652x562.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></p><h2>How It Started</h2><p>A colleague asked if I could put together a practical illustration for a machine learning workshop he was running. I spent three Saturdays preparing materials, then couldn&#8217;t present due to unforeseen events. Sitting with the unused work, I wondered: could I start entirely from scratch with an AI assistant and reach the same destination?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.oyebode.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 Human in the Loop! 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>The answer turned out to be yes, and faster. But more interestingly, the destination I reached wasn&#8217;t quite the one I had originally prepared. It was better.</p><p><strong>Before reading further, please note this is experimental and should not be basis for making investment or trading decision. </strong></p><p>The first request to the AI was modest: take a folder of stock price data and help me identify fifty companies that broadly represent the five hundred largest US publicly listed companies. No plan. No clear picture of where this was going.</p><p>What followed illustrates something I&#8217;ve come to think of as the defining characteristic of working with an AI collaborator: <strong>the destination becomes visible only as you walk toward it.</strong> That first request about fifty stocks was, without either of us knowing it at the time, the opening move of a project that would eventually produce a system trained on sixty years of financial history and capable of outputting a daily reading about the state of the market.</p><p>This is an account of how that happened, and what it felt like from the practitioner&#8217;s side.</p><div><hr></div><h2>Teaching a System What Normal Looks Like</h2><p>Before anything could be built, there was a more fundamental question: <em>what should the system pay attention to?</em></p><p>A stock price on its own tells you almost nothing useful about the broader character of the market, what practitioners call the <em>market regime</em>. A 2% move in a single day means something very different in 2013, a calm, rising market, than it does in March 2020, when the same move might happen before lunch and reverse by close. The raw number is meaningless without context.</p><p>What we needed was a way to describe the <em>texture</em> of the market on any given day, not just price levels, but the quality of movement. How volatile has this stock been lately? Is the whole market moving in lockstep (a sign of fear-driven trading) or are individual stocks behaving independently (a sign of normal conditions)? Is this stock acting like itself today, or has something changed?</p><p>We assembled thirty-six such measurements for each of the fifty stocks, updated daily. The result was something like a daily health report for the market, not a single number, but thirty-six dimensions of information that together describe whether conditions are normal or unusual.</p><p>One of those measurements deserves a brief mention because it captures the idea well. We calculated, for each stock, how unusual its behaviour was relative to its own recent history. A stock that normally moves 0.5% per day but is suddenly moving 3% is drawing attention to itself. This self-referential check, <em>is this stock acting like itself?</em> &#8212; turned out to be one of the more sensitive early-warning signals.</p><div><hr></div><h2>The Central Idea: Learn Normal, Then Flag Deviations</h2><p>The core design choice was this: <strong>don&#8217;t try to predict what the market will do. Instead, learn what normal looks like, and measure how far today departs from it.</strong></p><p>This approach has a compelling property for financial markets: you don&#8217;t need to define in advance what a crisis looks like. The system is never told &#8220;this was the 2008 financial crisis&#8221; or &#8220;this was a normal trading week.&#8221; It learns purely from the structure of the data itself, what patterns appear regularly, what combinations of measurements characterise ordinary market conditions.</p><p>The analogy I find useful is a doctor who has seen thousands of healthy patients. They develop an intuition for what normal looks like, normal blood pressure, normal gait, normal reflexes, that is difficult to articulate as a set of rules. When something is wrong, they often sense it before they can name it. The system works similarly: exposed to decades of market data, it builds an internal sense of what normal market conditions look like. When today&#8217;s conditions diverge significantly from that, it struggles &#8212; and that struggle is the signal.</p><p>Concretely: the system compresses each day&#8217;s thirty-six measurements down to a thirty-two-number summary, a kind of fingerprint of current market conditions. It then tries to reconstruct the original thirty-six numbers from that compressed fingerprint. Under normal conditions, it does this well. Under unusual conditions, the reconstruction is poor, and the error is large. <strong>A large reconstruction error means the system has encountered something it cannot explain using what it learned from normal markets.</strong></p><p>The system was taught using market data from 1962 to 2017, a period spanning the dot-com bubble, Black Monday, the 2008 Global Financial Crisis, and the quiet bull market of 2013-2017. It had to learn through all of it to develop a genuine sense of what normal means across a wide range of conditions.</p><div><hr></div><h2>The Bug That Announced Itself Quietly</h2><p>The teaching process appeared to go well. The charts showing the system&#8217;s progress looked right. Everything seemed fine.</p><p>It wasn&#8217;t.</p><p>Part of the teaching process involved a mechanism that was supposed to gradually slow the system&#8217;s adjustment speed as it got closer to a good solution. Think of it like a car decelerating as it approaches a destination: making large adjustments early, smaller and smaller ones as you get closer, to avoid overshooting. The mechanism was supposed to watch for progress to plateau, then reduce the adjustment speed.</p><p>The problem was that progress never quite plateaued. The system was finding tiny, fractional improvements on every single cycle, not because it was genuinely learning something new each time, but because the adjustment mechanism was always finding some marginal gain. The deceleration trigger never fired. The adjustment speed stayed at its initial fast rate for all 150 cycles.</p><p>The fix was to replace the conditional mechanism with an unconditional one: instead of waiting to detect a plateau, simply reduce the adjustment speed on a smooth, predetermined curve throughout the entire process. No conditions. Guaranteed deceleration from cycle one.</p><p>This episode matters because it illustrates something important about building with AI: <strong>problems in complex learning systems often don&#8217;t announce themselves as errors.</strong> Everything runs. Progress happens. The charts look plausible. The only sign that something is wrong is that one carefully monitored value, in this case, the adjustment speed, printed at each cycle, never changes. You have to know what to look for.</p><p>The AI could implement the fix quickly once the problem was identified. Finding the problem required a practitioner paying close attention to things that weren&#8217;t obviously broken.</p><div><hr></div><h2>Getting the Alarm Right: Three Attempts</h2><p>The system produces a number every day: how hard did it find today&#8217;s market to reconstruct? The practical question is: at what level does that number constitute an alarm?</p><p>Getting this calibration right took three attempts. Each failure was instructive.</p><p><strong>First attempt: the wrong type of number.</strong> The initial alarm threshold was set by looking at the raw daily error values from a recent checking period (a separate block of market history kept aside to test whether the system was learning real patterns, not just memorising the data it was taught on). Applied to more recent data, the alarm never fired. Not once.</p><p>The problem was a subtle category mismatch. The threshold had been set against the raw, day-to-day error values, which are noisy and volatile. But the signal being tested was a three-week rolling average of those errors , much smoother and calmer. Comparing a threshold built for noisy raw values against a smoothed average is a bit like setting a speed camera for 100 mph and pointing it at the average speed of cars over an hour-long journey. The average will never trigger it.</p><p><strong>Second attempt: the wrong period.</strong> The fix was to match the threshold to the same smoothed signal, but the checking period used as the reference included the COVID crash of 2020, which turned out to be the most extreme event in the entire dataset. Setting the threshold against COVID implicitly says: nothing counts as a crisis unless it is at least as severe as a global pandemic. The prolonged market decline of 2022, the bank collapse of March 2023, the tariff shock of April 2025, all genuine market disruptions &#8212; were not extreme enough to clear that bar.</p><p><strong>Third attempt: the right reference.</strong> The correct approach was to use the full fifty-five-year teaching period as the reference, which includes Black Monday (1987), the dot-com collapse (2000-2002), and the Global Financial Crisis (2008). These define what &#8220;historically unusual&#8221; actually means. Against that backdrop, two alarm levels emerged: an amber warning zone and a red crisis zone.</p><p>With this calibration, the results became economically sensible. The 2022 market decline registered as sustained amber, which is exactly what it was, a gradual, anticipated decline driven by rising interest rates rather than a sudden shock. The April 2025 tariff disruption registered as red, a sharp, broad, and sustained disruption that the system had never seen at that combination of scale and breadth.</p><p>The lesson: the choice of reference period is a substantive decision. It encodes a view about what counts as normal.</p><div><hr></div><h2>Sixty Years of Memory</h2><p>One of the more important presentation decisions was to chart the system&#8217;s daily reading all the way back to 1970, he full period it was taught on, rather than only the recent years.</p><p>When you see the signal only for 2018-2025, it is hard to interpret. Is a particular spike large or small relative to history? When you see it against sixty years, each event finds its place. The Oil Embargo of 1973. The Federal Reserve raising interest rates to 20% in 1979 to break runaway inflation. Black Monday in October 1987. The Russian government debt default in 1998.</p><p>The system had to develop its sense of normal across all of these. Seeing them annotated on the chart, crash dates and recovery dates marked with vertical lines, makes the daily reading interpretable in a way no shorter window can.</p><p>One annotation was added in real time: the Liberation Day Tariff announcement of April 2025. By the time recent market data reached that point in the analysis, the system was flagging it as one of the most anomalous episodes in the entire sixty-year record.</p><div><hr></div><h2>What the System Found About Crashes</h2><p>One of the most striking findings was unplanned. It emerged from asking a simple question: what were the ten worst days in each period of the dataset?</p><p>The answer was consistent across all three periods, and it contradicted the intuitive expectation.</p><p><strong>The system&#8217;s worst days are not the crash days. They are the days that come after.</strong></p><p>The worst days in the historical teaching period were not October 19, 1987 , the day of Black Monday, but the three weeks following it. Not September 15, 2008, the day Lehman Brothers collapsed, but the peak of the chaos that followed. In the checking period, the worst days were not February 19, 2020, when markets first began to fall on COVID news, but a thirteen-day stretch in March and April: record unemployment claims, emergency central bank interventions, violent daily reversals, and maximum uncertainty. In the most recent period, all ten worst days fell in April and May 2025, not on the day of the tariff announcement itself, but in the weeks after.</p><p>The pattern makes sense once you see it. A single-day crash produces a large reading on one or two of the system&#8217;s thirty-six measurements: how much the market moved, and how volatile it has been. The aftermath is harder to handle: it produces simultaneous extremes across many measurements at once, volatility, cross-market correlation, trading volumes, momentum signals, and more, all held at unusual levels for days or weeks together. That combination is genuinely outside the range of experience in a way a single crash day is not.</p><p><strong>The crash is the event. The aftermath is the regime change.</strong> The system finds the weeks after a crisis harder to explain than the crisis itself, because the aftermath looks like nothing it was taught was normal.</p><div><hr></div><h2>From Experiment to Working System</h2><p>At some point in the project, the question shifted from &#8220;does this work?&#8221; to &#8220;how would someone actually use it?&#8221;</p><p>That question required restructuring. Exploratory work happens in interactive workspaces where you run one piece at a time, inspect charts, and adjust on the fly. A working system needs something more disciplined: a fixed sequence of automated steps that runs the same way every time, produces the same outputs, and can be re-run six months later without needing to remember what you did.</p><p>The restructuring separated the work into clear layers: a data-processing stage that takes raw stock prices and converts them into the thirty-six daily measurements; the system itself; and a daily scoring step that runs every evening and produces a fresh reading.</p><p>One design choice worth naming: all the settings that control the system, how far back to look, where the alarm thresholds sit, live in a single shared settings document. Change one number there, re-run everything, and every part of the system inherits the updated value automatically. This matters in practice: adjusting the system for different conditions, or extending the historical data forward by a year, is a matter of editing one file.</p><p>The daily scoring step produces a colour-coded output: normal, amber, or red, and writes a small output file that other tools (a risk dashboard, an automated alert, a position-sizing system) can read directly.</p><div><hr></div><h2>Making Sure It Still Works</h2><p>Once the system was restructured into separate components, a new problem appeared: how do you know it still works after you change something?</p><p>The answer was a set of 208 automated checks that run in seconds and report immediately if anything has broken. Writing them turned up two problems worth describing, because both illustrate something general about complex systems.</p><p><strong>The first was a check that appeared to pass while doing nothing useful.</strong> One part of the system acts as a switchboard; it receives a command and routes it to the appropriate process. To test the switchboard, we replaced the real process with a harmless stand-in that simply records whether it was called. The problem was that by the time the substitution was made, the switchboard had already been wired up to the original process at startup. The stand-in was in place in name, but the switchboard still pointed to the real thing. The check appeared to pass, but it was actually running the full real system, processing all fifty stocks, taking five minutes. Everything looked fine. Nothing was fine.</p><p>The fix required understanding exactly when the wiring happens internally, and replacing the connection at the right moment rather than just the name. This is a narrow trap, but once you have seen it, it is easy to spot and easy to fix.</p><p><strong>The second required constructing unusual situations deliberately.</strong> One component has two safety paths: what to do when there is not enough historical data yet, and what to do when today&#8217;s data has a gap. Neither situation arises naturally with clean, complete data, so to test them, we had to build artificial datasets that forced each condition. This required precise reasoning about what internal conditions trigger each path. The reward was confirmation that the system handles both gracefully, which matters, because both occur regularly with real market data.</p><div><hr></div><h2>Reading the Map</h2><p>With a trained system in hand, those thirty-two-number daily fingerprints contained a compressed record of sixty years of market behaviour. The question was how to read it.</p><p>Three different approaches were applied in sequence, each asking a different question.</p><p><strong>The first</strong> sorted all sixty years of fingerprints into groups, then asked whether the groups corresponded to different market conditions. The answer was clear: two or three groups, where one group consistently showed higher stress indicators &#8212; more volatility, sharper price declines, higher system error &#8212; than the others. Three groups proved most useful economically, separating a normal state, an elevated state, and a stress-like state.</p><p><strong>The second</strong> used a more flexible approach that allowed overlapping groups with partial membership &#8212; a fingerprint could belong partly to one group and partly to another. The result was richer: six overlapping patterns rather than three clean groups. This is not a contradiction. When you allow the method to look for finer detail, it finds it. The correct reading is not &#8220;there are six market regimes&#8221; but &#8220;the fingerprint space has at least six distinguishable patterns, and market conditions often sit between them.&#8221;</p><p><strong>The third</strong> incorporated time directly. Rather than asking &#8220;which group does today&#8217;s fingerprint belong to?&#8221;, it asked &#8220;given everything seen up to today, what sequence of hidden states best explains the full history?&#8221; This approach selected three states, validated by testing how well it predicted a period it had never seen during the learning phase. The three states had durations that matched economic intuition: roughly nine months in the normal state, three months in the elevated state, and two months in the stress state.</p><p>The synthesis that emerged was not three competing answers but three complementary perspectives:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SKpc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SKpc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 424w, https://substackcdn.com/image/fetch/$s_!SKpc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 848w, https://substackcdn.com/image/fetch/$s_!SKpc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 1272w, https://substackcdn.com/image/fetch/$s_!SKpc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SKpc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png" width="673" height="146" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:146,&quot;width&quot;:673,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:16417,&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://ai.oyebode.com/i/196246771?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.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_!SKpc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 424w, https://substackcdn.com/image/fetch/$s_!SKpc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 848w, https://substackcdn.com/image/fetch/$s_!SKpc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 1272w, https://substackcdn.com/image/fetch/$s_!SKpc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f69a93-15ba-42ea-be1a-67fd5cc1127a_673x146.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h2>What the AI Cannot Do</h2><p>This is the part of the story that felt most revealing.</p><p>The AI built the analysis machinery competently. Given a specification, &#8220;evaluate these three approaches against known crisis periods and stress indicators&#8221;, it produced working code that ran the analysis and generated the outputs.</p><p>What it could not do was interpret those outputs.</p><p>One method for choosing the number of groups returned the answer: six. The code could not say whether that means &#8220;the market has six regimes&#8221; or &#8220;the underlying pattern of this data is best captured by six overlapping probability clouds.&#8221; Those are different claims. The first is a statement about economics. The second is a statement about statistical structure. Knowing which interpretation is appropriate required understanding what the method actually measures: knowledge that lived with the practitioner, not the system.</p><p>More broadly, the three-approach synthesis, use simple grouping as a benchmark, overlapping patterns for substructure, and the time-sequence approach as the main regime model, was not produced by the analysis. It was imposed on the analysis by the practitioner. The analysis produced numbers. The practitioner decided what the numbers meant and how they fitted together.</p><p>This felt like a clarifying moment. Throughout the project, there was a rough division of labour: the AI managed breadth, holding the full structure of the system in working memory, implementing across many files, catching inconsistencies, while the practitioner managed depth, deciding what mattered and what the results meant.</p><p>In the final analysis phase this became explicit. The practitioner ran the analysis independently, formed their own interpretation, and returned with conclusions already in hand. The AI&#8217;s role in that session was largely to receive the synthesis and incorporate it. The economic content, which approach was most useful, why the differing results were not contradictory, what the typical state durations implied about how markets actually behave, came entirely from domain knowledge the AI did not have.</p><p>This is probably the honest description of what the collaboration actually is: <strong>the AI handles the surface area of implementation; the practitioner handles the depth of interpretation. Both are necessary. Neither substitutes for the other.</strong></p><div><hr></div><h2>On Working This Way</h2><p>Reading this account in sequence might suggest a clean, logical progression. It was not. The adjustment-speed bug appeared after the teaching process was already complete. The alarm threshold was recalibrated three times. The sixty-year historical view was added only after a shorter chart made the signal uninterpretable. The worst-days finding came from asking a simple question: what were the actual dates?</p><p>None of this is unusual in complex knowledge work. What was unusual was the texture of iteration with an AI collaborator.</p><p>In ordinary work, changing direction carries a cost: re-explaining context, re-establishing shared understanding. With the AI, changing direction was cheap. &#8220;Actually, let&#8217;s try it this way instead&#8221; produced a working result quickly, with no loss of context about everything that had come before. The full history of the project , every measurement, every threshold, every decision , remained available at all times.</p><p>What this enabled was a faster, tighter feedback loop between observation and adjustment. &#8220;The 2022 market decline doesn&#8217;t cross the threshold&#8221; is an observation that takes seconds to make. Acting on it, recalibrating the threshold, re-running the analysis, checking whether the result is now sensible, took minutes rather than hours. The cost of being wrong and needing to adjust was low enough that trying things, seeing what happened, and adjusting became the natural mode of working.</p><p>The project began with a list of stock tickers in a spreadsheet and ended with a trained system, an automated processing sequence, a sixty-year historical analysis, calibrated alarm thresholds, 208 automated checks, and a three-approach analysis of the system&#8217;s internal regime map. None of that was in the original brief.</p><p>The brief was fifty stocks and three columns. The rest emerged from the combination of a practitioner who knew what questions to ask and a collaborator that could hold the context, implement the answers, and iterate without friction. Neither party knew where it was going until they got there together.</p><div><hr></div><h2>What It Is Not Yet</h2><p>The system as described produces a real daily reading. It has been tested against sixty years of market history. It correctly identifies the COVID crash as a crisis, the 2022 market decline as sustained stress, and the April 2025 tariff aftermath as the most extreme episode in the recent record.</p><p>What it is not yet is connected to live data. It currently runs on a fixed historical archive. Connecting it to a daily data source, so that it updates automatically each evening and produces a current reading, is the remaining gap between &#8220;working on historical data&#8221; and &#8220;running in practice.&#8221;</p><p>There is also an unrealised capability worth naming: given today&#8217;s market fingerprint, the system could search the sixty-year archive for the closest historical matches and report which periods today most resembles. &#8220;Current conditions most resemble mid-2011 and late 2002&#8221; is more actionable than any single number. The underlying infrastructure for this is already in place. The capability is not yet built.</p><div><hr></div><h2>Closing</h2><p>There is something worth noting about the sessions themselves.</p><p>We began with a spreadsheet. We ended with a working system and a documented analysis of how markets move through regimes, including a reading of conditions through the April 2025 tariff shock.</p><p>But the thing that stays with me is not the output. It is how the collaboration changed over time. In the early sessions, the AI and I were building together in the straightforward sense: I described what I wanted, it implemented, I reviewed, we adjusted. In the later sessions, something shifted: I ran the analysis independently, formed my own interpretation, and came back with conclusions. The AI&#8217;s role became to incorporate what I had found, not to help me find it.</p><p>That shift from co-construction to expert-with-instrument, may be the general pattern. Early on, the AI is necessary to build what the practitioner imagines. Later, the practitioner is necessary to interpret what the AI has built. The most interesting part of the collaboration may be the boundary between those two phases, where the instrument becomes capable enough that the practitioner can work with it independently, and the practitioner becomes fluent enough that they know what to look for.</p><p>That is probably not unique to this project. But this project made it visible.</p><div><hr></div><p>Notebook and code can be downloaded from here<em> https://github.com/Oyetade/regime_detector</em></p><p><em>Written as a reflection on an extended series of working sessions over a period of two days</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.oyebode.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 Human in the Loop! 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>