<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ml on PHP Boy Scout</title><link>https://phpboyscout.uk/tags/ml/</link><description>Recent content in Ml on PHP Boy Scout</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>Matt Cockayne</copyright><lastBuildDate>Wed, 15 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://phpboyscout.uk/tags/ml/index.xml" rel="self" type="application/rss+xml"/><item><title>About that 'no AI'...</title><link>https://phpboyscout.uk/about-that-no-ai/</link><pubDate>Wed, 15 Jul 2026 00:00:00 +0000</pubDate><guid>https://phpboyscout.uk/about-that-no-ai/</guid><description>&lt;img src="https://phpboyscout.uk/about-that-no-ai/cover-about-that-no-ai.png" alt="Featured image of post About that 'no AI'..." /&gt;&lt;p&gt;A couple of weeks back I made a claim I was happy to put my name to: &lt;a class="link" href="https://phpboyscout.uk/no-ai-in-my-photo-culler/" &gt;There&amp;rsquo;s no AI in my photo culler&lt;/a&gt;. And I meant it. The first pass that sorts Hailey&amp;rsquo;s wedding photos into keep, maybe and reject is arithmetic, the sort a patient enough person could do by hand. Blur is the variance of a Laplacian, a duplicate is sixty-four bits that happen to agree, exposure is a histogram. Not a trained weight in sight.&lt;/p&gt;
&lt;p&gt;Then I went and put two neural networks in it.&lt;/p&gt;
&lt;p&gt;So&amp;hellip; about that. When I said &amp;ldquo;no AI&amp;rdquo;, what I really meant was &amp;ldquo;no &lt;em&gt;generative&lt;/em&gt; AI&amp;rdquo;, and the gap between those two has turned out to be the whole point. There&amp;rsquo;s machine learning in krites now, there&amp;rsquo;s more on the way, and &amp;ldquo;AI&amp;rdquo; has become a word that can&amp;rsquo;t tell the two apart.&lt;/p&gt;
&lt;h2 id="the-bit-i-always-said-was-coming"&gt;The bit I always said was coming
&lt;/h2&gt;&lt;p&gt;In that culler post I drew a line. The heavy lifting of the first pass would always be model-free, I said, but some of what a photographer culls on genuinely needs a model: is this person mid-blink, is anyone actually looking at the camera, is the composition any good. Those would be model-backed when they came, and they&amp;rsquo;d sit outside the deterministic core, behind an interface, opt-in. That was a promissory note. Well, the first of them has come due: the eye-and-blink signal has landed, and it pays out exactly as I said it would.&lt;/p&gt;
&lt;p&gt;Spotting a blink genuinely needs a model. To know whether someone&amp;rsquo;s eyes are open you first have to find the face, then find the eyes on it, and there&amp;rsquo;s no way to do that with a &lt;code&gt;for&lt;/code&gt; loop over pixel brightness. So krites does it with two models, both run through ONNX Runtime: UltraFace to find the faces, and InsightFace&amp;rsquo;s 106-point landmark model to map the features on each one (&lt;a class="link" href="https://gitlab.com/phpboyscout/krites/-/blob/67a66ad/pkg/face/models/models.go#L31-L44" target="_blank" rel="noopener"
 &gt;the pinned models&lt;/a&gt;). Both are neural networks, trained on faces. This is machine learning, the thing the first pass pointedly did without. It&amp;rsquo;s off by default, it lives in its own package, and it sits behind the same &lt;code&gt;face.Analyzer&lt;/code&gt; interface the post promised, so the deterministic engine never imports it (spec &lt;a class="link" href="https://gitlab.com/phpboyscout/krites/-/blob/67a66ad/docs/development/specs/0004-face-eye.md" target="_blank" rel="noopener"
 &gt;&lt;code&gt;0004&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="but-look-at-what-it-gives-back"&gt;But look at what it gives back
&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s why two neural networks still don&amp;rsquo;t add up to the kind of &amp;ldquo;AI&amp;rdquo; everyone pictures now. The models don&amp;rsquo;t &lt;em&gt;decide&lt;/em&gt; anything. They hand back coordinates: a box around each face, and a hundred-odd points marking its features. The blink call is then plain geometry on six of those points per eye, the eye-aspect-ratio, the distance across the eyelids over the distance across the eye, a number that falls towards zero as the lid closes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// eyeAspectRatio is the Soukupová–Čech eye-aspect-ratio for a 6-point eye&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// contour (p0..p5 ordered around the eye, starting at the outer corner):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//	EAR = (|p1-p5| + |p2-p4|) / (2 · |p0-p3|)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// the two vertical lid distances over twice the horizontal width. Higher means&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// more open; a blink collapses the verticals toward zero.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I calibrated the open and closed thresholds against 556 frames from &lt;a class="link" href="https://phpboyscout.uk/i-built-my-wife-a-judge/" &gt;the wedding that started this whole project&lt;/a&gt;, actual blinks and actual open eyes, so the numbers mean something for the job (&lt;a class="link" href="https://gitlab.com/phpboyscout/krites/-/blob/67a66ad/pkg/face/onnx/config.go#L46-L60" target="_blank" rel="noopener"
 &gt;the anchors&lt;/a&gt;). And the interface it plugs into doesn&amp;rsquo;t just hope it behaves itself, it demands it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Analyzer finds faces in a frame and reports per-face eye state. Implementations&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// are model-backed (pkg/face/onnx) or faked in tests; the engine codes only to&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// this interface (0004 R-FACE-3). Analyze MUST be deterministic for a given&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// (img, model) and honour ctx cancellation/timeout (0004 R-FACE-4; 0002&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// R-GLOBAL-7, R-GLOBAL-8).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Analyzer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Same frame in, same answer out, every time (&lt;a class="link" href="https://gitlab.com/phpboyscout/krites/-/blob/67a66ad/pkg/analyze/face/face.go#L19-L27" target="_blank" rel="noopener"
 &gt;&lt;code&gt;face.go&lt;/code&gt;&lt;/a&gt;). That one requirement is the line between this and a chatbot. It&amp;rsquo;s machine learning that behaves like the arithmetic culler it sits next to: reproducible, debuggable, and tunable by Hailey rather than by me or a vendor. The model finds the eyes; a number she can move decides what counts as shut. And a blink only ever demotes a frame to &lt;em&gt;maybe&lt;/em&gt;, it never rejects one on its own, because krites proposes and the human disposes.&lt;/p&gt;
&lt;h2 id="the-word-got-narrow"&gt;The word got narrow
&lt;/h2&gt;&lt;p&gt;Now the actual argument. Ask most people what &amp;ldquo;AI&amp;rdquo; is today and they&amp;rsquo;ll describe a chat box. You type, it types back; it writes, paints, codes. Generative, conversational, and genuinely astonishing. I use it every day. But it&amp;rsquo;s one slice of a very large pie, and somewhere in the last three or four years it ate the whole word.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no &amp;ldquo;versus&amp;rdquo; in any of this. Generative models and the quiet predictive kind are branches of the same family, and I&amp;rsquo;m a fan of the lot. The point is only that &amp;ldquo;AI&amp;rdquo; used to name the whole field and now names one corner of it. Machine learning is enormous, decades older than the chatbot, and the overwhelming majority of it has nothing to say to you at all. It takes a varied input and returns a consistent, predictable output. It finds a face. It reads a postcode off an envelope. It flags the dodgy transaction. It spots the tumour on the scan. None of it talks, none of it improvises, and almost none of it gets called &amp;ldquo;AI&amp;rdquo; any more.&lt;/p&gt;
&lt;p&gt;Some of the most formative work of my career, years before GPT was a name anyone outside the field would recognise, was built on exactly this kind of model. A team I was part of was using BERT, a language model, to make sense of messy text, and there was protein folding and sequencing work going on alongside it that was some of the cleverest engineering I&amp;rsquo;ve stood near. All of it was AI by any textbook definition. None of it had a chat interface. It just did a specific, difficult, useful thing, and did it reliably.&lt;/p&gt;
&lt;h2 id="the-proof-is-in-her-camera-bag"&gt;The proof is in her camera bag
&lt;/h2&gt;&lt;p&gt;The cleanest example isn&amp;rsquo;t anything I built. It&amp;rsquo;s already in Hailey&amp;rsquo;s hands. Her Sony A7 IV does face detection, and eye detection, in the body of the camera, live, as she shoots. It has done for years. It&amp;rsquo;s so utterly ordinary that nobody thinks twice about it, nobody calls the camera &amp;ldquo;AI&amp;rdquo;, and nobody worries about where their face went. It&amp;rsquo;s a trained model welded into a physical object and taken completely for granted.&lt;/p&gt;
&lt;p&gt;The ONNX detector I just put into krites is the same species of thing: a small model that learned what an open eye looks like and gives a direct answer. The only real difference is timing. The camera does it at the instant of the shutter, and krites does it three thousand frames later, for the shots a blink spoiled that nobody caught on the day.&lt;/p&gt;
&lt;h2 id="why-the-muddle-is-worth-minding"&gt;Why the muddle is worth minding
&lt;/h2&gt;&lt;p&gt;So why does it nag at me, one slice eating the word? Two reasons.&lt;/p&gt;
&lt;p&gt;The first is that it follows the money, not the merit. The generative, chat-fronted slice is the one you can put on a slide and demo to a room, so it&amp;rsquo;s the one that gets the funding, the headlines and the name. The workhorse ML running your camera, your spam filter and your bank&amp;rsquo;s fraud checks doesn&amp;rsquo;t make a keynote, so it slides out of view, and &amp;ldquo;AI&amp;rdquo; comes to mean only the part someone&amp;rsquo;s selling. Even the firms surfacing the rest more thoughtfully are still in that lane: Google giving Imagen, Veo and Lyria their own identities is a good instinct, but those are identities for &lt;em&gt;generative&lt;/em&gt; tools. The quiet predictive stuff still doesn&amp;rsquo;t get a logo.&lt;/p&gt;
&lt;p&gt;The second reason is closer to home, and it&amp;rsquo;s about trust. When I tell Hailey that krites uses &amp;ldquo;a model&amp;rdquo; to spot blinks, the word &amp;ldquo;AI&amp;rdquo; invites her to picture four thousand irreplaceable wedding photos being shovelled into someone&amp;rsquo;s cloud chatbot. They aren&amp;rsquo;t. It&amp;rsquo;s a five-megabyte file that runs on her laptop with no network connection, finds eyes, and returns a number. When the inputs are this irreplaceable, telling those two things apart isn&amp;rsquo;t pedantry. It&amp;rsquo;s the difference between a tool she can hand the originals to and one she can&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="what-i-should-have-said"&gt;What I should have said
&lt;/h2&gt;&lt;p&gt;So, to set the record straight. The culler&amp;rsquo;s first pass is still pure arithmetic, no model, exactly as I said. But krites does machine learning now, and it&amp;rsquo;ll do more, deliberately, anywhere a learned model earns its place by giving a consistent, inspectable answer that a threshold on its own can&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;That isn&amp;rsquo;t me breaking the &amp;ldquo;no AI&amp;rdquo; promise. It&amp;rsquo;s me being precise about a word that&amp;rsquo;s stopped being precise. When I said no AI, I meant no generative AI. The machine learning was always coming. It was already in the camera that took the photo.&lt;/p&gt;</description></item></channel></rss>