<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Gabor Gulyas on Medium]]></title>
        <description><![CDATA[Stories by Gabor Gulyas on Medium]]></description>
        <link>https://medium.com/@guyko81?source=rss-626982c47f60------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*38b84dWUTJpNQyJe.</url>
            <title>Stories by Gabor Gulyas on Medium</title>
            <link>https://medium.com/@guyko81?source=rss-626982c47f60------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 13:39:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@guyko81/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How I Turned My Math Article Into a 14-Minute Animated Video]]></title>
            <link>https://medium.com/@guyko81/how-i-turned-my-math-article-into-a-14-minute-animated-video-e61e08184c9c?source=rss-626982c47f60------2</link>
            <guid isPermaLink="false">https://medium.com/p/e61e08184c9c</guid>
            <category><![CDATA[youtube]]></category>
            <category><![CDATA[text-to-speech]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Gabor Gulyas]]></dc:creator>
            <pubDate>Sat, 31 Oct 2026 17:35:49 GMT</pubDate>
            <atom:updated>2026-10-31T18:56:39.272Z</atom:updated>
            <content:encoded><![CDATA[<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FbZ3J2u-MEgM%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DbZ3J2u-MEgM&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FbZ3J2u-MEgM%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/5fcac90f56e1186ad54eee4db421c8ca/href">https://medium.com/media/5fcac90f56e1186ad54eee4db421c8ca/href</a></iframe><p>A while ago I wrote <a href="https://medium.com/@guyko81/the-hidden-geometry-of-numbers-524b864011f0">The Hidden Geometry of Numbers</a>: every whole number is a point in a lattice with one axis per prime, multiplying is adding arrows, and a sheet of glass sliding through that lattice finds the primes without dividing anything.</p><p>I always thought that it would be such a nice experience to watch it in a video, but making one takes a lot of effort. In this article I tell you what tools I used and how I solved the main problems.</p><h3>What’s in the video</h3><p>The video opens with a story: around 500 BC, in what is today Crotone in southern Italy, Pythagoras gathered a brotherhood around one belief: everything is number, and every length is a ratio of whole numbers. One of them, Hippasus, looked at the diagonal of a square with sides of length one is not a ratio of whole numbers. To a brotherhood whose motto was <em>all is number</em>, that was heresy. Legend says he went out to sea and never came back.</p><p>From there the video follows the article: numbers as places on a grid, multiplication as adding arrows, divisors, gcd and lcm, a new dimension for every prime, and fractions filling the other quadrants. Halfway through it returns to Hippasus, because by then his proof can be seen at a glance. The square root of 2 sits half a step along the 2-axis, between the grid points, while every fraction sits on a grid point. The last part is the ending of the article: the sheet of glass that sweeps through the lattice, catches the primes, and touches exactly one point for every number.</p><h3>The hardest part of an explainer video is timing</h3><p>When the narrator says “lay six’s arrow on its tip”, the arrow has to move at that moment, not half a second later. Lining this up by hand for fourteen minutes would be a lot of work, and every change to a sentence would shift everything after it.</p><p>To prevent myself from the timing hell above, I used the following approach: the voice was made first, the timing was taken from the voice, and the animation was written last, to follow that timing. This way I could avoid figuring out timings of an animation and just time it to the audio generated, even when I made changes in the narration. And I solved it in Python.</p><h3>The script</h3><p>The script is a Python file rather than a document: 14 scenes, 102 lines and 1,979 spoken words. Wherever something should happen on the screen, the line contains a mark in curly braces. Shortened, one of them looks like this:</p><pre>f&quot;Take seventy-two&#39;s arrow, {g6}and lay six&#39;s arrow on its tip.&quot;</pre><p>The mark {g6} stands for the moment the narrator starts the next word, in this case &quot;and&quot;. The whole script has 173 marks of this kind; a few of them only ask for a pause.</p><h3>The voice</h3><p>Each line was sent separately to a voice model, called Fish Audio Drama 3, running on my computer. A speech recognizer, Whisper, then listened to every recording and compared it word by word with the script. When a word was swallowed or missing, the line was recorded again. The final narration uses 148 recordings for the 102 lines, because 28 lines needed more than one attempt. Generating and checking the recordings that made it into the video took about 70 minutes.</p><h3>The timing</h3><p>Whisper gives more than the words it heard: it also says when each word starts and ends. So once the voice existed, I knew to a hundredth of a second when the narrator says the “and” in the sentence above: 62.13 seconds into its scene. The recordings were then joined into one narration track, with the pauses the script asks for, and a timing file was written next to it. That file records when each scene starts, when each line starts, and when each mark happens.</p><h3>The animation</h3><p>Only then came the picture. The animations are made with <a href="https://www.manim.community/">Manim</a>, the library 3Blue1Brown built for his videos, in its Community edition. The scene code never refers to a time in seconds. It waits for a mark and then moves. Simplified, the moment above reads:</p><pre>self.at(&quot;g6&quot;)    # wait until the narrator says &quot;and&quot;<br>self.play(...)   # then lay six&#39;s arrow on seventy-two&#39;s tip</pre><p>During rendering, the scene looks up g6 in the timing file and waits exactly that long. This is what keeps the code independent of any particular recording. When I rewrote a sentence, only that line had to be recorded again; the timing file was rebuilt, and every animation after it moved to its new place without any manual adjustment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J7_DICgjPzOUREvflJ6u_A.png" /><figcaption><em>Two sentences from the gcd and lcm scene. The blue shape is the narrator’s voice, the words sit where Whisper heard them, and each gold line is a mark from the script. The frames above show the screen 1.6 seconds after each mark.</em></figcaption></figure><p>One technical detail: Manim has a proper 3-D mode, but it was far too slow for clouds of glowing points. In a test it took about five minutes to render two seconds of film. The 3-D scenes are therefore ordinary 2-D scenes with a small perspective camera written by hand.</p><h3>Sound and the final cut</h3><p>Finally, the narration track was mixed with a quiet music bed that steps back whenever the narrator speaks, and with a few sound effects generated in code. The plucked string at the beginning, for example, is a few lines of <a href="https://en.wikipedia.org/wiki/Karplus%E2%80%93Strong_string_synthesis">Karplus–Strong synthesis</a>. The loudness was normalized to −14 LUFS, and the rendered scenes were joined with short crossfades.</p><h3>Reviewing the cuts</h3><p>While the video was being made, I watched it as a series of rough cuts: low-resolution previews at 480p, with sound, which render quickly. After each one I wrote notes, a code change3, and a new cut came back. Only when no notes were left would the video be rendered once at full quality, 1080p at 60 frames per second.</p><p>Some of the most important notes were about tone rather than mathematics. The voice model accepts a short instruction for every line, describing how to read it. For the dramatic moments of the Hippasus story the first instructions asked for a hushed delivery, and the model took that literally and whispered. It sounded strange rather than dramatic, so every line went back to a plain, calm narration. The opposite problem appeared in a few sentences written with too much excitement, such as “you just multiplied two numbers without multiplying anything!”, which sounded like a children’s programme. Those sentences were rewritten in a more even tone.</p><p>The most useful note was about a picture. The first 3-D scenes looked impressive in motion, but they were hard to read. When I put a frame from the video next to the plain matplotlib figure from my own article, the article’s version was clearly easier to understand. It shows the lattice inside a room with gridded walls, tick marks and labelled points, and those give the eye something to measure depth against. The 3-D scenes of the video were rebuilt in that style: the same room, a camera with almost no perspective distortion, sharp points, and a slow rotation that helps the eye read the depth.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5wRtnWfkIn5JmAjQx-R-5g.png" /><figcaption>The frame on the left comes from an earlier attempt from July, which was built on the same idea but had much flatter pictures. The frame on the right is the current video, after the change.</figcaption></figure><h3>Problems along the way</h3><p>A few problems were instructive enough to describe.</p><h3>The string that stopped vibrating</h3><p>The video opens with a plucked string playing an octave and a fifth, the whole-number ratios on which the Pythagoreans built their belief. The string’s movement on screen is driven by the actual loudness of the synthesized sound, so it should vibrate for exactly as long as the note can be heard. In the first cuts it didn’t: it moved briefly after each pluck and then froze while the note was still sounding.</p><p>Why did it happen? Manim tries to save work during pauses. The <a href="https://docs.manim.community/en/stable/reference/manim.scene.scene.Scene.html">documentation of </a><a href="https://docs.manim.community/en/stable/reference/manim.scene.scene.Scene.html">Scene.wait</a> says of its frozen_frame parameter: <em>&quot;If None (the default), the scene tries to determine whether or not the frame is frozen on its own.&quot;</em> It decides this by looking for updaters that depend on elapsed time. The string&#39;s movement was computed from the scene clock in a different way, so Manim concluded that nothing was moving and repeated the same frame. The fix was a single argument:</p><pre>self.wait(dt, frozen_frame=False)</pre><h3>The “ch” that was taken for noise</h3><p>Voice models sometimes leave a short breath or click after the end of a line, and at a cut this is heard as a glitch. The pipeline therefore cleaned the end of every recording: a sound that came after a silence was treated as noise and removed.</p><p>Then I noticed that the sentence “…predicting which point its sheet will touch” ended in “tou-”. The word “touch” contains a short silence of its own, the moment when the tongue closes before the “ch” is released. The cleaning step took that silence for the end of the line and removed the “ch” as noise.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NiH56w5d9vg95J89RJACmw.png" /><figcaption><em>The end of the sentence in the video’s narration track. The silence inside the word lasts 0.15 seconds.</em></figcaption></figure><p>The rule was then made stricter. A sound now counts as noise only if at least 0.35 seconds of silence separate it from the speech and it is shorter than 0.6 seconds. The pause inside a consonant is never that long.</p><h3>A file that Windows would not play</h3><p>The scenes are joined with crossfades in ffmpeg. The first finished cut would not open in the Windows Media Player, which reported “It uses unsupported encoding settings. 0x80004005.” It turned out that the crossfade filter had switched the video to the yuv444p pixel format, which many players and hardware decoders don’t support. Converting back to yuv420p at the end of the filter chain solved it. If a video made with ffmpeg plays in one player but not in another, the pixel format is a good first thing to check.</p><h3>The thumbnail</h3><p>Image generators are very good at atmosphere and poor at precision. A request for “a lattice with points labelled 1 to 6 and a plane passing them” produces something attractive but mathematically wrong.</p><p>Some of the thumbnails were made in two layers. ChatGPT generated only the backgrounds, from prompts that ruled out any text, kept all geometry soft and out of focus, and described which parts of the image to leave free: the top for the title, the left side for the diagram, and the right side for the red 7. Manim then drew everything that had to be correct on a transparent background: the room, the lattice, the glass sheet at the height of 7, the labels and the title. A short Python step placed the drawing over the background and added a soft glow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_6-hL0UA0_8MxcUT5lTRMg.png" /></figure><p>Two details mattered. First, the position of the drawing was measured from the background rather than estimated: the centre of the starburst was located in the pixels, and the lattice’s origin was placed exactly on it. Second, the transparent images from Manim store their colours in premultiplied form, and the fully transparent pixels still carried the scene’s background colour. Combined in the usual way, every semi-transparent edge became too dark. Rendering the drawing over black and combining it as premultiplied colour fixed that.</p><h3>Looking back</h3><p>Working this way, the voice and the timing turned out to be the backbone of the whole project. Once the animation followed the narration instead of the other way round, changing the script stopped being expensive, and that made it possible to keep improving the video through many rounds of notes.</p><p>The video is on <a href="https://youtu.be/bZ3J2u-MEgM">YouTube</a>, and the written version, with all its original figures, is <a href="https://medium.com/@guyko81/the-hidden-geometry-of-numbers-524b864011f0">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e61e08184c9c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Hidden Geometry of Numbers]]></title>
            <link>https://medium.com/@guyko81/the-hidden-geometry-of-numbers-524b864011f0?source=rss-626982c47f60------2</link>
            <guid isPermaLink="false">https://medium.com/p/524b864011f0</guid>
            <category><![CDATA[geometry]]></category>
            <category><![CDATA[prime-numbers]]></category>
            <category><![CDATA[mathematics]]></category>
            <category><![CDATA[number-theory]]></category>
            <category><![CDATA[science]]></category>
            <dc:creator><![CDATA[Gabor Gulyas]]></dc:creator>
            <pubDate>Tue, 30 Jun 2026 11:10:53 GMT</pubDate>
            <atom:updated>2026-10-31T18:56:35.742Z</atom:updated>
            <content:encoded><![CDATA[<h4>Numbers are secretly geometry — once you see it, you can’t unsee it.</h4><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FbZ3J2u-MEgM%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DbZ3J2u-MEgM&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FbZ3J2u-MEgM%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/5fcac90f56e1186ad54eee4db421c8ca/href">https://medium.com/media/5fcac90f56e1186ad54eee4db421c8ca/href</a></iframe><p>Pick a number. Say <strong>12</strong>. Break it into primes — 12 = 2 × 2 × 3 — and that’s the <em>only</em> way. This is the <strong>fundamental theorem of arithmetic</strong>: every whole number has exactly one prime recipe.</p><p>That sounds like a fact about <em>factoring</em>. I say it’s really about <strong>geometry</strong> — and once you see it, you can’t unsee it. Stay to the end and you’ll watch an <em>infinite</em> sheet slice through an <em>infinite</em> lattice and kiss a single point, and catch a prime in the very act of being prime:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*lVCdtgZS3SyfU8F_Raq4RA.gif" /></figure><p><em>That’s the destination. Here’s the road to it.</em></p><h3>From a number to a coordinate</h3><p>Here’s the move. Take the factorization of 12 and write it with explicit exponents, one slot per prime:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/267/1*ZKxDX4RndwHXn5uoWKd_aA.png" /></figure><p>Now throw away everything except the exponents and keep them in order:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/224/1*FbBCWomIBt713ngFQh7qHg.png" /></figure><p>That tuple <em>is</em> 12. No information was lost — uniqueness (the fundamental theorem) is exactly the promise that this address belongs to 12 and to nothing else. We’ve turned a number into a <strong>coordinate</strong>: the first axis counts powers of 2, the second counts powers of 3, the third counts powers of 5, and so on, forever. There is one dimension for every prime.</p><p>So integers live in an <strong>infinite-dimensional lattice</strong>, with the primes as the axes. That sounds intimidating, but almost every number you’d want to draw only lights up a few axes. If a number’s only prime factors are 2 and 3, all its other coordinates are zero — so it lives entirely in the <strong>2–3 plane</strong>, and we can draw it on ordinary graph paper.</p><p>Watch the small numbers take their seats. Horizontal axis = how many 2s, vertical axis = how many 3s:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-pGJSG9VCNuCehjLv6qLJg.png" /></figure><p>Read off a few to convince yourself:</p><ul><li><strong>1</strong> sits at the origin (0,0) — no primes at all, the empty product.</li><li><strong>2</strong> is (1,0), <strong>4</strong> is (2,0), <strong>8</strong> is (3,0): walking right doubles you each step.</li><li><strong>3</strong> is (0,1), <strong>9</strong> is (0,2): walking up triples you.</li><li><strong>6</strong> is (1,1), <strong>36</strong> is (2,2), <strong>12</strong> is (2,1).</li></ul><p>The grid isn’t a decoration. The position <em>encodes</em> the number, and — as we’re about to see — the <em>operations</em> you do to numbers become motions on the grid.</p><h3>Multiplying is just adding the arrows</h3><p>What happens to the coordinates when you multiply two numbers?</p><p>Multiplying means combining their prime factorizations — you pour both bags of primes into one bag. Powers of 2 add to powers of 2, powers of 3 add to powers of 3, each prime minding its own axis:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/253/1*bp_RosirdZN8pdtA_U5TWQ.png" /></figure><p>In coordinates that is the plainest thing in the world: <strong>add the vectors componentwise.</strong> Multiplication of numbers is <em>addition</em> of their coordinate vectors.</p><p>Take 12 = (2,1) and 18 = (1,2). Lay the second arrow tip-to-tail on the first, exactly the way you added vectors in school, and you land on (3,3) — which is 2³·3³ = <strong>216</strong>. And indeed 12 × 18 = 216.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*_XSYy3c-87rokAmT3TRleQ.gif" /></figure><p>Everything you know about vector addition now says something about arithmetic.</p><p><strong>Division is subtraction.</strong> To do 216 ÷ 18, walk the 216 arrow <em>backwards</em> by 18: (3,3) − (1,2) = (2,1) = 12. The catch: you only land on a whole number if no coordinate goes negative — which is exactly the condition that 18 <em>divides</em> 216. A negative coordinate would be a fraction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*3P7dbe63Xee90qyTBknzGg.gif" /></figure><p><strong>Raising to a power is scaling.</strong> Squaring doubles every exponent, so it just stretches the arrow to twice its length: 12² = (4,2) = <strong>144</strong>. Cubing scales it by 3, and so on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/520/1*iX0qy8OlRujRVOHmfRJE3Q.gif" /></figure><p>And <strong>1 is the origin</strong> — multiplying by 1 moves you nowhere, the zero vector.</p><p>The whole multiplicative world has quietly become a vector space over the exponents.</p><h3>gcd and lcm are two corners of a box</h3><p>This is the part that made me actually grin the first time I drew it.</p><p>Take two numbers and look at the smallest rectangle that has both of them as corners. Its other two corners — the bottom-left and the top-right — are the <strong>greatest common divisor</strong> and the <strong>least common multiple</strong>. For free.</p><p>Why? Go back to coordinates. A number d<em>d</em> divides <em>n</em> exactly when <em>d</em> sits “below and to the left” of <em>n</em> on <em>every</em> axis — every exponent of <em>d</em> is ≤ the matching exponent of <em>n</em> (you can’t have more 2s than <em>n</em> has and still divide it). So:</p><ul><li>the <strong>gcd</strong> of two numbers takes, on each axis, the <strong>smaller</strong> of the two exponents — the largest box that still fits underneath both. That’s the <strong>bottom-left corner</strong>.</li><li>the <strong>lcm</strong> takes, on each axis, the <strong>larger</strong> exponent — the smallest box that covers both. That’s the <strong>top-right corner</strong>.</li></ul><p>For 24 = (3,1) and 18 = (1,2):</p><ul><li>gcd: take the min on each axis → (1,1) = 2·3 = <strong>6</strong>.</li><li>lcm: take the max on each axis → (3,2) = 8·9 = <strong>72</strong>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*YNAVxiVR9L23tpureAbJ9A.gif" /></figure><p>Two famous facts fall out of the picture with no algebra at all.</p><p>First, <strong>gcd(a, b) × lcm(a, b) = a × b.</strong> On each axis, min + max is just the two original exponents added together (the smaller plus the larger is the same as the first plus the second). Adding coordinates is multiplying numbers — so the bottom-left corner times the top-right corner equals the two original numbers multiplied. Check it: 6 × 72 = 432 = 24 × 18. ✓</p><p>Second, two numbers are <strong>coprime</strong> exactly when their box is pinned to the axes — when the bottom-left corner is the origin (0,0) = 1. No shared axis, no common factor.</p><h3>Adding a third prime is just adding a third axis</h3><p>Nothing about this stops at two primes. A number whose factors are 2, 3, and 5 needs a third axis — the power of 5 — and now lives in 3-D space. The number <strong>30 = 2·3·5</strong> sits at (1,1,1), the unit corner pointing equally along all three primes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/459/1*S3jJM9RTRGYpukAzwuqnFA.gif" /></figure><p>Everything we just did still holds, verbatim. Multiply two numbers? Add their 3-D arrows. gcd and lcm? The min-corner and max-corner of the box they span, now a box in three dimensions. And it keeps going: a fourth prime is a fourth axis, a fifth prime a fifth axis, on and on. We just can’t <em>draw</em> past three — which is why the whole lattice is genuinely infinite-dimensional, with the primes as a basis. Every integer is one lattice point, reached by a unique combination of prime-axis steps.</p><p>So far we’ve stayed safely on the whole-number grid. But dividing and taking roots are about to carry us right off it — and each step we take outside rediscovers a famous kind of number.</p><h3>Step one: negative coordinates are the fractions</h3><p>We saw that dividing means walking an arrow backwards. So what happens when we divide <em>too far</em> — past the origin, into negative coordinates?</p><p>Take 6 = (1,1) and divide by 4 = (2,0). Subtracting walks us two steps left in the powers-of-2 direction, straight across the axis:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/296/1*Pg8DNkdOADDUMoWQS5Tv9A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*LBgTflNL8birZs_GZRfwaQ.gif" /></figure><p>That negative coordinate isn’t a glitch. <strong>Allow negative exponents and the lattice fills in with every rational number.</strong></p><h3>Step two: off the grid entirely — the irrationals</h3><p>Negative coordinates still land you on grid <em>points</em>. To leave the grid completely, we don’t subtract — we <strong>scale by a fraction.</strong></p><p>This is the exact step the ancient Greeks refused to take. The Pythagoreans built their world on a single faith: that every quantity is a ratio of whole numbers — every number sitting, in our picture, on a tidy grid point. So when one of them (legend names Hippasus) proved that √2 is <em>no such ratio</em> — that it slips forever between the grid points and can never be pinned to one — it landed not as a theorem but as a scandal. The story even has him drowned at sea for letting it out. They did not want to leave the grid. We’re about to anyway :)</p><p>Remember that squaring doubled an arrow’s length. Running that backwards, a <strong>square root halves it.</strong> Take the square root of 6 = (1,1) and you scale the arrow by ½:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/340/1*15N0PUA9tstd-YFVgUWaoA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*TLMjrHNmUF8Ex2DNpFaUeA.gif" /></figure><p>That point sits <em>between</em> the grid lines — at the exact midpoint of the diagonal out to 6, and there is no lattice crossing there. <strong>A point with non-integer coordinates is an irrational number.</strong></p><p>√6 isn’t a failure of the system. It’s the system telling you, geometrically, why √6 can’t be a fraction: a fraction must land on a rational-coordinate point, and ½ along <em>two</em> prime axes at once never resolves to one.</p><p>And what about π, or <em>e</em>? They’re here too — they just sit at coordinates that are themselves <em>irrational</em>. To reach √6 we <strong>divided the exponent by a whole number</strong> (a square root), landing on the rational coordinate ½. But you can’t reach π by dividing by any whole or fractional number — you have to raise to an <strong>irrational power</strong>. Since π = 2^{1.6515…}, it sits on the 2-axis at height log₂π, and that height is provably irrational (if it were a fraction p/q, then 2ᵖ = πᵠ, impossible). So the hierarchy is beautifully clean:</p><ul><li><strong>integer</strong> coordinates → whole numbers,</li><li><strong>negative </strong>coordinates included → fractions,</li><li><strong>rational</strong> coordinates → algebraic irrationals like √6, reached by <em>roots</em>,</li><li><strong>irrational</strong> coordinates → everything else, reached by <em>irrational powers</em> — where π, <em>e</em>, and the transcendentals live.</li></ul><p>Nothing is missing from the space. The “wilder” a number is, the stranger the coordinates you need to pin it down. <em>(A gem in passing: by the Gelfond–Schneider theorem, even an algebraic-but-irrational coordinate is enough to force transcendence — the point √2 on the 2-axis is 2^√2, a proven transcendental number.)</em></p><h3>Step three: let the exponents be anything — and a number becomes a plane</h3><p>Here’s where it gets strange in the best way. We’ve been treating 6 as <em>the</em> point (1,1) — the recipe 2 · 3. But is that the only way to reach the value 6?</p><p>What if we let the exponents be <em>any</em> real numbers? Then we could ask for pure powers:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/348/1*xhnYWBINV2OItq4reZQkzg.png" /></figure><p>Every one of these is a legitimate (a, b) whose value is exactly 6.</p><p>Collect them all, and they don’t form a point — they form a <strong>straight line:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/580/1*evyBfT3hS5llUpuCRJgSeg.gif" /></figure><p>The condition “2^<em>a </em>⋅ 3^<em>b </em>= 6” is, after taking logs, just <em>a</em>⋅ln2 + <em>b</em>⋅ln3 = ln6 — the equation of a line. The integer recipe (1,1) is merely the one spot where that line happens to cross a grid point.</p><p>And with three primes the line becomes a <strong>plane</strong> — the set of all (a, b, c) with 2^<em>a </em>⋅ 3^<em>b </em>⋅ 5^<em>c</em> = 6, tilted through the 2-3-5 space and passing through the lone integer recipe at (1,1,0):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*QfHAUACCtB9MS7bhNLWWWg.gif" /></figure><p>With all the primes, that plane becomes a <strong>hyperplane</strong> slicing through the infinite-dimensional space. That’s the final twist of the picture: once you free the exponents, a single number stops being a dot and reveals itself as an entire flat sheet of equivalent constructions — and the ordinary integer you started with is just where that sheet pierces the lattice.</p><h3>The hidden ruler: how far apart are these sheets?</h3><p>Here’s the prettiest part, and it’s exactly what makes the value a <em>number</em> rather than just a sheet. All of these “value = N” sheets are <strong>parallel</strong> — they share one normal direction, the <strong>log-prime vector</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*NY-0CszeSlCH-SPItYzL1Q.png" /></figure><p>And there’s a clean identity behind it. For a point <strong>x</strong>=(<em>a</em>,<em>b</em>,<em>c</em>,…), the dot product with that normal is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/362/1*gbm7Tr3-YGxpJrnxEoPHbQ.png" /></figure><p>So the sheet for <strong>value 1</strong> passes through the origin (ln1 = 0), and as you push the sheet straight out along the normal, its value climbs: <em>e^c</em> where <em>c</em> is how far you’ve gone. Let it sweep outward through the 2–3–5 lattice and watch which integers it sweeps across — each one lights up at the exact moment the plane reaches its grid point:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*lVCdtgZS3SyfU8F_Raq4RA.gif" /></figure><p>And here’s the payoff that brings us all the way back to the start. The plane sails right past <strong>7</strong> and <strong>11</strong> without ever touching a point — because 7 and 11 are <em>primes</em>, and there is no point for them anywhere in the 2–3–5 space. They demand axes we didn’t draw. That’s the fundamental theorem of arithmetic staring back at us geometrically: <strong>a prime is a number with no grid point in any lower-prime space — it forces a brand-new dimension into being.</strong> Every other number up to 12 is just a corner reachable from 2, 3, and 5.</p><p>The gap is the same story a dimension down or up. The “value = N” sheet always sits at log-height ln<em>N</em> along the normal from the origin sheet, so 1, 6, 36, 216 — equally spaced <em>multiplicatively</em> — land as <strong>equally spaced parallel sheets</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/520/1*b6Mx7_B0eGSlIwp2KH6F6Q.gif" /></figure><p>Bigger numbers are farther out; multiplying numbers <em>adds</em> these heights (because logs add). The logarithm of a number is, quite literally, how far along the normal its sheet sits.</p><p><em>One honest footnote on the word “distance.” The exact, well-behaved quantity is the dot product </em><strong>n</strong>⋅<strong>x </strong>= ln<em>N — a </em><strong><em>log-value</em></strong><em>, not a length. The true perpendicular distance is </em>ln<em>N divided by the </em><strong><em>length of the normal vector</em></strong><em>,</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/314/1*9MTemNAGiPzj50xLKvARWQ.png" /></figure><p><em>— the square root of the summed squares of the log-primes, one term per prime (not the count of primes). In the 2-D picture that length is √[(ln2)^2+(ln3)^2]​≈1.30, so the value-6 line sits about 1.38 from the origin, not 1.79. It’s a fixed constant in any finite picture, so the sheets stay equally spaced — just rescaled. But once you include all the primes, the sum under the root grows without bound, “Euclidean distance” collapses, and the log-value </em><strong><em>n</em></strong><em>⋅</em><strong><em>x</em></strong><em> is the only honest ruler.</em></p><h3>One infinite sheet, one grid point</h3><p>Now stop and really look at that value-6 sheet. It is <strong>infinite</strong>. It runs forever in every direction, slicing through an infinite-dimensional lattice that is itself packed with grid points everywhere you look — billions upon billions of them, out to infinity.</p><p>And it touches <strong>exactly one of them.</strong></p><p>Not two. Not zero. Not a scattering of them every so often. The entire boundless sheet threads through the single point (1, 1, 0, …) — the factorization 2 · 3 — and misses every other grid point in the whole infinite universe of integers, forever.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*W26BoAt5LPSBxWQXMQkQRg.gif" /></figure><p>That is unique factorization wearing its geometric face. A <em>second</em> grid point on this sheet would be a second whole-number recipe for 6 — a different bag of primes multiplying to the same value — and we know there isn’t one. So the sheet simply cannot graze another corner of the lattice. The uniqueness in the fundamental theorem <em>is</em> the statement that each value-sheet kisses the grid exactly once.</p><p>And now look back at the sheet sweeping outward. Every time it lit up an integer, it was <em>kissing</em> that number’s one grid point — so <strong>factoring a number is nothing more than knowing, in advance, which corner of the lattice its sheet is about to kiss.</strong> “Factor 60” means “predict the point the value-60 sheet will touch”: (2, 1, 1). And when the sheet glided past 7 and 11 touching nothing at all, that <em>was</em> the discovery of a prime — a sheet that kisses no point in the space it&#39;s allowed to reach.</p><p>And there’s something almost magical about <em>why</em> it can miss everything else so cleanly. The sheet is tilted by the log-primes ln2,ln3,ln5,…, and those numbers are <strong>incommensurable</strong> — no whole-number combination of them ever equals another (except the trivial, identical one). The tilt is “perfectly irrational” with respect to the grid. A sheet tilted at a <em>rational</em> angle would either miss the lattice entirely or, the instant it caught one point, catch an infinite row of them. This sheet does neither. It finds its one point and threads the infinite needle past all the rest — and it manages that trick precisely because of the primes underneath it.</p><p>That’s the picture I wanted to plant: the fundamental theorem of arithmetic isn’t only telling you that numbers factor. It’s handing you a coordinate system — and in it, integers are lattice points, fractions fill the quadrants, irrationals slip between the lines, and every value is a whole parallel sheet whose distance from the origin is its own logarithm.</p><p><strong>The arithmetic you’ve done your whole life was geometry all along.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=524b864011f0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fuzzy Decision Tree: You Don’t Have to Choose Between Accurate and Explainable Anymore]]></title>
            <link>https://medium.com/@guyko81/you-dont-have-to-choose-between-accurate-and-explainable-anymore-8f0e09cba7ab?source=rss-626982c47f60------2</link>
            <guid isPermaLink="false">https://medium.com/p/8f0e09cba7ab</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[statistics]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[explainability]]></category>
            <dc:creator><![CDATA[Gabor Gulyas]]></dc:creator>
            <pubDate>Tue, 23 Jun 2026 15:37:28 GMT</pubDate>
            <atom:updated>2026-06-30T11:14:30.269Z</atom:updated>
            <content:encoded><![CDATA[<h4>A single fuzzy decision tree matches a 300-tree random forest — and explains every prediction like a flowchart. Why this can be important for institutions (and for sheep).</h4><p>Imagine a flock of sheep scattered across a hillside, each one wearing a GPS collar. Every collar reports three numbers: how far east the sheep is, how far north, and its elevation. The sheep don’t move much — they just stand there, grazing.</p><p>From those readings alone, can you reconstruct the <strong>shape of the hill</strong>?</p><p>That’s the whole problem of regression in one picture. You have noisy measurements at scattered locations, and you want a model of the smooth surface underneath. The catch is that the tools most people reach for are surprisingly bad at it — and the tools that are <em>good</em> at it are black boxes you’re not allowed to use where we need to explain our decisions — like a bank or insurance company.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/863/1*K15tM6JdY4Esr1LO42Oubg.png" /></figure><p>Let me show you what I mean, and then show you a model that finally gets to have both.</p><h3>The usual suspects, and how they fail</h3><p>Here’s every common regressor’s best attempt at recovering our hill. Each one was tuned to its best hyper-parameters on a held-out validation split, plus our new contestant.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CQdhP9p7x4gP7KsFP_W14w.png" /><figcaption>Reproducing the hill</figcaption></figure><p>Look at what happens:</p><p><strong>A decision tree</strong> (R² = 0.84) turns a smooth hill into a ziggurat. Trees split the world into axis-aligned boxes and predict one flat value per box, so the surface comes out as a staircase. Two sheep standing one step apart on the hillside can get wildly different predicted elevations, just because a split happened to fall between them.</p><p><strong>Linear regression</strong> (R² = 0.42) does something even worse: it tilts a flat plane through the data and calls it a day. A hill is not a plane. No amount of fitting fixes that — the model simply can’t represent a bump.</p><p><strong>A random forest</strong> (R² = 0.94) and <strong>LightGBM</strong> (R² = 0.93) finally get the accuracy right. But notice the price: a random forest is <em>300 trees</em> averaged together, and LightGBM is a few hundred boosted trees. There’s no single flowchart you can point at. And if you slice through their surfaces, you can see they’re not even smooth — they’re accurate <em>but </em>slightly jagged.</p><h3>The fuzzy decision tree</h3><p>A <strong>fuzzy decision tree</strong> keeps the thing that makes a tree understandable — a series of yes/no questions about your features — but throws out the part that makes it ugly. Instead of a hard split (“is elevation-east greater than 0.3? go left, else go right”), every split is a <strong>smooth transition</strong>. Near the boundary, a sample partially belongs to <em>both</em> sides, weighted by how far it sits from the threshold.</p><p>The consequence is exactly what you want on a hillside: <strong>neighbouring locations get neighbouring predictions.</strong> There are no cliffs between leaves. The prediction surface is continuous, and it bends instead of stepping.</p><p>And it’s still <em>one</em> tree, so you’d expect it to be weaker than a forest, but it isn’t:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/571/1*015Smji8SwEFFRgoYX10BA.png" /><figcaption>Accuracy by model</figcaption></figure><p>One fuzzy tree beats the best random forest. On simpler problems it keeps pace with LightGBM too.</p><h3>“But a single deep tree always overfits”</h3><p>That’s the usual reason nobody trains one deep tree and trusts it. So let’s test it directly — grow the fuzzy tree deeper and deeper, and watch it against a random forest and LightGBM at <em>the same depth</em>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p448oLA5C3FNxkirm4kO6g.png" /><figcaption>Test overfitting by depth</figcaption></figure><p>Two things stand out. First, at matched depth the single fuzzy tree (blue) sits right on top of the 300-tree forest’s accuracy — it just gets there with one tree instead of three hundred. Second, and more importantly, <strong>it doesn’t blow up as it gets deeper.</strong> The fuzziness <em>is</em> the regularizer. Where a normal decision tree would carve the training data into one-sheep boxes and memorize the noise, the fuzzy memberships smear each sample across neighbouring leaves, so depth buys you <em>expressiveness</em> without buying you <em>overfitting</em>. The train-to-test gap stays small even with a hundred-plus leaves.</p><p>In code, that’s the whole model:</p><pre>pip install FuzzyDTree</pre><pre>from fuzzydtree import FuzzyTreeRegressor<br><br>model = FuzzyTreeRegressor(<br>    max_depth=12, min_samples_leaf=1, <br>    include_hard_splits=False, margin_min_scale=0.4<br>)<br>model.fit(X_train, y_train)<br><br>model.predict(X_test)            # smooth predictions<br>model.score(X_test, y_test)      # R²</pre><p>Two of those arguments are worth understanding, because they decide whether the tree comes out <em>smooth</em> or <em>staircased</em>:</p><ul><li><strong>include_hard_splits=False</strong> removes the option of a hard, zero-width step at a split. It’s important because a hard split always separates the <em>training</em> data most sharply, so if you leave it available the tree tends to pick it everywhere and you&#39;re back to a staircase-ish estimate. Turning it off forces every boundary to be a genuine ramp — which is the whole point on a smooth hill. Not every problem requires it, and the comprehensive test shows that keeping it True as default helps.</li><li><strong>margin_min_scale=0.4</strong> sets how soft those ramps must be: the narrowest allowed transition is 0.4× the feature&#39;s local spread. Raise it for smoother surfaces, lower it toward the default when some features genuinely have sharp thresholds.</li></ul><p>The flip side: on data with <strong>crisp, step-like signals</strong> — think “has this borrower ever been 90+ days late?” — you <em>want</em> hard splits, so you leave include_hard_splits=True (the default). The knobs let the tree match the shape of your problem; the <a href="https://github.com/guyko81/fuzzydtree#hard-splits-and-margin-scale--controlling-smoothness">README</a> has the full guidance.</p><p>It’s a scikit-learn estimator — fit, predict, score, get_params, the works. Drop it in wherever you&#39;d put a DecisionTreeRegressor.</p><h3>Why a bank should care</h3><p>Now replace “elevation on a hill” with “probability this customer defaults,” and the sheep with loan applicants. This is where the smoothness-and-explainability combo stops being an aesthetic preference and becomes a business and regulatory requirement.</p><p>Banks are, in practice, <strong>forced to use simple models</strong> — logistic regression or a shallow decision tree — for any decision that affects a customer, because they <em>have to explain</em> it. “Your loan was declined” has to come with reasons, and those reasons have to be honest reflections of what the model actually did, not a story bolted on afterward. That rules out random forests and gradient boosting for a lot of high-stakes decisions, and it leaves a painful amount of accuracy on the table.</p><p>A fuzzy decision tree breaks that trade-off. It’s a single tree, so every prediction is just a weighted walk from the root to the leaves — and you can read that walk off as a <strong>waterfall of feature contributions, built in.</strong> No SHAP, no surrogate model, no post-hoc approximation. The explanation <em>is</em> the model.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q9cL9NQ5aUkkHoaYeeFmCw.png" /><figcaption>Explaining a prediction</figcaption></figure><pre>import matplotlib.pyplot as plt<br>from fuzzydtree import FuzzyTreeClassifier<br><br>clf = FuzzyTreeClassifier(max_depth=5)<br>clf.fit(X_applicants, defaulted)<br><br># Why did THIS applicant get THIS decision?<br>fig, ax = plt.subplots(figsize=(9, 5))<br>clf.plot_log_odds_contributions(applicant, feature_names=feature_names, ax=ax)<br>plt.tight_layout()<br>plt.show()</pre><p>On the left, an applicant the model declines: a high debt-to-income ratio and high revolving utilization push the default odds up, and not even a decent income pulls them back enough. On the right, an approved applicant: strong income dominates, and nothing else moves the needle much. <em>(This example uses a synthetic credit dataset for illustration, but the mechanics are identical on real ones — e.g. Kaggle’s “Give Me Some Credit” or the German Credit data.)</em></p><p>A loan officer can read those two charts. A regulator can read them. The customer can read them. And the model behind them is as capable as the random forest the bank wasn’t allowed to deploy.</p><h3>How it compares on ordinary benchmarks</h3><p>The honest benchmark story is not “one fuzzy tree always beats a forest.” It usually doesn’t. If your only goal is the highest possible predictive score, a random forest or a gradient-boosted ensemble is still often the better default: they get to average or boost many trees, and that extra capacity matters.</p><p>The more interesting comparison is against a <strong>single hard decision tree</strong>, because that is the model class people choose when they need something compact, inspectable, and easy to explain. On that comparison, the fuzzy tree is often a large upgrade.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fBkgHdtZzpzRjpcgMIBiwA.png" /><figcaption>Regression benchmark</figcaption></figure><p>In the regression benchmarks, random forests are frequently ahead of one fuzzy tree, and LightGBM is often ahead of both. But the fuzzy tree consistently behaves like a much stronger single-tree model than DecisionTreeRegressor. The hard tree tends to plateau early or degrade as depth increases; the fuzzy tree keeps improving on several datasets because soft membership gives it a smoother bias without turning it into an ensemble.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1iHmbVQf22O-MNhu0STF7g.png" /><figcaption>Classification benchmark</figcaption></figure><p>The classification picture is similar. A random forest or LightGBM is usually the accuracy-first choice, especially when the data has many interactions or noisy boundaries. But the fuzzy classifier can easily beat a lone hard decision tree while remaining a single model whose predictions can be traced and explained directly.</p><p>So the practical positioning is:</p><ul><li>If you are free to deploy a black-box ensemble and only care about score, use the forest or booster.</li><li>If you need a single transparent model, a fuzzy tree is often a better baseline than a plain decision tree.</li><li>If you need smooth predictions or stable explanations near split boundaries, the fuzzy tree gives you something ordinary trees simply cannot represent.</li></ul><h3>The point</h3><p>For years the implicit deal in applied machine learning has been: <em>if you want accuracy, accept a black box; if you want to explain yourself, accept a worse model.</em> Linear models and lone decision trees on one side; random forests and gradient boosting on the other.</p><p>The sheep on the hill show that deal isn’t necessary. A single fuzzy decision tree:</p><ul><li>recovers a smooth surface where a plain tree gives a staircase and a linear model gives a plane,</li><li>can approach forest-level accuracy on smooth problems — with one tree,</li><li>often beats a plain single decision tree on tabular benchmarks,</li><li>doesn’t overfit as it deepens, because the fuzziness regularizes it, and</li><li>explains every prediction with a built-in waterfall, no SHAP required.</li></ul><p>You’re no longer forced to pick a black box just to get good predictions — for regression <em>or</em> classification.</p><pre>pip install FuzzyDTree</pre><ul><li><strong>Docs:</strong> <a href="https://guyko81.github.io/fuzzydtree/">https://guyko81.github.io/fuzzydtree/</a></li><li><strong>GitHub:</strong> <a href="https://github.com/guyko81/fuzzydtree">https://github.com/guyko81/fuzzydtree</a></li><li><strong>PyPI:</strong> <a href="https://pypi.org/project/fuzzydtree/">https://pypi.org/project/fuzzydtree/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f0e09cba7ab" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Claude Mythos: A Theory of Loops and Engrams]]></title>
            <link>https://medium.com/@guyko81/claude-mythos-a-theory-of-loops-and-engrams-20dfd491a010?source=rss-626982c47f60------2</link>
            <guid isPermaLink="false">https://medium.com/p/20dfd491a010</guid>
            <category><![CDATA[chatgpt]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[anthropic-claude]]></category>
            <dc:creator><![CDATA[Gabor Gulyas]]></dc:creator>
            <pubDate>Sat, 11 Apr 2026 12:37:49 GMT</pubDate>
            <atom:updated>2026-04-11T12:37:49.941Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Anthropic hasn’t said what’s inside. This is my theory — speculative, but every paper and number is sourced.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xaqTyQjTR9yVtMzXcqH-AQ.png" /></figure><p><strong>The short version:</strong> I believe Mythos combines a <strong>looped Transformer — </strong>the same block of layers running multiple times, giving the model deeper reasoning without more parameters — with an <strong>Engram-style memory layer</strong> that handles fact retrieval in constant time, freeing every loop iteration for pure reasoning. The rest of this article is the evidence trail.</p><p>On March 26, 2026, Fortune <a href="https://fortune.com/2026/03/26/anthropic-says-testing-mythos-powerful-new-ai-model-after-data-leak-reveals-its-existence-step-change-in-capabilities/">broke the story</a>: a draft blog post in an unsecured Anthropic data store revealed a model called <strong>Claude Mythos</strong> — described internally as a “step change” in capabilities. Two weeks later, Anthropic <a href="https://www.anthropic.com/glasswing">officially announced</a> Mythos Preview through <strong>Project Glasswing</strong>, a cybersecurity alliance with Amazon, Apple, Google, Microsoft, Nvidia, CrowdStrike, and others.</p><p>Leaked internal documents call it the <strong>“Capybara” tier</strong> — a new model class above Opus, <a href="https://fortune.com/2026/03/26/anthropic-says-testing-mythos-powerful-new-ai-model-after-data-leak-reveals-its-existence-step-change-in-capabilities/">“larger and more intelligent”</a> than anything before it.</p><p>The benchmarks show why. From the <a href="https://www-cdn.anthropic.com/08ab9158070959f88f296514c21b7facce6f52bc.pdf">system card</a> (Table 6.3.A):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/566/1*vt04FMpyHHhPT_NHQ0u1Vg.png" /></figure><p>Look at GraphWalks. <strong>Mythos more than doubles Opus 4.6 and nearly quadruples GPT-5.4</strong> on a benchmark that requires coherent reasoning across up to a million tokens of graph traversal.</p><p>And in cybersecurity, <a href="https://red.anthropic.com/2026/mythos-preview/">it autonomously found thousands of zero-day vulnerabilities</a> — including a 27-year-old bug in OpenBSD and a 17-year-old remote code execution flaw in FreeBSD — bugs that survived decades of human review and millions of automated fuzzing tests.</p><p>Something fundamental changed. But raw scale alone doesn’t explain it — GPT-5.4 is massive too, yet Mythos outperforms it by <strong>4x on long-context graph reasoning</strong>. The question: <em>what architectural innovation makes Mythos different?</em></p><p>The answer, I believe, lies in a quiet revolution unfolding in the research literature since late 2025.</p><h3>The Looped Transformer Revolution</h3><p>Starting in 2025, a series of papers converged on an alternative: <strong>what if you ran the same layer block multiple times, like a loop?</strong> The model doesn’t need more parameters — it needs more <em>iterations</em>. Each pass refines the internal representation in high-dimensional latent space, and the model only projects back to tokens at the very end.</p><p>The idea has an ancestor.</p><h3>The Universal Transformer (2018) — The Seed</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/761/0*69AFbameEKBA9rKI.png" /></figure><p>Google’s <a href="https://arxiv.org/abs/1807.03819">Universal Transformer</a> (Dehghani et al., ICLR 2019) was the first to bring recursion into Transformers. A single block repeated T times, with an <strong>Adaptive Computation Time (ACT)</strong> mechanism that learns when to stop:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/214/1*RqJQlN_RDQk2m0BYGNaFaQ.png" /></figure><p>Same function, every step. A fixed-point iteration that refines until it converges.</p><p>It was ahead of its time — the scaling laws of the era rewarded more parameters, not more iterations. But the idea never died.</p><h3>HRM (June 2025) — The Viral Moment</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lUFlCAOkER0MC43B.png" /><figcaption>HRM Concept and results</figcaption></figure><p>The <a href="https://arxiv.org/abs/2506.21734">Hierarchical Reasoning Model</a> (HRM) changed everything. Two recurrent modules — one for slow strategic planning, one for fast detailed computation — inspired by the prefrontal cortex vs. lower brain regions. With just <strong>27M parameters and 1,000 training samples</strong>, it scored 40.3% on ARC-AGI-1.</p><p>It went viral. Over 4 million views on social media. The <a href="https://arcprize.org/blog/hrm-analysis">ARC Prize Foundation’s analysis</a> later showed much of the gain came from the outer loop refinement process rather than the hierarchy itself — but the genie was out of the bottle. The community was paying attention to recursive architectures again.</p><h3>TRM (October 2025) — The Simplification</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/354/1*yJMZFQtX6uk_uJhi5KdQiA.png" /><figcaption>TRM Architecture</figcaption></figure><p>Samsung SAIL’s <a href="https://arxiv.org/abs/2510.04871">Tiny Recursive Model</a> stripped HRM down to its essence: <strong>one module, two roles</strong>. A 2-layer, 7M-parameter network alternating between “thinking” (updating the latent scratchpad with the input) and “answering” (updating the solution without the input). It hit 45% on ARC-AGI-1 — outperforming models 10,000x its size — with full backpropagation through all recursive steps.</p><p>The message was clear: <strong>recursive depth beats parameter count for reasoning.</strong></p><h3>ByteDance Enters — And Solves the Scaling Problem</h3><p>The small recursive models proved the concept, but they had a fatal flaw for production use: <strong>loops run sequentially</strong>. If you have L loops, every token takes L times longer to generate. Fine for a 7M research model. Catastrophic for a frontier-scale production system.</p><p>ByteDance published two papers in October 2025 that solved this.</p><h3>Parallel Loop Transformer / PLT — Zero Extra Latency</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HW78f-bCX__CTbXF.png" /><figcaption>ByteDance Loop Transformer</figcaption></figure><p>The <a href="https://arxiv.org/abs/2510.24824">Parallel Loop Transformer</a> introduced <strong>Cross-Loop Parallelism (CLP)</strong>: instead of waiting for all loops of one token to finish, you compute different loops of different tokens simultaneously:</p><ul><li>Token i, Loop 1</li><li>Token i-1, Loop 2</li><li>Token i-2, Loop 3</li><li>…all in the same decoding step.</li></ul><p>This works because LLM decoding is <strong>memory-bound, not compute-bound</strong> — most of the GPU is sitting idle anyway. The parallel FLOPs are essentially free.</p><p>Combined with <strong>Gated Sliding-Window Attention</strong> (sharing the first loop’s KV cache globally), PLT achieves the accuracy of a traditional looped model with <strong>virtually zero extra latency</strong>.</p><p><em>Sound like exactly what you’d need to deploy a looped model at frontier scale?</em></p><h3>Ouro / LoopLM — The Large-Scale Proof</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xk3hdPxvuN5iGfhE.png" /><figcaption>ByteDance Ouro</figcaption></figure><p><a href="https://arxiv.org/abs/2510.25741">Ouro</a> (“Ouroboros”) is the first large-scale, pre-trained looped language model. A standard decoder-only Transformer (RoPE, SwiGLU, sandwich norm) whose <strong>entire block runs T=4 times</strong>. The latent state never collapses into token space between loops — the model thinks in high-dimensional latent space and only projects to tokens at the very end.</p><p>The headline result: <strong>Ouro-1.4B with 4 recurrent steps matches 4B-parameter standard transformers.</strong></p><p>But here’s the result that matters most for our theory. The ByteDance team ran controlled experiments and found:</p><blockquote><strong><em>The advantage comes from superior knowledge manipulation — especially on tasks requiring multi-step reasoning and graph search.</em></strong></blockquote><p>Now look at that Mythos benchmark table again. <strong>GraphWalks BFS: 80%</strong>. Opus 4.6: 38.7%. GPT-5.4: 21.4%.</p><p>If the Ouro findings generalize — that looping specifically improves knowledge <em>manipulation</em> over long, structured contexts — then a looped architecture would predict exactly this pattern: a disproportionate jump on graph traversal, the benchmark most dependent on iterative multi-hop reasoning.</p><h3>The Missing Piece: Where Does the Knowledge Live?</h3><p>Looped architectures give you deeper reasoning, and PLT makes it deployable. But Ouro’s blind spot remains: ~2 bits per parameter, no matter how many loops. Loops help you <em>use</em> what you know, but they don’t help you <em>know more</em>.</p><p>The problem runs deeper. A conventional Transformer stores everything in its weights — facts, entities, idioms, all tangled together with the computation that’s supposed to reason over them. When the model encounters “Diana, Princess of Wales,” it doesn’t just look up who that is. It burns computation cycles in the early layers assembling those three tokens into a coherent entity — rebuilding a lookup table from scratch, every single forward pass. That’s a chess grandmaster re-deriving the rules of chess before every move. The knowledge is there, but it’s encoded in the wrong format — mixed into the same weights that do the thinking.</p><p>For a model like Mythos that needs to reason over vast codebases, chain obscure CVEs together, and recall the specifics of a 27-year-old OpenBSD system call — you need a different approach entirely.</p><h3>DeepSeek Engram (January 2026) — The Memory Primitive</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZJkCLGupMdVuK0VE.png" /></figure><p>DeepSeek’s <a href="https://arxiv.org/abs/2601.07372">Engram</a> (<a href="https://github.com/deepseek-ai/Engram">code</a>) proposes exactly that: <strong>pull the static knowledge out into a dedicated O(1) lookup module, and let the Transformer layers focus on reasoning.</strong> Deterministic hash functions map N-grams to embedding tables — no attention, no FFN, just a constant-time table lookup. A learned gating mechanism decides when to trust the result and when to ignore it, since hash collisions mean the lookup sometimes returns garbage.</p><p>The key infrastructure trick: because the lookups are deterministic and read-only during inference, the entire memory module can live on <strong>CPU RAM</strong> with negligible latency. The model’s factual capacity is no longer bounded by GPU memory — you can keep scaling the lookup tables on cheap system memory while the expensive GPU stays dedicated to active computation.</p><p>In the Engram-27B model, DeepSeek traded 17 MoE experts for a 5.7 billion parameter embedding module — and performance went <em>up</em> across the board. The lesson: when you give a model a proper place to store facts, the layers you free up become better at everything else.</p><p>For a looped model, this matters even more. Every loop iteration that used to be partly wasted on recalling facts can now focus entirely on what loops are good at: connecting dots, chaining inferences, exploring paths. One provides the facts. The other provides the thinking. For a model that <a href="https://red.anthropic.com/2026/mythos-preview/">chains KASLR bypasses, heap sprays, and ROP gadgets into working exploits</a> in under half a day, this pairing starts to look less like speculation and more like a requirement.</p><h3>Putting It Together: The Mythos Theory</h3><p>Anthropic has said nothing about what’s under the hood. This is a theory — but the circumstantial case is hard to ignore.</p><p>ByteDance showed you can loop for depth and parallelize the loops. DeepSeek showed you can pull knowledge into a separate memory module. Maybe Anthropic put the pieces together.</p><p>Mythos scores higher in exactly the places you’d expect looping to matter: 4x on graph traversal over GPT-5.4, disproportionate jumps in multi-step exploit chaining and long-context coherence. Its cybersecurity capabilities “emerged as a downstream consequence” of general improvements — not domain-specific training.</p><p>But Mythos also knows more. MMMLU up to 92.7% from Opus 4.6’s 91.1%, GPQA Diamond to 94.5% from 91.3%. Ouro showed looping can’t push knowledge storage past ~2 bits per parameter — so where are the extra facts coming from? That’s the Engram-shaped hole.</p><p>Maybe Anthropic found a completely different path. But they gave Mythos a new tier name because the old ones didn’t fit. I think it’s not a larger Transformer, a bigger Opus. I think it’s a new era.</p><ul><li><a href="https://arxiv.org/abs/1807.03819">Universal Transformers</a> — Dehghani et al., 2018</li><li><a href="https://arxiv.org/abs/2506.21734">Hierarchical Reasoning Model</a> — Wang et al., 2025</li><li><a href="https://arxiv.org/abs/2510.04871">Less is More: Recursive Reasoning with Tiny Networks (TRM)</a> — Jolicoeur-Martineau et al., 2025</li><li><a href="https://arxiv.org/abs/2510.24824">Parallel Loop Transformer</a> — ByteDance, 2025</li><li><a href="https://arxiv.org/abs/2510.25741">Scaling Latent Reasoning via Looped Language Models (Ouro)</a> — ByteDance, 2025</li><li><a href="https://arxiv.org/abs/2601.07372">Conditional Memory via Scalable Lookup (Engram)</a> — DeepSeek, 2026</li><li><a href="https://www.anthropic.com/glasswing">Project Glasswing</a> — Anthropic, 2026</li><li><a href="https://www-cdn.anthropic.com/08ab9158070959f88f296514c21b7facce6f52bc.pdf">Claude Mythos Preview System Card</a> — Anthropic, 2026</li><li><a href="https://fortune.com/2026/03/26/anthropic-says-testing-mythos-powerful-new-ai-model-after-data-leak-reveals-its-existence-step-change-in-capabilities/">Fortune: Anthropic ‘Mythos’ AI model representing ‘step change’</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=20dfd491a010" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Predicting Numbers. Start Predicting Distributions.]]></title>
            <link>https://medium.com/@guyko81/stop-predicting-numbers-start-predicting-distributions-0d4975db52ae?source=rss-626982c47f60------2</link>
            <guid isPermaLink="false">https://medium.com/p/0d4975db52ae</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[statistics]]></category>
            <category><![CDATA[time-series-forecasting]]></category>
            <dc:creator><![CDATA[Gabor Gulyas]]></dc:creator>
            <pubDate>Tue, 17 Mar 2026 22:42:07 GMT</pubDate>
            <atom:updated>2026-06-30T11:14:54.324Z</atom:updated>
            <content:encoded><![CDATA[<h4>How a single LightGBM model can output full probability distribution — no parametric assumptions required</h4><p>When your model predicts “tomorrow’s sales will be 4,200 units,” what does that actually mean? Is it confident? Could it be 3,000 or 6,000 with equal likelihood? Is the distribution symmetric, or is there a long tail of risk on one side?</p><p>A point prediction tells you none of this. And yet, most regression models in production output exactly one number per sample.</p><p>This matters: if you’re setting inventory levels, pricing insurance, or allocating resources, the <em>shape</em> of the uncertainty around a prediction is often more valuable than the prediction itself. A bimodal distribution (two likely outcomes) requires a fundamentally different decision than a tight unimodal one, even if the mean is the same.</p><p><strong>DistributionRegressor</strong> is a Python package that predicts full probability distributions p(y|x) using a single LightGBM model. It’s nonparametric, fast, and requires no distributional assumptions. In this article, I’ll explain how it works and why it takes a different approach than existing methods.</p><pre>pip install distribution-regressor</pre><h3>The problem with parametric distributional regression</h3><p>The most well-known approach to distributional regression is <strong>NGBoost</strong> (Duan et al., 2020), which uses natural gradient boosting to fit parameters of a chosen distribution family — Normal, LogNormal, etc.</p><p>NGBoost is elegant, but it has a fundamental constraint: <strong>you must choose the parametric family in advance.</strong> If you choose Normal but the true conditional distribution is bimodal, every prediction will be wrong in a way that no amount of training data can fix. The model literally cannot represent the true distribution.</p><p>This isn’t a theoretical concern. Real-world conditional distributions are often:</p><ul><li><strong>Multimodal</strong> — commute times (traffic vs. no traffic), house values (neighborhood mix)</li><li><strong>Zero-inflated</strong> — retail sales (store open vs. closed), insurance claims</li><li><strong>Heavily skewed with complex tails</strong> — medical costs, financial returns</li></ul><p>You could try fitting a mixture model, but then you need to choose the number of components. You could use a more flexible parametric family, but you’re always one assumption away from misspecification.</p><h3>A different approach: learn the CDF directly</h3><p>DistributionRegressor sidesteps parametric assumptions entirely. Instead of fitting distribution parameters, it learns the <strong>conditional cumulative distribution function</strong> (CDF) directly:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/274/1*AMlcaVXF8a2Xcbu7Vxz2iA.png" /></figure><p>The key insight is that this is a binary classification problem. For any threshold t and any training sample <em>(x_i, y_i)</em>, we know exactly whether<em> y_i</em> falls below <em>τ</em>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/148/1*qYJGoheGkgOQpV_MA8wq6w.png" /></figure><p>This is just a binary label — and LightGBM is very good at binary classification.</p><h3>The algorithm in four steps</h3><p><strong>1. Create a grid.</strong> Discretize the target range into n_bins threshold points τ_1,τ_2,…,τ_K.</p><p><strong>2. Create binary targets.</strong> For each training sample and each threshold, compute <em>z_ij</em>​=1{<em>y_i</em>​≤<em>τ_j</em>​}.</p><p><strong>3. Expand the dataset.</strong> Cross every sample with every threshold point. A dataset of <em>N</em> samples becomes <em>N</em>×<em>K</em> rows, each with the original features plus the threshold value as an additional feature.</p><p><strong>4. Train a single LightGBM model</strong> with cross-entropy loss and a <strong>monotone increasing constraint</strong> on the threshold feature. This constraint is critical — it ensures the model outputs a valid CDF (non-decreasing in <em>τ</em>).</p><p>At inference, the model predicts <em>F</em>(<em>τ</em>∣<strong>x</strong>) for all grid points, and the probability mass function (PMF) is recovered by differencing the CDF.</p><p>That’s it. One model. No custom loss functions, no natural gradients, no distribution parameters. The monotonicity constraint does the heavy lifting — it forces the output to be a valid CDF regardless of the input.</p><h3>Why this works surprisingly well</h3><p>The CDF formulation has several elegant properties:</p><p><strong>Every training sample provides dense signal.</strong> A single sample (<strong>x_</strong><em>i</em>​,<em>y_i</em>​) with <em>K</em> grid points generates <em>K</em> training examples: all thresholds below <em>yi_</em>​ get target 0, all thresholds above get target 1. There’s no ambiguity — the labels are exact.</p><p><strong>Monotonicity is baked in.</strong> LightGBM’s native monotone constraints ensure the predicted CDF is non-decreasing in the threshold, so you always get a valid probability distribution.</p><p><strong>Logistic loss gives calibrated probabilities.</strong> Cross-entropy loss naturally encourages the model to output well-calibrated probabilities, which is exactly what we want for CDF values.</p><p><strong>Arbitrary distribution shapes are free.</strong> Multimodal? Skewed? Zero-inflated? The model doesn’t care — it just learns where the CDF steps are.</p><h3>Show me the code</h3><pre>from distribution_regressor import DistributionRegressor<br><br># Train - that&#39;s it<br>model = DistributionRegressor(<br>    n_bins=100,<br>    n_estimators=1000,<br>    learning_rate=0.01,<br>)<br>model.fit(X_train, y_train)<br><br># Point predictions<br>y_mean = model.predict(X_test)          # E[Y|X] (mean)<br>y_mode = model.predict_mode(X_test)     # peak of density<br><br># Uncertainty quantification<br>intervals = model.predict_interval(X_test, confidence=0.9)<br>std = model.predict_std(X_test)<br><br># Full distribution<br>grids, dists, _ = model.predict_distribution(X_test)<br><br># Proper scoring<br>nll = model.nll(X_test, y_test)  # negative log-likelihood</pre><p>The API follows scikit-learn conventions. It also provides pdf(), cdf(), ppf() (inverse CDF), and logpdf() for density evaluation at arbitrary points.</p><h3>Real-world example: retail sales forecasting</h3><p>Let’s look at a concrete example using the Rossmann Store Sales dataset. We train a model to predict daily sales for a single store using calendar features, promotions, and holiday indicators.</p><pre>model = DistributionRegressor(<br>    n_bins=100,<br>    n_estimators=1000,<br>    learning_rate=0.01,<br>)<br>model.fit(train[features], train[&#39;Sales&#39;])</pre><h3>Time series with uncertainty bands</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Uk10qoELTg7uxTrS.png" /></figure><p>The model produces prediction intervals that naturally adapt to context. Promotional days show wider uncertainty bands. Sundays (when the store is closed) collapse to zero. The model learns all of this from the data — no special handling required.</p><h3>Individual predicted densities</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8_bkTRDajmRXlNgS.png" /></figure><p>Each test sample gets a full predicted density. The closed-day prediction is a sharp spike at zero. Promotional days show wider spreads. Regular weekdays are tighter. The red dashed line is the actual sales value.</p><p>These are not Gaussian distributions forced onto the data. They’re whatever shape the data requires.</p><h3>Comparison with NGBoost on California Housing</h3><p>To benchmark against NGBoost, we use the California Housing dataset from scikit-learn (20,640 samples, 8 features). House value distributions are often multimodal and bounded — properties that violate simple parametric assumptions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/297/1*asNdMMAXzudjfo563C0IEw.png" /></figure><p>DistributionRegressor outperforms NGBoost by 0.42 nats — without any distributional assumptions. NGBoost’s Normal distribution forces every prediction into a symmetric bell curve, which is a poor fit for bounded, multimodal house value distributions. (Target is median house value in $100k, sklearn default scale.)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZJADU-uuv9Bje16E.png" /></figure><p>DistributionRegressor (blue) captures complex conditional densities that NGBoost’s Normal (green dashed) simply cannot represent. The parametric model is forced into a symmetric shape regardless of the true data distribution.</p><p><strong>The nonparametric approach wins on both aggregate NLL and individual prediction quality.</strong> If your downstream decision depends on the <em>shape</em> of the distribution (not just its mean or variance), the nonparametric approach gives you information that the parametric one discards.</p><h3>What about memory?</h3><p>The main practical consideration is memory. The training set expansion — <em>N</em> samples times <em>K</em> grid points — means a 100k-sample dataset with 100 bins becomes 10M rows. This is manageable for most problems, but can be a constraint for very large datasets.</p><p>For those cases, there’s a Monte Carlo training mode (monte_carlo_training=True) that samples a subset of grid points per training sample, reducing peak memory by 5-20x. The mc_resample_freq parameter resamples fresh grid points every N trees during training, so different groups of trees see different thresholds — analogous to mini-batch SGD over the grid dimension. In our benchmarks, MC with resampling produced distributions close to the full grid at a fraction of the memory cost.</p><h3>Key design decisions</h3><p>A few choices that make this work in practice:</p><p><strong>Single model, not an ensemble of binary classifiers.</strong> Earlier approaches (like the histogram-based methods) train a separate classifier for each threshold. DistributionRegressor uses the threshold as a <em>feature</em> in a single model, which lets it share information across thresholds and requires far fewer parameters.</p><p><strong>Cross-entropy loss, not MSE.</strong> The binary targets <em>z_ij</em>​∈{0,1}, and we want calibrated probabilities. Cross-entropy is the natural choice and gives much better-calibrated CDFs than MSE.</p><p><strong>Monotone constraints, not post-hoc correction.</strong> Rather than predicting unconstrained values and fixing monotonicity violations after the fact, the constraint is built into the LightGBM training. This produces smoother, more stable CDFs.</p><p><strong>Output smoothing as a post-processing step.</strong> An optional Gaussian smoothing on the predicted CDF can be applied (and tuned) after training, without refitting. This is useful for smoothing stepped densities at lower grid resolutions, and can preserve discrete mass points (like exact zeros) through the atom_values parameter.</p><h3>When to use this</h3><p>DistributionRegressor is a good fit when:</p><ul><li>You need <strong>full conditional distributions</strong>, not just point predictions</li><li>The true distribution shape is <strong>unknown or complex</strong> (multimodal, skewed, zero-inflated)</li><li>You want a <strong>nonparametric</strong> approach with no distributional assumptions</li><li>Your data is <strong>tabular</strong> (it’s LightGBM under the hood)</li><li>You want a <strong>scikit-learn compatible API</strong> that drops into existing workflows</li></ul><p>It’s not the right tool if you need distributions over structured outputs (sequences, images), if your data is very high-dimensional (use a neural density estimator), or if you have strong prior knowledge about the parametric family (NGBoost will be more accurate in that case).</p><h3>Try it</h3><pre>pip install distribution-regressor</pre><ul><li><strong>Documentation</strong>: <a href="https://guyko81.github.io/DistributionRegressor/">guyko81.github.io/DistributionRegressor</a></li><li><strong>GitHub</strong>: <a href="https://github.com/guyko81/DistributionRegressor">github.com/guyko81/DistributionRegressor</a></li><li><strong>PyPI</strong>: <a href="https://pypi.org/project/distribution-regressor/">pypi.org/project/distribution-regressor</a></li></ul><p>The package is MIT licensed and the API is stable at v2.0. Contributions and feedback are welcome.</p><p><em>Gabor Gulyas</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0d4975db52ae" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>