Author’s Note: Most of the posts in this sequence are essentially a log of work-in-progress. This post is intended as a more presentable (“public”) and higher-confidence (“static”) write-up of some formalizations of abstraction. Much of the material has appeared in other posts; the first two sections in particular are drawn almost verbatim from the opening “What is Abstraction?” post.
Let's start with a few examples (borrowed from here) to illustrate what we're talking about:
The general pattern: there’s some ground-level “concrete” model (or territory), and an abstract model (or map). The abstract model throws away or ignores information from the concrete model, but in such a way that we can still make reliable predictions about some aspects of the underlying system.
Notice that the predictions of the abstract models, in most of these examples, are not perfectly accurate. We're not dealing with the sort of "abstraction" we see in e.g. programming or algebra, where everything is exact. There are going to be probabilities involved.
In the language of embedded world-models, we're talking about multi-level models: models which contain both a notion of "table", and of all the pieces from which the table is built, and of all the atoms from which the pieces are built. We want to be able to use predictions from one level at other levels (e.g. predict bulk material properties from microscopic structure and/or macroscopic measurements, or predict from material properties whether it's safe to sit on the table), and we want to move between levels consistently.
To repeat the intuitive idea: an abstract model throws away or ignores information from the concrete model, but in such a way that we can still make reliable predictions about some aspects of the underlying system.
So to formalize abstraction, we first need some way to specify which "aspects of the underlying system" we wish to predict, and what form the predictions take. The obvious starting point for predictions is probability distributions. Given that our predictions are probability distributions, the natural way to specify which aspects of the system we care about is via a set of events or logic statements for which we calculate probabilities. We'll be agnostic about the exact types for now, and just call these "queries".
That leads to a rough construction. We start with some low-level model ML and a set of queries Q. From these, we construct a minimal high-level model MH by keeping exactly the information relevant to the queries, and throwing away all other information. By the minimal map theorems, we can represent MH directly by the full set of probabilities P[Q|ML]; MH and P[Q|ML] contain exactly the same information. Of course, in practical examples, the probabilities P[Q|ML] will usually have some more compact representation, and MH will usually contain some extraneous information as well.
To illustrate a bit, let's identify the low-level model, class of queries, and high-level model for a few of the examples from earlier.
Already with the second two examples there seems to be some "cheating" going on in the model definition: we just define the query class as all the events/logic statements whose probabilities change based on the information in the map. But if we can do that, then anything can be a "high-level map" of any "low-level territory", with the queries Q taken to be the events/statements about the territory which the map actually has some information about - not a very useful definition!
In order for abstraction to actually be useful, we need some efficient way to know which queries the abstract model can accurately answer, without having to directly evaluate each query within the low-level model.
In practice, we usually seem to have a notion of which variables are “far apart”, in the sense that any interactions between the two are mediated by many in-between variables.
The mediating variables are noisy, so they wipe out most of the “fine-grained” information present in the variables of interest. We can therefore ignore that fine-grained information when making predictions about things far away. We just keep around whatever high-level signal makes it past the noise of mediating variables, and throw out everything else, so long as we’re only asking questions about far-away variables.
An example: when I type “4+3” in a python shell, I think of that as adding two numbers, not as a bunch of continuous voltages driving electric fields and current flows in little patches of metal and doped silicon. Why? Because, if I’m thinking about what will show up on my monitor after I type “4+3” and hit enter, then the exact voltages and current flows on the CPU are not relevant. This remains true even if I’m thinking about the voltages driving individual pixels in my monitor - even at a fairly low level, the exact voltages in the arithmetic-logic unit on the CPU aren’t relevant to anything more than a few microns away - except for the high-level information contained in the “numbers” passed in and out. Information about exact voltages in specific wires is quickly wiped out by noise within the chip.
Another example: if I’m an astronomer predicting the trajectory of the sun, then I’m presumably going to treat other stars as point-masses. At such long distances, the exact mass distribution within the star doesn’t really matter - except for the high-level information contained in the total mass, momentum and center-of-mass location.
Formalizing this in the same language as the previous section:
Mathematically: P[Y|X]=P[Y|f(X)] for any Y which is “not too close” to X - i.e. any Y which do not overlap with Z (or with X itself). Our high-level model replaces X with f(X), and our set of valid queries Q is the whole joint distribution of Y given X.
Now that we have two definitions, it’s time to start the Venn diagram of definitions of abstraction.
So far, we have:
The definition in the previous section just focuses on abstracting a single variable X. In practice, we often want to take a system-level view, abstracting a whole bunch of low-level variables (or sets of low-level variables) all at once. This doesn’t involve changing the previous definition, just applying it to many variables in parallel.
Rather than just one variable of interest X, we have many low-level variables (or non-overlapping sets of variables) XL1…XLn and their high-level summaries XH1:=f1(X1)…XHn:=fn(Xn). For each of the XLi, we have some set Zi of variables “nearby” XLi, which mediate its interactions with everything else. Our “far-away” variables Y are now any far-away X’s, so we want
for any sets of indices S and T which are “far apart” - meaning that XLT does not overlap any XLS or ZS.
(Notation: I will use lower-case indices like Xi for individual variables, and upper-case indices like XS to represent sets of variables. I will also treat any single index interchangeably with the set containing just that index.)
For instance, if we’re thinking about wires and transistors on a CPU, we might look at separate chunks of circuitry. Voltages in each chunk of circuitry are XLi, and XHi summarizes the binary voltage values. Zi are voltages in any components physically close to chunk i on the chip. Anything physically far away on the chip will depend only on the binary voltage values in the components, not on the exact voltages.
The main upshot of all this is that we can rewrite the math in a cleaner way: as a (partial) factorization. Each of the low-level components are conditionally independent given the high-level summaries, so:
This condition only needs to hold when S picks out indices such that ∀i≠j∈S:Zi∩Xj=∅ (i.e. we pick out a subset S of the XLi’s such that no two are “close together”). Note that we can pick any set of indices S which satisfies this condition - so we really have a whole family of factorizations of marginal distributions in which no two variables are “close together”. See the appendix to this post for a proof of the formula.
In English: any set of low-level variables XLS which are all “far apart” are independent given their high-level summaries XHS. Intuitively, the picture looks like this:
We pick some set of low-level variables XL1…XLk which are all far apart, and compute their summaries XH1=f1(XL1),…,XHk=fk(XLk). By construction, we have a model in which each of the high-level variables is a leaf in the graphical model, determined only by the corresponding low-level variables. But thanks to the abstraction condition, we can independently swap any subset of the summaries with their corresponding low-level variables - assuming that all of them are “far apart”.
Returning to the digital circuit example: if we pick any subset of the wires and transistors on a chip, such that no two are too physically close together, then we expect that their exact voltages are roughly independent given the high-level summary of their digital values.
We’ll add this to our Venn diagram as an equivalent formulation of the previous definition.
I have found this formulation to be the most useful starting point in most of my own thinking, and it will be the jumping-off point for our last two notions of abstraction in the next two sections.
So far we’ve only talked about “queries” on the joint distribution of variables. Another natural step is to introduce causal structure into the low-level model, and require interventional queries to hold on far apart variables.
There are some degrees of freedom in which interventional queries hold on far apart variables. One obvious answer is “all of them”:
… with the same conditions on S as before, plus the added condition that the indices in S and T also be far apart. This is the usual requirement in math/programming abstraction, but it’s too strong for many real-world applications. For instance, when thinking about fluid dynamics, we don’t expect our abstractions to hold when all the molecules in a particular cell of space are pushed into the corner of that cell. Instead, we could weaken the low-level intervention to sample from low-level states compatible with the high-level intervention:
We could even have low-level interventions sample from some entirely different distribution, to reflect e.g. a physical machine used to perform the interventions.
Another post will talk more about this, but it turns out that we can say quite a bit about causal abstraction while remaining agnostic to the details of the low-level interventions. Any of the above interventional query requirements have qualitatively-similar implications, though obviously some are stronger than others.
In day-to-day life, causal abstraction is arguably more common than non-causal. In fully deterministic problems, validity of interventional queries is essentially the only constraint (though often in guises which do not explicitly mention causality, e.g. functional behavior or logic). For instance, suppose I want to write a python function to sort a list. The only constraint is the abstract input/output behavior, i.e. the behavior of the designated “output” under interventions on the designated “inputs”. The low-level details - i.e. the actual steps performed by the algorithm - are free to vary, so long as those high-level interventional constraints are satisfied.
This generalizes to other design/engineering problems: the desired behavior of a system is usually some abstract, high-level behavior under interventions. Low-level details are free to vary so long as the high-level constraints are satisfied.
Finally, one important special case. In math and programming, we typically use abstractions with sharper boundaries than most of those discussed here so far. Prototypical examples:
In these cases, there’s no noisy intermediate variables, and no notion of “far away” variables. There’s just a hard boundary: the internal details of high-level abstract objects do not interact with things of interest “outside” the object except via the high-level summaries.
We can easily cast this as a special case of our earlier notion of abstraction: the set of noisy intermediate variables Zi is empty. The “high-level summary” XHi of the low-level variables XLi contains all information relevant to any variables outside of XLi themselves.
Of course, exact abstraction overlaps quite a bit with causal abstraction. Exact abstractions in math/programming are typically deterministic, so they’re mainly constrained by interventional predictions rather than distributional predictions.
We started with a very general notion of abstraction: we take some low-level model and abstract it into a high-level model by throwing away information in such a way that we can still accurately answer some queries. This is extremely general, but in order to actually be useful, we need some efficient way to know which queries are and are not supported by the abstraction.
That brought us to our next definition: abstraction keeps information relevant to “far away” variables. We imagine that interactions between the variable-to-be-abstracted X and things far away are mediated by some noisy “nearby” variables Z, which wipe out most of the information in X. So, we can support all queries on things far away by keeping only a relatively small summary f(X).
Applying this definition to a whole system, rather than just one variable, we find a clean formulation: all sets of far-apart low-level variables are independent given the corresponding high-level summaries.
Next, we extended this to causal abstraction by requiring that interventional queries also be supported.
Finally, we briefly mentioned the special case in which there are no noisy intermediate variables, so the abstraction boundary is sharp: there’s just the variables to be abstracted, and everything outside of them. This is the usual notion of abstraction in math and programming.
We start with two pieces. By construction, XHi is calculated entirely from XLi, so
… without any restriction on which subsets of the variables we look at. Then we also have the actual abstraction condition
… as long as XLT does not overlap ZS or XLS.
We want to show that
… for T any set of non-nearby variables (i.e. ∀i,j∈T:Xj∩(Zi∪Xi)=∅). In English: sets of far-apart low-level variables are independent given their high-level counterparts.
Let’s start with definitions of “far-apart” and “nearby”, so we don’t have to write them out every time:
As before, I will use capital letters for sets of indices and lower-case letters for individual indices, and I won’t distinguish between a single index and the set containing just that index.
With that out of the way, we’ll prove a lemma:
… for any S far apart from S′, both far apart from T and T′ (though T and T′ need not be far apart from each other). This lets us swap high-level with low-level given variables as we wish, so long as they’re all far apart from each other and from the query variables. Proof:
P[XLT,XHT|XLS]=P[XLT|XLS]P[XHT|XLT] (by construction)
=P[XLT|XHS]P[XHT|XLT] (by abstraction)
=P[XLT,XHT|XHS] (by construction)
By taking T←(T∪T′) and then marginalizing out unused variables, this becomes
That’s the first half of our lemma. Other half:
P[XLT,XHT′|XLS,XHS′]=P[XHS′|XLS]−1P[XLT,XHT′,XHS′|XLS] (by Bayes)
=P[XHS′|XHS]−1P[XLT,XHT′,XHS′|XHS] (by first half)
=P[XLT,XHT′|XHS∪S′] (by Bayes)
That takes care of the lemma.
Armed with the lemma, we can finish the main proof by iterating through the variables inductively:
P[XLT∪i,XHT∪i|XHS]=P[XLi,XHi|XHS]P[XLT,XHT|XHS,XLi,XHi] (by Bayes)
=P[XHi|XLi]P[XLi|XHS]P[XLT,XHT|XHS,XLi] (by construction)
=P[XHi|XLi]P[XLi|XHS]P[XLT,XHT|XHS∪i] (by lemma)
=(P[XLi|XHi]P[XHi]P[XLi])(P[XHS|XLi]P[XLi]P[XHS])P[XLT,XHT|XHS∪i] (by Bayes)
=P[XLi|XHi](P[XHS|XHi]P[XHi]P[XHS])P[XLT,XHT|XHS∪i] (by lemma & cancellation)
=P[XLi|XHi]P[XHi|XHS]P[XLT,XHT|XHS∪i] (by Bayes)
Here i, S, and T are all far apart. Starting with empty S and applying this formula to each variable i, one-by-one, completes the proof.
Planned summary for the Alignment Newsletter:
If we are to understand embedded agency, we will likely need to understand abstraction (see <@here@>(@Embedded Agency via Abstraction@)). This post presents a view of abstraction in which we abstract a low-level territory into a high-level map that can still make reliable predictions about the territory, for some set of queries (whether probabilistic or causal).For example, in an ideal gas, the low-level configuration would specify the position and velocity of _every single gas particle_. Nonetheless, we can create a high-level model where we keep track of things like the number of molecules, average kinetic energy of the molecules, etc which can then be used to predict things like pressure exerted on a piston.Given a low-level territory L and a set of queries Q that we’d like to be able to answer, the minimal-information high-level model stores P(Q | L) for every possible Q and L. However, in practice we don’t start with a set of queries and then come up with abstractions, we instead develop crisp, concise abstractions that can answer many queries. One way we could develop such abstractions is by only keeping information that is visible “far away”, and throwing away information that would be wiped out by noise. For example, when typing 3+4 into a calculator, the exact voltages in the circuit don’t affect anything more than a few microns away, except for the final result 7, which affects the broader world (e.g. via me seeing the answer).If we instead take a systems view of this, where we want abstractions of multiple different low-level things, then we can equivalently say that two far-away low-level things should be independent of each other _when given their high-level summaries_, which are supposed to be able to quantify all of their interactions.
I really like the concept of abstraction, and think it is an important part of intelligence, and so I’m glad to get better tools for understanding it. I especially like the formulation that low-level components should be independent given high-level summaries -- this corresponds neatly to the principle of encapsulation in software design, and does seem to be a fairly natural and elegant description, though of course abstractions in practice will only approximately satisfy this property.