Latent Program Networks 能解决抽象推理吗?[Clement Bonnet]
Bonnet 的核心判断是,ARC 击败当前神经网络,原因在于其隐藏任务位于模型学习分布之外,而非神经网络天生无法学习这些任务。 如果已知测试任务分布并据此训练模型,“就能解决它”;ARC 要求把人类的基础先验以陌生组合方式拼接起来,而预训练模型无法零样本处理。
Latent Program Networks 将在巨大参数空间或符号程序空间中的搜索,替换为在程序压缩连续表征中的优化。 编码器提出一个潜在的“最佳猜测”,梯度搜索不断调整它,直到某个潜变量能够解释所有示例,再由解码器将其应用于测试输入。其核心赌注是:这个压缩空间可能比巨大的参数空间更容易、更高效地进行测试时搜索。
早期结果是架构层面的证据,而不是 ARC 的突破:约 4000万参数、从零训练的模型,在评测中准确率约为 10%。 Bonnet 称,实验没有使用预训练 LLM,也没有接触评测集;400个训练任务经 Re-ARC 扩展后,约产生1亿个样本。模型尚未训练至收敛,他还表示,解码器容量可能导致部分任务根本无法学会。
所谓“无先验”的结果有一个重要限定:Macfarlane 指出,Re-ARC 本身就是一个相当强的先验。 Bonnet 承认,Re-ARC 注入了对象、计数等假设,但他认为这些先验主要是在教一个随机初始化的 Transformer 理解有意义的二维网格与随机序列之间的差异,这与 ARC 的“开发者知情”规则一致。
单一连续潜空间很可能无法实现不受限制的组合推理。 Bonnet 表示,Type 2 问题可能无法处理;单线程搜索“永远不会带来组合”,但浅层组合或许会自然出现。他提出的可行扩展是,把多条潜变量搜索线程接入展开式计算,也可以进一步结合可执行的符号程序。
系统的失败模式指向集成式方案,而不是一种通用推理架构。 初步检查发现,许多未学会的任务是传导式的:当任务不需要紧凑的归纳规则时,潜空间中“没有对应的点”来表示这种变换,只能让解码器包办一切。符号程序依靠可靠的解释器执行,潜变量搜索则针对程序搜索的组合爆炸。
Bonnet 更广泛的判断是,深度学习或许可以覆盖90%–99%的任务,但昂贵的最后一公里需要规划和程序合成。 LLM 能够为新颖解法赋予非零概率,但抵达其中一些解法可能需要100万次采样,这会让它们“创造力低几个数量级”。LPN 的扩展也并非没有代价:空间越大,可能越不平滑,并在训练和推理阶段都需要更多搜索。
1. ARC 测量的是与训练分布的距离,而非神经网络的不可能性
Bonnet 将 ARC——Abstraction and Reasoning Corpus——定义为一个测试系统适应新颖性的程序合成基准。其私有测试任务被刻意设计得不同于训练集,且事实上几乎不存在于互联网上,因此预训练 LLM 表现不佳。
Macfarlane 的反驳值得保留:他们对话中的大多数句子也从未出现在网上,但 LLM 仍然可以处理。Bonnet 的回答是,对话只占据相对狭窄的已学习潜空间,而 ARC 则以“任意方式”重组核心先验;在他看来,训练数据中不存在与之接近的类比。
这个绝对化的反事实假设明确了他的论点:如果研究人员知道一个包含测试任务的分布,并用“任何神经网络架构”进行训练,“就能解决它”。缺失的能力是极端的分布外泛化,而不是普通的机器学习能力。
2. 压缩比归纳与传导的标签更重要
Bonnet 将传导定义为直接根据数据进行预测,而不是使用解空间表征;Macfarlane 则把区别表述为根据数据创建模型,而不是复用现有模型,并认为只要权重不变,LPN 就属于归纳式方法。Bonnet 以 Awni Hannun 的检索、微调、预测流程作为传导的例子,但仍将潜变量搜索置于测试时训练的光谱上。
Bonnet 更深层的判断标准是“表征压缩瓶颈”。Python 程序或潜在程序是一种能够生成输出的简短解释;浅层的测试时参数重组压缩程度更低,因此更难进行组合式搜索。
关于核技巧的交锋揭示了 Bonnet 认为这套词汇不够有用的原因:线性回归既可以被表述为学习一个紧凑函数,也可以将其计算展开成训练数据与新输入的直接函数。LPN 同样位于测试时训练的光谱上,只不过它搜索的是由输入条件决定的潜空间,而不是完整参数空间。
3. LPN 将每个任务转化为可搜索的潜在程序
每个输入—输出对都会独立通过一个类似变分自编码器的编码器。由于能够解释单个输入—输出对的程序有无限多个,编码器输出的是潜在程序的分布,而不是一条明确规则。
同一任务中的示例应当产生相近的分布。概念验证阶段只是简单平均它们的潜在表征,再从平均点开始搜索;Bonnet 预计,基于混合分布的聚合或低温采样可能优于均值。
内循环使用解码器和已知的输入—输出对作为规格说明。它在潜空间中移动,寻找一个能够更好解释所有已观测输出的点,然后将同一个潜在程序和未见过的输入交给解码器——这“实际上就是在应用程序”,只是这个程序是一个向量。
训练时轮流留出每一个输入—输出对:其余 N−1 个示例生成用于预测该对的潜变量。这避免了直接编码目标输出的捷径,迫使表征捕捉输入与输出之间的关联。
4. 训练必须塑造潜空间的几何结构,才能支持搜索
目标函数通过 VAE 的 ELBO 损失,将重构项与高斯先验结合起来。没有这个先验,程序会扩散到“非常无结构、尖峰状的空间”中,彼此距离很远,使局部搜索几乎失去作用。
搜索在训练期间就已参与其中,因为编码器的职责是提供直觉,而不是给出最终答案。没有优化,解码器只会学习编码器对最佳猜测分布的回归;加入优化后,“最佳猜测最终可能很差,但离一个非常好的猜测很近”,这与 MAML 等元学习方法相呼应。
这会显著增加训练开销,因此 Bonnet 建议先在不使用搜索的情况下预训练,再使用搜索微调。他们尝试了零阶随机局部搜索和一阶优化;学习得到的解码器似然曲面足够平滑,因此一阶梯度优化表现良好。
Bonnet 也坦承,团队还没有深入绘制不同潜在方向分别代表什么。他们的可视化显示,起初是一团尚未理解的程序,随后学到的程序逐渐分离成簇并占据整个空间。他们也还没有测试主动向输入中加入噪声的效果。
5. 10%的概念验证使用小模型,但依赖大量合成数据
编码器和解码器都是从零训练的原生 Transformer,参数量各约2000万。模型将经过填充的 30×30 网格展平成包含900个值的序列,使用二维位置编码,并加入形状信息,使模型能够预测不同的网格尺寸。
Macfarlane 起初惊讶于只用“几乎什么都没有”的数据训练 Transformer 竟然能够运行;关键修正是,Re-ARC 将400个固定训练程序扩展为约1亿个输入—输出数据点。新增样本改变的是输入,而不是底层程序,教会模型理解相对位置和网格结构。
主持人质疑,这个生成器可能使“无先验”的说法失效。Bonnet 的回应是:Re-ARC 确实编码了对象性、计数等人类概念,但这些假设主要是在教模型理解二维网格,而不是向它泄露评测答案。
在这一设置下,团队在更难的评测分布上取得了约10%的初步结果,没有使用预训练 LLM,也没有接触评测集。Bonnet 强调,性能并不高,算力在收敛前就耗尽了,而且架构瓶颈可能使解码器无法掌握所有任务。
6. 连续程序以组合搜索换来了3个新的上限
Bonnet 修正了主持人对插值能力的乐观判断:固定的连续空间很可能无法容纳任意复杂的组合程序。它或许支持叠加或“几步组合”,但他认为单一搜索线程不会带来真正的组合能力,也无法解决 Type 2 问题。
他们提出的推测性桥梁是多线程搜索加分阶段执行:一个潜在程序先变换输入,另一个程序再消费变换后的输出,由此形成递归、展开式的计算图。Bonnet 称,ARC 的暴力解法深度从1到12或15不等,有时甚至达到50;Macfarlane 则认为,有界的神经组合或许可以覆盖大多数任务。
Macfarlane 提议使用显式 Python 程序,以适应不同网格尺寸。Bonnet 认同,使用解释器执行、并与现实对象绑定的程序,确实能够承载相关先验;一旦找到程序,解释器也能正确执行。相比之下,LPN 既要找到正确潜变量,又要提供足够大的解码器来执行它;当模型或潜空间过小时,继续搜索也无法找回解码器无法表示的程序。
初步任务分析也支持归纳—传导集成方案:多数未学会的任务看起来是传导式的。对于不需要紧凑规则的任务,潜空间中“没有对应的点”,因此 Bonnet 认为,强化解码器,或结合潜变量、直接解法与符号方法,都有明确空间。
7. 创造力是搜索效率问题,而规模扩张可能让问题恶化
Bonnet 仍然是连接主义者,但预计一次性深度学习或许可以处理90%–99%的任务;离散推理、长程规划以及“最后一个百分点”则需要程序合成。符号可能把合成推入一个完全新颖、无法被跟踪或摊销的组合空间。
他对创造力的更细致表述并不是说 LLM 完全没有原创性。Greenblatt 的采样方法显示,强程序位于一条长尾中,且具有非零概率,但抵达它们可能需要100万次抽样:模型只是“创造力低几个数量级”。
Macfarlane 反驳称,集体智慧本身就是一个巨大的采样器——许多人先提出弱想法,最终才有一个灵光乍现;而创造力还包含受文化影响的有趣性,而不只是新颖性。Bonnet 则将比较范围收窄到个人解决 ARC 的场景:直觉在“十分之一秒”内出现,随后可能提出2个、3个或4个假设,而在他的例子中,第5个假设是正确答案。
暴力扩展规模未必能保留今天这种容易进行的梯度搜索。Bonnet 预计,更大的程序流形会变得更不线性、更难插值、更不平滑,需要在训练和推理阶段投入更多搜索,同时还要扩大编码器和解码器。他始终押注于面向任务或查询的“小型表征”;这或许也有助于适应和认知不确定性,尽管他表示,当前系统还无法估计这种不确定性。
My real name is Clement Bonnet. I’ll go by Clem, so Clement, Clem Bonnet—whatever is easier for you.
Amazing. Clem, welcome to MLST. It’s so nice to have you here.
Thank you so much for having me.
I’ve just been reading your paper, and Chollet told me yesterday that it’s one of his favorite approaches to the ARC challenge. Can you tell the audience about the approach?
1. Search Moves Into Latent Space
The ARC benchmark—the Abstraction and Reasoning Corpus—is a program-synthesis benchmark whose goal is to assess how AI systems can adapt to novelty at test time. It’s a very interesting benchmark because pretrained LLMs perform very poorly on it. The tasks at test time are very different from the training set.
The whole assumption behind the architecture I’m going to introduce is that we should build search into the architecture, so that it can perform test-time search. The way we do this is by embedding programs into a latent space that we train to be easy to search.
TABS.ai is a new AI research lab I’m starting. It’s funded from past ventures involving AI, and we’re a Swiss version of DeepMind: a small group of very motivated, hardworking people trying to do research, starting with LLMs and ARC-style models. We’re looking for a chief scientist and research engineers. You can check out the positions at tabs.ai.
There have been many approaches to the ARC challenge, going back to the days of DreamCoder. Kevin Ellis had a domain-specific language, and perhaps even before that people were searching a DSL, which is exponential time. Ellis had a neurally guided search, where you train a neural network, and these days everyone is embracing this kind of Greenblatt approach, where you just make an LLM generate programs.
Yours is completely different from all of the other types of test-time adaptation. You’re embedding the program in a latent space and then searching that latent space. You’re not actually creating programs; you’re generating the solutions directly.
Yes, exactly. The assumption we had was that we want to do search at test time, and we want to do search in a program space. To do that efficiently, we try to embed computationally hard programs into a continuous latent space that we can then search using any search method we want.
By learning the manifold of programs in a single, coherent latent space, we allow efficient test-time adaptation. There have been a lot of methods for this. The top-performing methods use test-time training, which is parameter-efficient fine-tuning given a new test input. You search in your parameter space for parameters that would perform better on your test input.
That’s a very effective way to do this kind of recombination synthesis at test time. We argue that it’s a very inefficient way to do so because the parameter space is huge, and it’s not obvious that you can recombine low-level primitives in a compositional way.
It remains to be seen whether this approach can solve ARC, but we believe that by embedding programs into a more coherent and compressed representation, we can perform easier and more efficient search.
2. ARC Demands Extreme Generalization
It might be worth meditating for a second on why the ARC challenge is so impervious to neural networks. Chollet wanted it to be that way. Why is that?
Chollet designed it to be robust to memorization. The reason it is robust to memorization is that the input-output tasks are so different from anything seen in the training distribution. It’s quite guaranteed that they don’t exist anywhere on the internet. It’s a private test set that is kept completely hidden, and therefore the task similarity isn’t high enough for LLMs to generalize zero-shot, or at least we don’t get zero-shot generalization.
I want to push on that just a tiny bit. Many of the sentences we’ve spoken in this conversation don’t exist on the internet either. What’s the difference between that and an ARC program?
I believe our discussion can be embedded into a rather small latent space that the LLMs have learned to emulate and compose. The ARC tasks are truly novel. They’re based only on core human-knowledge priors, but they compose these priors in arbitrary ways that I believe are not anywhere on the internet. The pretrained language models cannot make sense of them; otherwise, we would get zero-shot generalization, which we don’t.
Are you saying that the reason neural networks can’t learn ARC is simply because there isn’t a lot of ARC-like data on the internet? It’s not because there’s something intrinsic about ARC—that it’s a discrete program distribution, or something like that—that neural networks can’t learn?
You’re saying they can learn it; it’s just that there’s no data out there. If we knew the kind of distribution of tasks that are in the test set and trained a neural network on it, we would solve it.
If you trained any neural-network architecture on a distribution that included the test-set data, you would solve it. It’s machine-learning training. The reason we can’t solve these test tasks is that the test distribution is quite far away from the training distribution. The system isn’t able to perform extreme generalization or come up with a novel way of recombining knowledge to perform the task.
3. Induction Meets Transduction
MLST is sponsored by CentML, which is the compute platform specifically optimized for AI workloads. They support all of the latest open-source language models out of the box, like Llama, for example. You can pay on consumption, essentially, where you can have a model which is always working or it can be freeze-dried when you’re not using it. All of the models that they deploy support the OpenAI API specification out of the box, which means it’s just a one-line change in your application to switch over to CentML and start saving money and make your application go faster.
This gets to the next interesting point. A few years ago, if you had asked me whether it was possible in principle to learn ARC, I might have said no. Many people, including Kevin Ellis, love the idea of generating programs, and that’s what Greenblatt did. We intuitively feel that we should generate a program because when it runs on a computer, programs can perform this kind of compositionality and various forms of generalization.
Intuitively, we think that if we generate the actual answer without going through an intermediate program, it won’t be as good. But that isn’t the case, is it?
I assume you’re referring to transduction versus induction—going through a compressed representation, which would be induction-based, versus directly trying to predict the answer.
Maybe we should define induction and transduction, because even before you get to transduction, you could train a neural network just to generate the solution directly, without using transduction.
That would still be transduction. If you predict the output data directly from the inputs—reading out the inputs to me—I define transduction as generating a prediction directly from the data, rather than as a statement about using the solution space.
For example, Awni Hannun’s work is transductive because, given some input examples, he performs retrieval, fine-tunes the model, and then makes a prediction. He’s building a new model for every datum that is received.
But we could build a massive neural network that directly predicts the solution space. That would still be an inductive model.
That’s where the fine line between induction and transduction comes in. I don’t know whether the distinction between induction and transduction actually matters. What matters to me is how much you compress the representation.
When you have an inductive method that comes up with a Python program—or perhaps some kind of latent program—the small representation is supposed to emulate your output. I would call that an induction method because there’s a representation-compression bottleneck.
If you’re not trying to do that, and you’re trying to perform a shallow recombination through test-time training, I would put that in the group of transductive methods. For me, it’s all about the compression, because compression allows more efficient search in the architecture.
We build a very compressed representation of programs in which we can effectively search. I believe that methods that generate Python programs are very similar methods: they generate a very compressed, low-bit program in Python that can generate an output.
I was going to ask you why a kernel method isn’t induction.
The goal of linear regression is to come up with a linear function that best explains your data. Once you have this function, you can use it for inference on a new input. When you solve a linear regression of the form \(y = Xa\), you have a solution of the form \((X^\top X)^{-1}X^\top y\).
You can write the output as a pure function of the training data and your new input by just multiplying matrices. I think that’s one representation of the kernel trick. Although you have this very low representation—a linear function—you can rewrite the whole thing as one computation. You could unroll whatever computational graph you have, and the whole thing becomes a single function.
But isn’t the kernel matrix the training data? It’s the inner product of the data, essentially giving you this positive-semidefinite matrix. Isn’t that the inductive function?
Yes. If you write it out—if you unroll whatever computational graph you have—the whole thing is a single computation. That’s where the equivalence between transduction and induction comes in, at least as I see it. That’s why I don’t think it’s a particularly useful distinction here.
I really like the distinction. To me, it’s about creating a model from the data rather than reusing an existing model. For example, we could make your model transductive if we fine-tuned the encoder based on some augmentations of the data.
I would argue that your system is not inductive if we’re doing some kind of gradient optimization through the model but aren’t changing the model. Therefore, it’s inductive.
We’re not changing the model; that’s the whole point. Although I do think that our latent program network, or LPN, search actually fits on the spectrum of test-time training methods.
If you see the latent space as an input-conditioned parameter space, in a way, we’re changing this space. We’re searching through it using zero-order or first-order optimization methods to find a better explanation for your data. That’s quite similar to searching through your parameter space and doing parameter fine-tuning at test time.
I do think they fit in the same spectrum. I’m curious to explore methods where this adaptation at test time would be as efficient as possible. If we want to create systems that adapt online very efficiently, I think real-time training is probably a very suboptimal method, because it involves searching in this vast parameter space.
Can you talk me through the architecture?
We want to embed programs into a latent space. To do that, we first have an encoder that takes input-output pairs independently and embeds them into a program, similar to a variational autoencoder architecture.
If you think about it, an input-output pair can be explained by an infinite number of programs of varying description length or complexity. We would want to find a minimum-description-length program, but because you can explain an input-output pair with an infinite number of programs, we use a variational framework.
We encode these programs into a distribution of programs. We embed the input-output pair into a distribution of programs, and then we have this search component in the middle, which I’ll explain in a moment. That component refines the latent representation to make it better. Once we have a better latent representation, we try to decode it with the decoder.
The decoder takes a new input and the latent program, and generates an output for it. It’s literally applying a program. I’m saying “programs,” but I really mean latent vectors. We’re working in a geometric space.
The true novelty of this architecture is the middle stage and how it works. We expect the encoder to output a first guess of what the program looks like. It’s similar to human intuition when solving an ARC task: “I believe there’s something about shapes here, and we should move them around.” That’s a first guess.
We then use optimization methods to refine this latent representation, finding a different point in the latent program space that would better explain the data. You have this inner loop that uses the decoder and the input-output pairs to refine the latent representation and find a better point in latent space.
For each input-output pair, that point should generate the right output. Once you find a point in latent space that better explains the input-output pairs, you’re confident that you can apply it to your new test inputs.
That’s where the induction part comes in. We’re trying to find a small representation—a small latent program—that explains the whole task.
ARC typically comes with about 3 specifications. By “specification,” we mean that you get a few input examples and place all of them through the encoder. The encoder gives you 3 different points in the latent space, you average those together, and then you perform gradient steps to improve the latent representation to a point where you get a good solution for all 3 examples. Is that roughly right?
Yes, exactly. We need to find a way to recombine these different latent distributions for the different input-output pairs. Ideally, each input-output pair should generate a roughly similar latent distribution through the encoder because they correspond to the same underlying task.
The straightforward way to do this is to compute a mean in latent space and use that as the starting point for the search. I believe there are better ways to do this. We could view the whole aggregation as a mixture of distributions and perhaps sample with a small temperature. There are probably better ways to aggregate these distributions.
Our first proof of concept used mean aggregation, which ends up working pretty well. The nice thing about this architecture is that it’s trained end to end using a variational autoencoder loss, the ELBO loss.
The ELBO loss is decomposed into 2 terms: the reconstruction loss and the prior loss. The reconstruction loss asks the model, given a refined latent program, to reproduce an input-output pair given all the other input-output pairs in the specification. The prior loss makes sure that the latent space looks roughly Gaussian. This is the classic prior loss from the variational autoencoder, and it encourages the latent space to be structured.
We did try training without the variational framework—a pure autoencoding framework, with no prior on what the latent space should look like. We ended up with very unstructured, spiky spaces where most programs tended to be as far away from one another as possible. That made search impossible and rendered the latent space rather useless.
The variational aspect is very important for keeping a Gaussian, compressed representation of the program space.
Let’s double-click on that. We want to have a well-formed space that doesn’t degenerate and doesn’t memorize solutions. I think you also use the KL divergence. In your paper, you said that you want to prevent encoding the output directly in the latent space.
You encode an input-output pair into the latent space, but train the representation to decode the output of a different input-output pair, to prevent memorization. That was one of the tricks you used.
It’s not a trick. With most of these latent-space ideas, when they’re applied to program-synthesis benchmarks such as ARC, there’s usually a problem: you want to learn a compressed representation of whatever your input-output space is and then try to decode it, but the obvious issue is that you may end up compressing your output into the latent space.
You would leak your output into the latent space, making it easier to reconstruct. That would be a shortcut that machine-learning training would find, and it would be useless because you wouldn’t learn anything that connects the input to the output. You would simply learn to map the output into the latent space.
We deal with this by using a setup during training that is very similar to the setup we have at test time. We have access to N input-output pairs, and we want to predict an N + 1 output.
During training, if we assume N input-output pairs, then for each input-output pair we embed the other N − 1 pairs into the latent space to predict that pair. We do this for all the pairs in parallel.
The other great trick you use is gradient steps—search steps—during training, because that makes the final latent space more amenable to search at inference time. Was that something you thought of intuitively, or did you add it because the system wasn’t working?
We’ve always wanted to train using search. The reason is that during training, and even at the end of training, the encoder isn’t perfect. The assumption is that it provides a best guess of what the task is.
If you’re not allowed to refine that guess, the decoder can only be as good as the encoder’s guess. You end up with a latent space that cannot encode many programs; it just encodes something like the regression of the encoder’s best-guess distribution.
We activate the search method during training. It brings significant overhead at training time, so we propose some kind of pretraining without it, followed by fine-tuning with it. But the main point is that by switching it on during training—using random local search or first-order gradient optimization—we train the latent space to be good at being searched.
The best guess ends up being quite bad, but very close to a very good guess. That’s the whole idea behind meta-learning methods such as MAML and similar approaches.
Qualitatively, tell me about the latent space. You’ve looked at it. Do you find that different directions correspond to different types of ARC problems, or whatever? What do you see?
We haven’t analyzed the latent space that much yet by comparing different ARC tasks, although I think it would be very interesting to do so.
In the paper, we show a t-SNE visualization of the different programs that are learned. During training, we see a kind of blob containing the input-output pairs that aren’t well understood either by the decoder or by the encoder. They end up in this bag of unlearned programs.
As programs start being learned, they begin to cluster and occupy the space. We scaled this architecture for ARC very recently and got some nonzero, yet non-SOTA, results.
Although we don’t get very high performance, we get it without any priors: without using pretrained LLMs and without looking at the evaluation set. We trained only on the 400 training-task distributions, yet obtained around 10% on the evaluation set, which is itself a harder distribution of tasks.
That was quite impressive from the first scaling curves we obtained.
4. Training Transformers From Scratch
Can you explain that in a little more detail? You’ve got an encoder and a decoder, and they’re Transformer models. You’ve got the re-ARC tasks from Michael Hodel. What kind of Transformers were they, and what kind of training did you do?
The LPN architecture assumes encoder and decoder modules. To train this architecture on ARC, we used vanilla Transformers from scratch to represent the input-output grids.
You could use any generative model or sequence model. We decided to use self-attention Transformers that flatten the input-output grids into sequences of 900 values, because these are 30 × 30 grids. We used relatively smart 2D positional encodings to make sense of the spatial structure, but otherwise it was a very vanilla Transformer architecture.
The models were rather small—about 20 million parameters each, or roughly 40 million in total for the encoder and decoder. We didn’t tune the Transformer architecture very much. We had to do some work on the positional encoding to make sense of the grids, but otherwise it was very standard.
We focused on learning a good latent space of programs and on how to search it efficiently during training and at test time. We experimented with the Transformer architectures at first, decided we were good to go, and then focused on what we thought was interesting.
This blows my mind because most people who use Transformers fine-tune them. They take models that have been trained on internet data and fine-tune them. The intuition is that it’s very difficult to train Transformers, and they only start doing useful things when you train them with a lot of data for a long time.
You started from scratch. I think you said you didn’t even train it to convergence, using just Michael Hodel’s re-ARC dataset. That’s hardly anything, and it still worked quite well.
The goal of our training was to learn to embed these 400 training tasks into the latent space. If we do that in a structured way, then by interpolating through the latent space during search at test time—given a new input-output pair, or rather a new task—we hope to find programs that weren’t seen during training but represent interpolations or superpositions of programs seen during training.
Training a Transformer on single input-output pairs from a single program would be a very bad idea. There isn’t enough data for it to make sense of positional relationships and so on.
We use Michael Hodel’s very useful library of re-ARC transformations. The dataset he created is a distribution of input-output pairs for each of the 400 tasks, following that task’s program. The program is constant, so there isn’t much more information to extract. The program is constant; only the input distribution is broad rather than consisting of a single input.
This makes it possible to train Transformers to make sense of 2D grids in a way that they couldn’t when initialized from scratch on a single input.
We show some training curves on ARC. We didn’t have enough compute to train to convergence, but we showed an increase in accuracy on the training set. We believe there are bottlenecks in the architecture that make learning very slow or prevent it from converging to 100%. We’re trying to figure that out.
In this re-ARC dataset generator, with the 400 tasks expanded using Michael Hodel’s data generation, how much data do you have? How many program examples?
It’s on the order of 100 million data points.
That’s a lot.
It is quite a lot. We sample these programs extensively. Transformers are very bad at making sense of random sequences of pixels when they’re trained from scratch, so we need this intensive training to make sense of the input-output grids.
It’s a bit unusual, but we believe ARC is a developer-aware generalization benchmark. We wanted to have no priors from anywhere on the internet, so we didn’t use pretrained LLMs. We trained the architecture only on the data that matters to the task.
Isn’t the re-ARC dataset generator itself a prior, though? To me, that would invalidate the developer-aware generalization. The prior contained in re-ARC seems like a pretty big one.
The priors contained in re-ARC are about situations such as having 2 objects in an input-output pair when you could have the same transformation with 3 objects. It includes priors such as counting, objectness, and so on.
The priors used in re-ARC are primarily there to teach the Transformers how to make sense of 2D grids, which I believe is in line with the developer-aware generalization assumption.
We can debate that. How did it generalize to different grid sizes? You’re using a Transformer, so you turn the grid into a sequence. Did you notice any weird overfitting to sequence sizes?
We handle the shapes in a very naive way. Let’s say you have a 10 × 10 input grid and a 10 × 11 output grid. We predict that it will have 10 rows and 11 columns, and then we use the 900 tokens corresponding to padded 30 × 30 grids, most of which are padding that the Transformer ignores.
We end up with sequences of 902 tokens per grid, including the shape information. That’s how we predict shapes. There are many things to say about how to optimize this, and we don’t think it’s optimal, but it’s probably sufficient for ARC, given that we have a lot of data to train on.
5. Symbolic Programs Bring Composition
I suppose this might be an example of why generating an explicit Python program could be better, because it could generalize to different grid sizes.
Definitely. Python programs, or grounded programs executed by an interpreter, would make sense of all these priors that aren’t contained in a randomly initialized Transformer.
I do believe real programs are also a very smart way to perform induction and compress representations. One of the problems with program search, of course, is the combinatorial explosion of searching through the program space.
We try to compress that by learning the manifold of programs, which we hope will be easier to search. But it comes with some caveats. Potentially, you can’t represent all programs in this latent space.
There are interesting ways to combine both ideas. There are pros and cons to program search and latent-program search.
I’m thinking about when I first interviewed Chollet years ago. He always said there were Type 1 and Type 2 problems, and Type 2 problems were not interpolative. But now we’re taking it for granted that this is actually interpolative after all.
We have this latent space with all of these modes, and we perform test-time computation. The more test-time computation we do, the better the results. At the moment, if I understand correctly, you’re using a first-order gradient-search method. First of all, talk me through that. You do more search at inference time and get better results?
Before that, I do believe Type 2 problems would not work with our method. One of the big limitations of learning a continuous latent space is that you can’t fit arbitrarily complex compositional problems—or programs—into it.
I think you can do some things such as a superposition of programs, but I don’t think you can do composition of programs, which is what we’re trying to investigate in follow-up work. A single-threaded search in this single latent space would not bring you composition.
I think you can get some composition, perhaps a couple of steps of composition, but at the end of the day it’s a fixed, continuous latent space. It’s unclear whether you can really compose objects with it.
You’ve touched on an interesting point. This gets to the core of it. People such as Gary Marcus and Chollet were intuiting the lack of compositionality.
You’re saying there is no compositionality in the latent space, but we could develop some kind of compositional inference-time method that gives us compositionality. We could have multiple search threads through the latent space and a System 2 program that composes the things together, while still using your method. Is that what you’re saying?
I think that’s a promising way to think about it. Single-threaded search in a single latent space would never bring you composition, but having multiple threads or multiple things happening at the same time and synthesizing them afterward could be a way to obtain composition.
We wanted to do a proof of concept of how to fit as many programs as we can into the latent space. I think this opens up many possibilities for extending the idea.
With symbolic programs, it feels as though we could compose them together. For example, we could learn iterative steps of functions that are composed together. In this case, we have direct solution predictions.
Let’s say I’ve found 3 solutions that represent 3 different types of transformations that need to be composed together. Now we need a function that can somehow take those 3 solutions and combine them.
I don’t think you can easily look at those 3 solutions and synthesize them in one shot. It would be equivalent to having a single, better search thread.
If you want to explore different regions of the space and recombine them, you might need to perform multiple forward passes. One thread could give you a primitive program, and then you apply the second thread to the output of the first primitive, and so on.
That would give you a recursive, unrolled computational graph. It’s a research question how to think about these things.
Or perhaps the decoder could take multiple inputs and learn to compose them to a fixed depth. Language isn’t actually that compositional. We use only limited depth; some languages don’t use much depth at all. In English, we have a depth of perhaps 3, 4, or 5.
You might find that most of the ARC problems we ever want to solve have a fixed depth, which we could somehow encode in a neural network.
ARC solutions using brute-force program search have depths varying from 1 to 12 or 15, and sometimes up to 50. But for most tasks, you can solve them using a rather short program.
We’re pragmatic people. I love the theoretical arguments about compositionality, symbolic AI, and so on, but in the real world we don’t need an infinite conversation.
Going back to the search methods, the assumption is that there exists a different latent program in your latent space that is better than your first guess. The question is how to find that program.
It’s simply a question of search. We tried 2 things. First, we tried zero-order random search: a local search around the first program. Then we realized that the latent space itself, learned during training, ended up being very smooth.
By smooth, I mean smooth with respect to the decoder likelihood of decoding the correct input-output pair. You want to maximize the likelihood of decoding the correct output, and this likelihood ended up being very smooth.
That allowed us to use first-order methods. I do believe that, with different tasks or if we trained for longer, we could have more discrepancies and local optima in this space. That might bring methods such as evolutionary strategies or other zero-order methods to the table, perhaps in combination with gradient methods.
For our use case, gradient descent worked very well. I don’t think it’s the best method, but it worked.
At the moment, the space is smooth and amenable to a simple gradient-ascent method. What about its resilience to noise? Have you tried putting noisy inputs in?
Another possibility is that you could undertrain the architecture but perform more and more search. Have you explored that relationship?
We haven’t tried adding noise and deliberately shooting ourselves in the foot, although perhaps we should. We have tried scanning the architecture size.
If the Transformers are too small, they can’t represent or decode the output very well. There’s a trade-off because architecture size and latent-space size go somewhat hand in hand.
When the architecture is too small, it can’t make sense of the sequences, so it can’t decode the program very well, regardless of how large the search space is. When we train the architecture, there isn’t much tuning to do, but we need to make sure that the decoder has enough capacity and that the latent space is large enough.
There needs to be a point in latent space corresponding to the right program, and the decoder needs enough capacity to execute that program. That isn’t the case when you use Python programs, because the interpreter will execute the program correctly; you only need to find it.
There’s a second constraint here: making sure the decoder can learn the program. We have preliminary results—not included in the paper—showing that it isn’t obvious that the decoder can learn all of the tasks. There’s also a paper mentioning this issue.
It comes down to what priors you build into the architecture in terms of 2D spatial representations and so on. It’s a trade-off between how much time you want to spend optimizing the architecture to solve a specific benchmark and how much you want to improve the method itself, namely the search for latent programs.
Ellis and Welling recently released a paper comparing induction and transduction. They had an ensemble approach that did both at the same time. They have a beautiful Venn diagram showing that sometimes induction with Python programs works really well, while sometimes direct solution-space prediction works really well.
Have you analyzed what types of ARC tasks work well or poorly on your system?
We don’t have a strong analysis yet, but from what we’ve seen, most of the unlearned programs correspond to transductive tasks. I think that’s very much aligned with their paper. It was a great paper highlighting this distinction.
When it came out, about a week before the ARC Prize deadline, we realized that this was probably what was happening: we were struggling to learn these transductive tasks.
There are obvious ways to use an ensemble technique with our architecture and with other architectures. The problem with transductive tasks is that there is no point in the latent space corresponding to a transductive task. By definition, no induction is necessary there.
Everything happens in the decoder. I think there are ways to help the decoder with that. We’re trying to remain as benchmark-agnostic as possible and not overfit to ARC, but the obvious ways to improve are there.
6. Deep Learning Needs Program Synthesis
Is there a story that you’re not a symbolist? Have you always been a connectionist, and are you relatively new to trying to improve connectionist architectures to tackle the 1% or 10% of tasks that people are interested in these days—reasoning and search?
I believe in a future where most tasks can probably be tackled with one-shot, deep-learning-based architectures such as LLMs. But some of the hard, last-mile tasks—the final 1% or 10%—need something different.
I’m a strong believer in deep planning and program synthesis being merged to achieve that last percent of tasks.
So you’re not amenable to the school of thought that these systems are simply smarter than us and that we should let them do what they do, even if we can’t understand it?
I don’t think we can comprehend arbitrary combinatorial sequences of symbols. That’s where connectionism is useful: we learn manifolds of complexity. It can bring you very far, but it can only bring you so far.
Deep architectures can solve perhaps 90% or 99% of tasks, but there will be tasks that are inherently discrete, or tasks that require long-horizon planning and reasoning—things we’re trying to tackle with LLMs and other deep architectures today.
More recently, I’ve started to think that all the architectures could do this much more efficiently. I also believe it’s connected to creativity.
Deep-planning models are not highly creative. I won’t say they aren’t creative, because if you go back to Greenblatt’s solution—sampling a lot of programs—you can see logarithmic scaling curves as you sample more and more programs.
If you plot the distribution of possible programs sampled by an LLM that outputs code, you have nonzero mass, with long tails, on very creative and good solutions. You only get those solutions if you sample a million times.
In that sense, I can say that these systems aren’t creative. If you sample enough, they will create these things, but they’re exponentially less creative. You would need exponentially more samples to make them creative.
I think symbolic programming and program synthesis could help with this creativity issue. What we call programs may just be sequences of symbols. We could fight this creativity issue by doing symbolic program synthesis, moving into a combinatorial space that is completely novel and cannot be tracked or amortized.
That gets us closer to things such as computational irreducibility. I do think program synthesis and symbols could help deep architectures become more creative.
That makes sense. I’m very interested in creativity. I’ve taken an almost human-chauvinist approach to creativity, believing there’s a creativity gap in LLMs—that we have access to some source of creativity that LLMs don’t.
Many people I’ve interviewed in Europe have told me the opposite. Saba Karamcheti said the opposite at ICML in June. He said these systems are very creative, partly because creativity isn’t just about novelty. It’s also about interestingness—things we find interesting given our cultural biases and interests.
These systems seem to capture a lot of that. You can say that Greenblatt’s approach requires sampling 30,000 times to get something interesting, but is that really so different from us? We’re a huge collective intelligence. We all come up with ideas, most of which are rubbish, and every now and then we have a flash of inspiration.
Perhaps we’re embedded and situated, with access to all this entropy and everything else, but is it really so different?
There’s something to be said about collective intelligence. I haven’t thought that much about collective intelligence in general, so I can’t comment very well on it.
If you focus for a moment on individual intelligence, it seems obvious that you don’t assemble a million programs in your mind when trying to solve an ARC task or come up with something creative. Some kind of synthesis happens in your brain much more efficiently.
I’m interested in trying to get closer to that kind of synthesis. I do believe there’s something very interesting about collective intelligence, which brings much more entropy and much more of the sampling we talked about.
Perhaps both schools could come up with interesting breakthroughs.
When you solve an ARC challenge, are you being creative?
When I try to solve an ARC task, I have some intuition about what the task might look like within a tenth of a second. I see the structures and regularities very quickly. Then I realize, “No, this isn’t as easy as I thought.”
I propose a few hypotheses—perhaps 2, 3, or 4—and refine them. The fifth one is correct. I check that it matches all the specifications, apply it, and get the answer.
That’s how I solve perhaps half of the tasks. The other half takes more time, but the basic idea is to come up with a few hypotheses, test them, and not come up with a million hypotheses.
Those hypotheses seem quite creative in the sense that they can be very different from one another and recombine priors in a way that is combinatorial and compositional. I need to remain humble about how I solve the tasks, because I struggle with many of them, but that’s the idea: propose a few hypotheses and test them without generating a million.
How do you cut down the search space efficiently? There’s probably much more to human cognition, but getting this would at least be a good approximation.
Let’s say we scaled your solution up ridiculously: much more training data, a much larger model, and longer training. What do you think would happen?
I think it could scale quite a bit, especially if we scale the latent space. One limitation is how much you can actually search in that space.
I would expect the space to become less linear, less interpolative, and less smooth. If you end up scaling the tasks and problems, the space would become harder to search.
It will depend on how much search you can perform during training. You would probably need to scale the encoder, decoder, latent space, and search through the latent space. That would be quite costly.
There are better ways to iterate on this idea. I believe that coming up with a small representation of whatever task or query you have can be a very useful way to perform synthesis, adapt, and deal with epistemic uncertainty.
I don’t believe we can currently estimate epistemic uncertainty, but searching through this space could help resolve some of that uncertainty.
That’s very insightful. You’re saying that when we scale this up, the latent space might become less interpretable. Could we do meta-optimization, use a different type of latent space, or perhaps use a topological space or a graph representation? I’m just spitballing, but what else could we do?
I don’t think it’s only about scaling the latent dimension of the space, which has been the approach with large models. From my intuition, what’s happening in large language models is that they have intricate, highly sparse, high-dimensional vector functions that can be recombined in some narrow way.
The space is very rich, but also very local. You’re trying to find an abstraction locally that is slightly better. I don’t think that gives you all the benefits of composition and out-of-distribution generalization.
I’m a big believer in trying to find small representations that can explain your outputs. For natural language, that would be a query and the answer to the query.
How you build these spaces and how you search through them are open questions. I’ve seen a lot of interest in this direction lately, so it’s very exciting.
Congratulations on the work and on the recognition from Chollet. It’s a completely different approach from the others—a very interesting one indeed. Keep up the good work, and thank you for coming on.
Thank you so much. Thank you for having me.