<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://albertree.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://albertree.com/" rel="alternate" type="text/html" /><updated>2026-06-09T14:57:55+00:00</updated><id>https://albertree.com/feed.xml</id><title type="html">Seokki’s Homepage</title><subtitle>Personal website of Seokki Lee</subtitle><author><name>Seokki Lee (Albert)</name></author><entry><title type="html">Two ways to solve ARC — and why I took the harder one</title><link href="https://albertree.com/posts/2026/06/two-ways-to-solve-arc/" rel="alternate" type="text/html" title="Two ways to solve ARC — and why I took the harder one" /><published>2026-06-09T00:00:00+00:00</published><updated>2026-06-09T00:00:00+00:00</updated><id>https://albertree.com/posts/2026/06/two-ways-to-solve-arc</id><content type="html" xml:base="https://albertree.com/posts/2026/06/two-ways-to-solve-arc/"><![CDATA[<p>I spend most of my days thinking about a benchmark called <strong>ARC</strong> (the Abstraction and Reasoning Corpus). Each task gives you a handful of input→output grid pairs and asks you to produce the output for one new input. To a human it usually takes a few seconds. To most AI systems it’s a wall. That gap is the whole point — ARC is built to resist memorization and reward <em>reasoning</em>.</p>

<p>This post is less about ARC the benchmark and more about a choice I keep making, and re-justifying to myself.</p>

<h2 id="two-ways">Two ways</h2>

<p>If you look at everything that works on ARC, the approaches split cleanly into two families.</p>

<p><strong>Induction</strong> — program synthesis. You look at the demo pairs and search for a <em>program</em> that maps input to output, then run that program on the test input. The answer is an explicit, inspectable transformation. This is the world of brute-force DSL search, hand-written DSLs, library learning, and LLMs that write code.</p>

<p><strong>Transduction</strong> — predict the output grid <em>directly</em>, no program in between. The dominant trick here is Test-Time Training: at inference, fine-tune a model on the demo pairs of <em>this specific task</em> and let it generalize. No explicit rule, just a model that has bent itself around the examples.</p>

<p>The striking finding from the 2024 ARC Prize is that these two families <strong>solve different sets of tasks</strong>. Induction-only tops out around the low 40s%; transduction-only, similar. Neither alone is SOTA. The state of the art <em>ensembles both</em> — precisely because they fail in different places.</p>

<p>So the pragmatic move is obvious: do both. And yet I’ve chosen to go all-in on one side — pure, symbolic induction. The slower one. The one that, on its own, has a known blind spot. Here’s the reasoning I keep coming back to.</p>

<h2 id="a-spectrum-of-recombination">A spectrum of recombination</h2>

<p>There’s a nice way to see these two families as the <em>opposite extremes of one axis</em>: how you recombine pre-existing pieces.</p>

<ul>
  <li>Program search recombines a <strong>small set of generic primitives</strong> <em>deeply</em> — short vocabulary, long compositions.</li>
  <li>Test-Time Training recombines a <strong>vast set of specialized building blocks</strong> (the functions baked into a network’s weights) <em>shallowly</em> — enormous vocabulary, a few gradient steps of glue.</li>
</ul>

<p>Same underlying idea — “reuse and recombine” — but mirror images in the number of pieces, their generality, and the depth of composition. Once you see it that way, the question stops being “which one wins?” and becomes “<strong>where do you want intelligence to accumulate?</strong>”</p>

<h2 id="where-the-knowledge-lives">Where the knowledge lives</h2>

<p>That’s the question I actually care about. Across ARC systems, “the place where learning sticks” is different every time:</p>

<ul>
  <li>In Test-Time Training, knowledge lives in <strong>model weights</strong>.</li>
  <li>In library-learning systems like DreamCoder, it lives in a <strong>growing DSL</strong>.</li>
  <li>In fixed-DSL search, it doesn’t accumulate at all.</li>
</ul>

<p>My bet is a fourth option: let knowledge accumulate in a <strong>relational knowledge graph</strong> — a structured, symbolic semantic memory that grows as the system solves more problems. Not weights, not a flat library of program fragments, but <em>relations between objects, grids, and tasks</em>.</p>

<p><img src="/images/blog/arbor-architecture.png" alt="ARBOR architecture: a Soar-style decision cycle reading and writing a 5-layer ARCKG (Task → Pair → Grid → Object → Pixel)" />
<em>The system I’m building, ARBOR: a Soar-style working memory and decision cycle on the left, and on the right the knowledge it reads from and writes to — a 5-layer graph from Task down to Pixel. The “learning” lives in that graph, not in any weights.</em></p>

<p>Why that, specifically?</p>

<ol>
  <li><strong>It’s auditable.</strong> Every step is formal logic you can read. When a symbolic system is wrong, you can point at the exact rule that was wrong. A weight matrix can’t tell you where a concept is stored.</li>
  <li><strong>It’s bottom-up and self-extending.</strong> I want the system to grow its own abstractions from individual task pairs, not from a training corpus it had to see in advance.</li>
  <li><strong>It borrows from how people actually reason.</strong> The architecture I work in leans on ideas from cognitive science — Soar’s decision cycle, structure-mapping as the basis for analogy. Generalization, in this view, is finding the shared skeleton between two cases — <em>anti-unification</em> — which has a real cognitive justification, not just a statistical one.</li>
</ol>

<p>I gave this system a name: <strong>ARBOR</strong> — partly an acronym, partly Latin for <em>tree</em>, partly because it grows hierarchically, and partly (I’ll admit) because of my own handle, <em>Albert-tree</em>. The image of a tree that keeps branching is the whole vision in one word.</p>

<h2 id="the-honest-part">The honest part</h2>

<p>None of this makes the blind spot go away. Pure symbolic induction structurally <em>misses</em> the tasks that transduction is good at — if SOTA is an ensemble and I’m building one branch, I am, by construction, leaving tasks on the table.</p>

<p>I think that’s a fair trade, for now, and here’s the bet behind it: the interesting open question isn’t “induction <em>or</em> transduction.” It’s whether you can take transduction’s strength — <em>shallow recombination over a huge vocabulary</em> — and express it symbolically. A very large semantic library, composed shallowly. If that works, you don’t bolt a neural module onto a symbolic one; you grow the symbolic side until it covers the same ground, while staying explainable the whole way.</p>

<p>I don’t know yet if that’s possible. That uncertainty is most of why this is interesting. But that’s the direction — and it’s why, when there were two ways to go, I took the harder one.</p>

<p><em>If you’re working on ARC, reasoning, or neuro-symbolic systems and any of this resonates (or you think I’m wrong), I’d genuinely like to hear it — my contact is in the sidebar.</em></p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="thoughts" /><category term="ARC" /><category term="reasoning" /><category term="neuro-symbolic" /><category term="program-synthesis" /><category term="cognitive-science" /><summary type="html"><![CDATA[There are two ways to attack ARC, they solve different tasks, and the state of the art just ensembles both. So why am I betting everything on only one of them — the slower, harder, symbolic one?]]></summary></entry><entry><title type="html">Why stacking approximations makes me uneasy</title><link href="https://albertree.com/posts/2026/01/stacking-approximations/" rel="alternate" type="text/html" title="Why stacking approximations makes me uneasy" /><published>2026-01-21T00:00:00+00:00</published><updated>2026-01-21T00:00:00+00:00</updated><id>https://albertree.com/posts/2026/01/stacking-approximations</id><content type="html" xml:base="https://albertree.com/posts/2026/01/stacking-approximations/"><![CDATA[<p>Sooner or later, every “neuro-symbolic” conversation collapses to a single fork: <strong>symbolic representation vs. vector representation.</strong> I want to be honest about which side I lean to, and <em>why</em> — because the why is more interesting than the slogan.</p>

<p>Let me start by conceding the strong case for vectors. Their generalizability is real, and it’s the reason neural networks and transformers have developed at an insane pace. A continuous, distributed representation is diverse and near-universal; it handles big, noisy, multimodal data beautifully. I’m not arguing against any of that.</p>

<p>What bothers me is narrower: <strong>the habit of building everything out of accumulated approximations.</strong></p>

<p>Approximation is essential for <em>finding an optimal decision</em>. It is not obviously the right tool for <em>everything</em>. And if we commit to representing all knowledge as approximations stacked on approximations, the bill comes due as ever more compute and ever-better optimization — forever.</p>

<p>Here’s the picture I keep coming back to. Think about a <strong>data-scarce</strong> situation — which is the normal situation for a new ARC task, where you get a handful of examples and nothing else. There, you can’t average over a big corpus; you have to learn something that works <em>only here</em>, by trial and error. That’s what a scientist does: set up a hypothesis with <strong>defined variables</strong> — independent, dependent, manipulated, controlled — test it, and repeat. We learn <em>part by part</em>, and crucially, each thing we learn is <strong>anchored to a variable</strong>. What we learn is specific, not general.</p>

<p>Now compare that to a network updating all of its parameters from a single observation. There’s no anchor for the new information to stick to, so it gets distributed — smeared thin, scattered across weights. My claim is that this happens because there was no hypothesis, and therefore no anchor.</p>

<p>The cleanest way I can show the gap is with one tiny example. In my knowledge graph, a single PIXEL is just:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ color: 0, coordinate: { row: 3, col: 7 } }
</code></pre></div></div>

<p>That is already a symbolic fact. Now imagine that same pixel living inside a transformer — some vector, some tensor, not the legible thing above. Ask for it back, reconstruct it from the vector, and you might get:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ color: -0.0001388839, coordinate: { x: 2.999998793, y: 6.788208334 } }
</code></pre></div></div>

<p>Very close to what I started with. But here’s the thing humans do effortlessly and approximation alone does not: we know the difference between <strong>“exactly the same”</strong> and <strong>“very similar.”</strong> To recover <em>exactly-the-same</em>-level information, you need something that <strong>snaps to one side, like a magnet</strong> — something that makes the neural ↔ symbolic translation <em>commutable</em> instead of lossy.</p>

<p>This is the gap I think the serious neuro-symbolic work is circling. <a href="https://arxiv.org/abs/2012.05876">Garcez and Lamb’s <em>Neurosymbolic AI: The 3rd Wave</em> (2020)</a> frames the trade-off precisely: neural representations are distributed and continuous and ground concepts well on feature vectors; symbolic representations are localist and discrete and reason well and explainably. The open problem is grounding logical concepts onto vectors. My unease is specifically about the <em>return trip</em> — vector → symbol — where “very close” quietly replaces “the same.”</p>

<p>And I don’t think this is a fussy philosophical worry. It’s close to why pure deep learning struggles on ARC at all: the benchmark rewards <strong>skill-acquisition efficiency in the data-scarce regime</strong> (<a href="https://arxiv.org/abs/1911.01547">Chollet, <em>On the Measure of Intelligence</em>, 2019</a>) — exactly the regime where “learn part by part, anchored to a variable” should beat “approximate everything.” So when I bet on a symbolic graph as the place where knowledge accumulates, this is the intuition underneath it: I want the anchors back.</p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="thoughts" /><category term="neuro-symbolic" /><category term="representation" /><category term="reasoning" /><summary type="html"><![CDATA[Every neuro-symbolic debate eventually collapses to one question: vectors or symbols? I want to say carefully what bothers me about building everything out of approximations.]]></summary></entry><entry><title type="html">A solution is a tree, not a line</title><link href="https://albertree.com/posts/2025/07/a-solution-is-a-tree/" rel="alternate" type="text/html" title="A solution is a tree, not a line" /><published>2025-07-18T00:00:00+00:00</published><updated>2025-07-18T00:00:00+00:00</updated><id>https://albertree.com/posts/2025/07/a-solution-is-a-tree</id><content type="html" xml:base="https://albertree.com/posts/2025/07/a-solution-is-a-tree/"><![CDATA[<p>When people write an ARC solution, it usually looks like a straight line of steps:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">solve_0520fde7</span><span class="p">(</span><span class="n">I</span><span class="p">):</span>
    <span class="n">x1</span> <span class="o">=</span> <span class="n">vmirror</span><span class="p">(</span><span class="n">I</span><span class="p">)</span>
    <span class="n">x2</span> <span class="o">=</span> <span class="n">lefthalf</span><span class="p">(</span><span class="n">x1</span><span class="p">)</span>
    <span class="n">x3</span> <span class="o">=</span> <span class="n">righthalf</span><span class="p">(</span><span class="n">x1</span><span class="p">)</span>
    <span class="n">x4</span> <span class="o">=</span> <span class="n">vmirror</span><span class="p">(</span><span class="n">x3</span><span class="p">)</span>
    <span class="n">x5</span> <span class="o">=</span> <span class="n">cellwise</span><span class="p">(</span><span class="n">x2</span><span class="p">,</span> <span class="n">x4</span><span class="p">,</span> <span class="n">ZERO</span><span class="p">)</span>
    <span class="n">O</span>  <span class="o">=</span> <span class="n">replace</span><span class="p">(</span><span class="n">x5</span><span class="p">,</span> <span class="n">ONE</span><span class="p">,</span> <span class="n">TWO</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">O</span>
</code></pre></div></div>

<p>Six lines, top to bottom. It <em>reads</em> as a sequence. But that’s an artifact of how we write code, not the shape of the thing. Trace the data flow and the same solution is a <strong>tree</strong> (a DAG, really): <code class="language-plaintext highlighter-rouge">vmirror</code> branches into <code class="language-plaintext highlighter-rouge">lefthalf</code> and <code class="language-plaintext highlighter-rouge">righthalf</code>, those flow back together at <code class="language-plaintext highlighter-rouge">cellwise</code>, and constants like <code class="language-plaintext highlighter-rouge">ZERO</code>, <code class="language-plaintext highlighter-rouge">ONE</code>, <code class="language-plaintext highlighter-rouge">TWO</code> feed in from the side.</p>

<p><img src="/images/blog/solution-as-tree.png" alt="The same ARC solution drawn as a tree of DSL operations: inputs and constants as leaves, transformations as internal nodes, the output at the root" class="align-center" width="620px" />
<em><code class="language-plaintext highlighter-rouge">solve_0520fde7</code> as code and as a tree. The linear program is just one serialization of this branching structure.</em></p>

<p>I think this matters because <strong>human thinking isn’t sequential either</strong> — it’s more tree- or graph-shaped, with sub-results that get combined. So the natural representation of a solution is a tree: <strong>transformations as internal nodes, operands as leaves</strong> (input grids, constants, extracted properties).</p>

<p>And it’s not only the <em>transformation</em> that’s a tree. The <strong>condition</strong> that decides <em>when</em> a rule should fire is one too. A realistic firing condition is a nested pile of logic:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(((A or B or C) and D and E) or (F and G and H)) and ... and O
</code></pre></div></div>

<p>which is naturally a tree with <code class="language-plaintext highlighter-rouge">AND</code>/<code class="language-plaintext highlighter-rouge">OR</code> as internal nodes and <strong>property tests as leaves</strong>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AND
├── OR
│   ├── AND ── (A or B or C), D, E
│   └── AND ── F, G, H
└── ...
        A = { size.height   == 1 }
        D = { symmetry.hori  == true }
        J = { shape          == "rectangle" }
</code></pre></div></div>

<p>So both halves of a rule — the <em>what</em> (transformation) and the <em>when</em> (condition) — want to be trees.</p>

<p>Here’s why I find that exciting rather than just tidy. <strong>Once a solution is a tree, you can compare two solutions by comparing their trees.</strong> If two different tasks are solved by structurally similar trees, they very likely share an underlying abstraction. And “the shared part of two trees” is not a vague idea — it’s a precise operation: find the largest common skeleton of both trees and leave the parts that differ as variables.</p>

<p>That operation has a name. It’s <strong>anti-unification</strong> — the dual of unification — introduced by <a href="https://arxiv.org/abs/2302.00277">Plotkin (1970)</a> and Reynolds the same year, which computes the <em>least general generalization</em> of two terms (for a modern tour, see <a href="https://arxiv.org/abs/2302.00277">Cerna and Kutsia’s 2023 survey</a>). It is, almost literally, the formal version of the question <em>“what do these two solutions have in common?”</em> — the same <strong>commonality-finding</strong> move I keep coming back to, but lifted from grids to programs.</p>

<p>I’m not claiming my system does this yet. But this is the bridge I keep seeing in the distance, and it’s why I care about getting the representation right first:</p>

<blockquote>
  <p>represent solutions and conditions as <strong>trees</strong> → <strong>compare</strong> the trees → <strong>generalize</strong> the shared skeleton by <strong>anti-unification</strong> → accumulate those skeletons as reusable abstractions.</p>
</blockquote>

<p>Get a line of code to admit it’s a tree, and suddenly “learning a general rule from two examples” turns into a concrete tree operation. That’s the thread I’m pulling next.</p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="research-notes" /><category term="ARC" /><category term="program-synthesis" /><category term="anti-unification" /><category term="representation" /><summary type="html"><![CDATA[We write ARC solutions as straight sequences of steps. But the solution — and the logic that guards it — is really a tree. Once you see that, a way to compare two solutions falls out.]]></summary></entry><entry><title type="html">How big is the answer?</title><link href="https://albertree.com/posts/2025/07/how-big-is-the-answer/" rel="alternate" type="text/html" title="How big is the answer?" /><published>2025-07-09T00:00:00+00:00</published><updated>2025-07-09T00:00:00+00:00</updated><id>https://albertree.com/posts/2025/07/how-big-is-the-answer</id><content type="html" xml:base="https://albertree.com/posts/2025/07/how-big-is-the-answer/"><![CDATA[<p>Here’s a question that sounds trivial and isn’t: when you solve an ARC task, <strong>how big is the output grid?</strong></p>

<p>For a lot of tasks the output is the same size as the input, and you never think about it. But of the 1,000 ARC-AGI training tasks, <strong>320 change the grid size.</strong> Before you can draw the answer, you have to know its shape — and for these 320, that’s its own little puzzle.</p>

<p>So I tried to pin it down with rules. I wrote out the cases I could think of for <em>how</em> an output size gets determined:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>case 1 : the size difference is constant across all pairs
         1-a : output is N× the input
         1-b : output is 1/N× the input
case 2 : output is always a fixed size
case 3 : output size = the size of some object in the input
case 4 : output size comes from a property of an object/grid
case 5 : computing the size takes several steps
</code></pre></div></div>

<p>Then I counted. Cases 1 and 2 — the easy, “just look at the ratio” ones — cover <strong>182 of the 320</strong>. Case 3, where the answer’s size is borrowed from an object already sitting in the input, matches <strong>163</strong> tasks (73 of them <em>only</em> by case 3). After cases 1–3, <strong>63 tasks</strong> still fall through.</p>

<p><img src="/images/blog/grid-size-decision-tree.png" alt="A decision tree for determining output grid size" class="align-center" width="560px" />
<em>The branching I ended up with — first ask whether the size even changes, then whether it’s measurable directly, then fall back to objects.</em></p>

<p>I went through those 63 by hand, and they’re where it gets interesting. Almost none of them <em>give</em> you the size. You have to <strong>compute</strong> it:</p>

<ul>
  <li>count the rectangular objects, and that count <em>is</em> a dimension;</li>
  <li>take an object’s height <code class="language-plaintext highlighter-rouge">h</code> and width <code class="language-plaintext highlighter-rouge">w</code>, and the answer is <code class="language-plaintext highlighter-rouge">2w + h</code>;</li>
  <li>the side is the number of non-background colors, times the input width;</li>
  <li>and a recurring one — <em>the output is always a square</em>, which you can only exploit once you’ve noticed it.</li>
</ul>

<p>The thing I took away: “how big is the answer” is almost never handed to you. It’s a small reasoning problem of its own — a computation over what you observe, like object sizes and color counts. If a solver can’t <em>measure and do arithmetic on what it sees</em>, it can’t even get to the starting line for a third of the size-changing tasks. Determining the canvas is already part of solving the puzzle.</p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="research-notes" /><category term="ARC" /><category term="program-synthesis" /><category term="data" /><summary type="html"><![CDATA[Before you can solve an ARC task, you often have to answer a smaller question: how big is the output grid? I went through 1,000 tasks to see how hard that question really is.]]></summary></entry><entry><title type="html">Objects are not enough</title><link href="https://albertree.com/posts/2025/07/objects-are-not-enough/" rel="alternate" type="text/html" title="Objects are not enough" /><published>2025-07-07T00:00:00+00:00</published><updated>2025-07-07T00:00:00+00:00</updated><id>https://albertree.com/posts/2025/07/objects-are-not-enough</id><content type="html" xml:base="https://albertree.com/posts/2025/07/objects-are-not-enough/"><![CDATA[<p>If you want to reason about an ARC grid in terms of <em>objects</em>, you first have to decide what an object is. A common way — the one I’ve been using — is Hodel’s <code class="language-plaintext highlighter-rouge">objects()</code> detector, which takes three booleans:</p>

<ul>
  <li><strong>single-valued</strong> — must an object be one color, or can it be many?</li>
  <li><strong>diagonal</strong> — do diagonally-touching cells count as connected?</li>
  <li><strong>without-bg</strong> — is the most common color “background”, or just another color?</li>
</ul>

<p>Three switches, so eight ways to carve a grid into objects. And honestly, it works — most of the time it returns the things a human would point at and call “objects”.</p>

<p><strong>But it’s not enough.</strong> The moment I sit down and actually try to <em>solve</em> tasks with these objects, I keep hitting the same wall: I need to select things more freely than any fixed setting allows.</p>

<p><img src="/images/blog/objects-clamp.png" alt="An ARC task where a black 'clamp' must be split into a head and an arm" class="align-center" width="520px" />
<em>A task that looks like a clamp grabbing something and dragging it. To solve it you have to split the single black clamp into a “head” and an “arm” — a cut no fixed detector gives you for free.</em></p>

<p>Two kinds of trouble come up again and again:</p>

<ul>
  <li><strong>I need to merge things the detector splits.</strong> Two same-colored marks far apart on the grid — a frame, a border, a scattered outline — should sometimes be treated as <em>one</em> object. The justification is simple: they’re the same color.</li>
  <li><strong>I need to split things the detector merges.</strong> In the clamp task above, the black shape is really a head plus an arm, and you can only tell because the <em>output</em> shows a piece with the same color and shape. So the grounds for the cut come from elsewhere in the task.</li>
</ul>

<p>What I actually want isn’t a better fixed detector. I want the ability to <strong>decompose</strong> a detected object and <strong>freely recombine</strong> pieces into a new whole, with an explicit reason each time (same color, same shape, same size…).</p>

<p>And once you allow that, something nicer falls out. The ARCKG hierarchy I use — GRID, OBJECT, PIXEL — starts to look like nothing more than <em>object sizes</em>: a GRID is just the largest multi-colored object, a PIXEL is just the smallest single-colored one, and there’s a whole continuum of objects in between. The layers shouldn’t be three rigid shelves. They should be a <strong>fluid</strong> scale, where “what counts as one object” is decided per task, by the reasoning, not fixed in advance.</p>

<p>That realization — <em>objects are not a given, they’re a decision</em> — reshaped how I think about the whole representation.</p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="research-notes" /><category term="ARC" /><category term="objects" /><category term="DSL" /><summary type="html"><![CDATA[A standard object detector finds what looks like 'an object' most of the time. But the moment you actually try to solve tasks with it, you feel how rigid it is.]]></summary></entry><entry><title type="html">What do we actually compare?</title><link href="https://albertree.com/posts/2025/04/what-do-we-actually-compare/" rel="alternate" type="text/html" title="What do we actually compare?" /><published>2025-04-05T00:00:00+00:00</published><updated>2025-04-05T00:00:00+00:00</updated><id>https://albertree.com/posts/2025/04/what-do-we-actually-compare</id><content type="html" xml:base="https://albertree.com/posts/2025/04/what-do-we-actually-compare/"><![CDATA[<p>A while back I <a href="/posts/2024/08/humans-learn-by-comparing/">argued</a> that humans learn by comparing — by putting two things side by side and finding what’s the same and what’s different. I still believe that’s the engine. But it leaves an obvious gap: to compare two things, you first have to decide <strong>what about them</strong> to compare. So: what do we actually compare?</p>

<p>When I put an ARC input and output grid side by side, I’m clearly <em>not</em> comparing raw pixels. I’m comparing <strong>features</strong> — size, the set of colors, the area each color covers, symmetry — and for each feature I note whether it’s the <strong>same</strong> (COMM) or <strong>changed</strong> (DIFF). In my solver I literally print this out as a little “comparison receipt”:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>task 220 - pair 0
========================================
size
              rel    input  output  delta
  height      COMM   4      4       0
  width       DIFF   9      12      3
  area        DIFF   36     48      12
----------------------------------------
color
  count       DIFF   4      5       1
  4           COMM   4      4       .
  ...
----------------------------------------
symmetry
  hori        COMM   False  False
  ...
</code></pre></div></div>

<p>For a long time I treated this as just an engineering choice. Then I realized a cognitive scientist had written down almost exactly this, nearly fifty years ago.</p>

<p>In <strong><a href="https://pages.ucsd.edu/~scoulson/203/tversky-features.pdf">“Features of Similarity” (1977)</a></strong>, Amos Tversky rejected the then-standard view that similarity is <em>distance</em> in some geometric space. Instead he modeled each object as a <strong>set of features</strong>, and similarity as a <strong>contrast of common and distinctive features</strong>:</p>

\[S(A, B) = \theta\, f(A \cap B) \;-\; \alpha\, f(A - B) \;-\; \beta\, f(B - A)\]

<p>Read that slowly and it’s almost unsettling how close it is to the receipt above. The <strong>common</strong> features <code class="language-plaintext highlighter-rouge">A ∩ B</code> are my <strong>COMM</strong> rows — they push similarity <em>up</em> (the <code class="language-plaintext highlighter-rouge">θ</code> term). The <strong>distinctive</strong> features <code class="language-plaintext highlighter-rouge">A − B</code> and <code class="language-plaintext highlighter-rouge">B − A</code> are my <strong>DIFF</strong> rows — they push it <em>down</em> (the <code class="language-plaintext highlighter-rouge">α</code>, <code class="language-plaintext highlighter-rouge">β</code> terms). My comparison receipt is, more or less, <strong>a Tversky contrast model computed over grid features</strong>.</p>

<p>What I find most useful is what Tversky’s model <em>predicts</em> beyond the formula. Because similarity is feature-matching with weights, it is <strong>asymmetric</strong> (comparing A to B need not equal B to A) and <strong>context-dependent</strong> (which features you weigh shifts with what else is present). For ARC that isn’t a footnote — it’s the whole game. The <em>same</em> two grids, compared on a different set of features, point to a different rule. So <strong>choosing the features to compare is not preparation for the reasoning; it <em>is</em> the reasoning.</strong></p>

<p>This also tells me where comparison has to go next. Tversky compares objects on their <em>features</em>. But a lot of ARC isn’t about an object’s features — it’s about the <strong>relations</strong> between objects, and how those relations change. Comparing <em>relational structure</em> rather than flat features is precisely the step from Tversky toward Gentner’s structure-mapping — and toward <a href="/posts/2025/07/a-solution-is-a-tree/">comparing whole solution trees</a> instead of single grids. Same move, one level up.</p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="research-notes" /><category term="ARC" /><category term="comparison" /><category term="cognition" /><summary type="html"><![CDATA[Comparison is the engine. But to compare two things you first have to decide what about them to compare — and it turns out a cognitive scientist wrote down the formula for it in 1977.]]></summary></entry><entry><title type="html">The whole comes first</title><link href="https://albertree.com/posts/2024/11/the-whole-comes-first/" rel="alternate" type="text/html" title="The whole comes first" /><published>2024-11-26T00:00:00+00:00</published><updated>2024-11-26T00:00:00+00:00</updated><id>https://albertree.com/posts/2024/11/the-whole-comes-first</id><content type="html" xml:base="https://albertree.com/posts/2024/11/the-whole-comes-first/"><![CDATA[<blockquote>
  <p><em>The whole is greater than the sum of its parts.</em></p>
</blockquote>

<p>That line is the slogan of <strong>Gestalt psychology</strong>, and the more I work on perception in ARC, the more I keep returning to it.</p>

<p>Gestalt theory emerged in early-twentieth-century Austria and Germany (<a href="https://en.wikipedia.org/wiki/Gestalt_psychology">Wertheimer, 1923</a>; Koffka, 1935; Köhler, 1929) as a <em>rejection</em> of the structuralist idea that perception is built up by adding together atomic sensory elements. The Gestaltists argued the opposite: we perceive whole patterns and configurations first, and the parts come after. Their grouping laws are still the cleanest description I know of how raw pixels become <em>things</em>:</p>

<ul>
  <li><strong>Similarity</strong> — elements alike in color, shape, or size are seen as belonging together.</li>
  <li><strong>Proximity</strong> — elements close together are grouped together.</li>
  <li><strong>Closure</strong> — we fill gaps to perceive a complete figure.</li>
  <li><strong>Continuity</strong> — smooth, uninterrupted arrangements are read as one.</li>
  <li><strong>Figure–ground</strong> — we split a scene into a focal figure and its background.</li>
</ul>

<p>When you look at an ARC grid, this is <em>exactly</em> what happens before any “reasoning” begins. You don’t scan pixels left to right. You see groups — this cluster is one object, this color is figure, black is ground — and only then do you start comparing.</p>

<p>Why does this matter to me beyond being a nice description? Because Gestalt is where I feel the boundary of my own approach most honestly. I work in <strong>pure symbolic</strong> representations, and when I go law by law, some translate cleanly and some resist:</p>

<ul>
  <li><strong>Similarity</strong> is symbol-friendly — I can measure it from explicit features (same color, same shape, same size). ✓</li>
  <li><strong>Proximity</strong> is half-friendly — I can measure distance, but “closer <em>therefore</em> belongs together” isn’t itself a crisp symbolic fact; it’s a soft tendency.</li>
  <li><strong>Closure</strong> and <strong>figure–ground</strong> feel genuinely hard to make symbolic. They seem to need a notion of <em>the whole</em> and a <em>shifting focus</em> — figure–ground is almost a <strong>mode</strong> you flip, depending on what you’re attending to, not a fixed property you can write down.</li>
</ul>

<p>So Gestalt sits right on the seam of my research: it’s evidence that <em>some</em> of human perception is wonderfully symbolic, and a warning that some of it strains against a static, discrete representation. That’s the same tension I wrote about in <a href="/posts/2026/06/two-ways-to-solve-arc/">why I took the harder, symbolic road</a>.</p>

<p><img src="/images/blog/gestalt-history.png" alt="A diagram placing Gestaltism in the lineage of psychology that feeds into Cognitive Science and AI" class="align-center" width="680px" />
<em>Where Gestaltism sits, in my reading: a holism-leaning strand of psychology that — alongside information theory, linguistics, and computer science — flows into Cognitive Science and AI.</em></p>

<p>And the question is far from settled in modern AI, either. Do deep networks “see” Gestalt wholes? The evidence is genuinely mixed: convolutional nets show human-like sensitivity to grouping principles like closure and proximity — but often <em>only at the output layer</em>, which suggests they learn fundamentally different perceptual properties than we do (<a href="https://arxiv.org/abs/1810.08697">Understanding Deep Convolutional Networks through Gestalt Theory, 2018</a>; <a href="https://arxiv.org/abs/2203.07302">Mixed Evidence for Gestalt Grouping in Deep Neural Networks, 2022</a>). So “what is the right substrate for whole-first perception” is still open — which is exactly why it’s worth working on.</p>

<p><em>I put together a short talk walking through this history of Gestaltism and where it meets AI — <a href="https://youtu.be/kmM2ozltVrM?si=1JhONArOo07opLAZ">here on YouTube</a> if you’d like the longer version.</em></p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="thoughts" /><category term="cognition" /><category term="gestalt" /><category term="perception" /><category term="neuro-symbolic" /><summary type="html"><![CDATA[Gestalt psychology says we perceive wholes before parts. I find that idea beautiful — and I also think it quietly marks the boundary of how far a purely symbolic system can go.]]></summary></entry><entry><title type="html">Humans learn by comparing</title><link href="https://albertree.com/posts/2024/08/humans-learn-by-comparing/" rel="alternate" type="text/html" title="Humans learn by comparing" /><published>2024-08-15T00:00:00+00:00</published><updated>2024-08-15T00:00:00+00:00</updated><id>https://albertree.com/posts/2024/08/humans-learn-by-comparing</id><content type="html" xml:base="https://albertree.com/posts/2024/08/humans-learn-by-comparing/"><![CDATA[<p><em>This is the first of a few short notes I’m posting from my research notebook — older thoughts, lightly cleaned up, that trace how I got to where I am now.</em></p>

<p>A question I keep coming back to: <strong>how do humans learn a new concept? Where does it start?</strong></p>

<p>The answer I keep landing on is almost embarrassingly simple. It starts from <strong>comparison</strong> — from putting two things side by side and noticing what is the <strong>same</strong> and what is <strong>different</strong>.</p>

<p><img src="/images/blog/arc-example.png" alt="Three input→output example pairs and a test input with a question mark" class="align-center" width="420px" />
<em>A single ARC task. You’re given a few input→output pairs and one test input. To find the rule, you do exactly one thing first: you compare.</em></p>

<p>Take an ARC task. You look at the input grid, then the output grid, and then — without being told to — you compare them.</p>

<ul>
  <li>Some pixels changed color. → that’s a <strong>difference</strong>.</li>
  <li>Some pixels stayed in the same place. → that’s a <strong>commonality</strong>.</li>
  <li>The grid size is unchanged, the background is still black. → more <strong>commonalities</strong>.</li>
</ul>

<p>That’s it. That’s how we start finding the rule. And here’s the part I find interesting: you don’t even need two grids. You can do it with <em>one</em>.</p>

<p>Look at a single grid with a red pixel sitting in a field of black. Why do you see the red pixel as a <em>thing</em>? Because of the <strong>difference</strong> between it and its neighbors — a color boundary. (Even human infants segment the world this way: they find boundaries first.) And when you notice that the yellow pixels all sit one diagonal step from the red one — that’s a <strong>commonality</strong> you just discovered between them.</p>

<p>So my working picture of concept formation is roughly:</p>

<ol>
  <li><strong>Notice the differences</strong> first — they’re what catch the eye.</li>
  <li><strong>Find a commonality</strong> that connects them.</li>
  <li><strong>Name it</strong> — formulate the discovery as a concept.</li>
  <li><strong>Verify it later</strong>, by repetition, by finding the same commonality again.</li>
</ol>

<p>And if something new shows up that doesn’t fit anything you have — you just define it as its own concept, and wait to confirm it on a later example.</p>

<p>I don’t think any of this is special to ARC. It’s how I learned the alphabet (a teacher put twenty-six shapes on a board and I started telling them apart), how I learned a second language, how anyone learns a new piece of mathematics. We understand complicated concepts through <em>examples</em> precisely because examples are things you can compare.</p>

<p>If that’s right, then “comparison” isn’t a preprocessing step before the real reasoning. It might <em>be</em> the real reasoning — and a system that wants to learn like a human should probably be built around it. That’s a thread I’ve been pulling on ever since.</p>]]></content><author><name>Seokki Lee (Albert)</name></author><category term="thoughts" /><category term="cognition" /><category term="comparison" /><category term="ARC" /><summary type="html"><![CDATA[Where does learning a new concept actually start? I keep arriving at the same answer: it starts from comparison — finding what is the same and what is different.]]></summary></entry></feed>