How to derive Variational Free Energy: A Software Engineer's Guide - Part 1.
The static case: one observation, one hidden cause.
Scope: This derives the simplest case from Bogacz (2017): inferring a single fixed quantity from a single observation, with a linear-Gaussian model and fixed precisions. No dynamics, no time, no action yet — those will come later in the series. (For the mathematically inclined: this is the Laplace-approximated free energy, where the approximate posterior collapses to a point estimate — which is why the KL/entropy term you may have seen elsewhere doesn't appear here.)
Skip my ramblings, this isn't some hippy cookbook.
1. Introduction
If you've ever been like me, lying awake at night contemplating how to implement continuous generative models within the framework of Active Inference, then I'm genuinely surprised because I thought I was weird. In my spare time I am building Active Inference POMDP models with my partner in crime Kev, mostly using the pymdp python toolbox. Although this toolbox is brilliant and has been a joy to use, it currently doesn't support the creation of Continuous Generative Models (CGMs). This makes my current ultimate goal of creating a Mixed Generative Model (MGM) somewhat more difficult. To do this I need to understand how the fundamental equations of these models are derived, hence this post and subsequent posts. Due to its nature, there isn't a large amount of easily accessible content on Active Inference in general, so whilst learning how to code this stuff, I thought I might as well document it as I go.
So what is Variational Free Energy, before we drown in Greek? It is a mathematically tractable upper bound on sensory "surprise" (or negative log-evidence).
Extra: Variational Free Energy in English
In simpler terms: biological systems cannot directly measure how objectively surprising their environment is, because they do not have direct access to the hidden states of the world. Instead, they compute and minimise Variational Free Energy. By minimising VFE, an agent indirectly minimises its sensory surprise.

In this derivation we actually maximise negative VFE instead of the classic literature move of minimising positive VFE. They are approximately identical for the contents of this post.
This post follows the structure of Rafal Bogacz's 2017 paper, "A tutorial on the free-energy framework for modelling perception and learning". Brilliant, but dense enough at times to make this pleb software engineer's to make your eyes water. My job is to slow it right down, swap the heavy notation for runnable Python and plain variable names, and generally make it survivable to an none-academic. It's also the first in a series I'm writing to drag Active Inference into the engineering spotlight.
Note: There will be a lot of acronyms and symbols to deal with in this post. There is an appendix for both.
Our goal is to go from first principles, based on a simple organism, to this monstrosity:
It looks frightening now. By the end it'll look like an old friend who happens to owe you money. The whole thing is just two competing pulls wearing the latest Greek-algebra summer collection, and once you've seen what each piece is doing, the rest is bookkeeping. We'll build it up one honest step at a time, the same way I did in my notebook (mistakes and false starts included, because pretending I got it first try would be dishonest and hopefully you can laugh at me to give your brain a break).
Here's the roadmap. We set up a simple organism trying to guess one number, in this case the size of a food item. We write down the textbook-correct way to solve it, notice that the textbook-correct way involves an integral nobody wants to compute, and then quietly walk away from it. We then do the thing the brain (allegedly) actually does, which is find the single best guess instead of the whole distribution. That shortcut is what gives us Free Energy. Then we differentiate it, get an update rule, and stare at the two terms until they make sense.
2. The Simplest Case
2.1 Setting up the problem
Bogacz starts with a single value of a single variable being inferred by a single observation. One number in, one number out.
The story is this. We have a simple animal (Bogacz uses "animal" so I will too to keep it consistent, but I find "animal" does a lot of heavy lifting) that wants to infer the size of a food item. We'll call this size . The animal can't see size directly. What it can do is sense light intensity, because it has a single light-sensitive receptor, and bigger things reflect more light. The sensed light intensity is , and crucially has noise.
So our two characters:
- — the size of the food (the hidden cause we want to infer)
- — the noisy light intensity the receptor actually reports (the observation)
Now we need to connect them. Light reflected off an object scales with its area, so Bogacz picks a nice clean non-linear function relating average light intensity to size:
Keep an eye on that . It's the animal's internal model of "how my sensor responds to the world".
Next, the noise. The animal's receptor doesn't report exactly; it reports something scattered around it. Bogacz assumes the perceived light intensity is normally distributed with mean and variance . In symbols, the likelihood of an observation given a size is:
where is the density of a normal distribution which is shown with this formula:
Read as "the bell curve evaluated at , for a distribution whose mean is and whose variance is ". The semicolon is just separating the value we're plugging in (on the left) from the two settings that define the curve's shape and position (on the right). I'll use the notation throughout — whenever you see it, it's the same formula, just with different things slotted into the blanks.
Side note for Bogacz, and worth repeating here: a Gaussian isn't really the right distribution for light intensity, because a Gaussian happily applies probabilities to negative numbers and there's no such thing as negative brightness. But it's clean, it's tractable, and it makes the maths behave, so let's just agree to look the other way, for now. This is a recurring theme in modelling: pick the distribution that lets you finish the derivation, apologise in the footnotes.
Extra: Why is the mean just g(v)?
Before moving on I want to justify that "mean " claim, because I'm not a professional academic and it wasn't intuitive to me.
The noisy observation is really the clean prediction plus some noise, which we notate with an :
Don't be fooled by the , it means the same thing as the notation, just with the distribution named outright — where leaves the distribution generic, is us saying explicitly "and the distribution here is the Normal (Gaussian) one."
For a fixed , the term is just a constant. There's a handy fact about Gaussians: Adding a constant to a Gaussian random variable just shifts the mean by that constant. Formally, if , then .
So:
which is exactly equation (1). In plain English: "a normal distribution over the variable , with mean and variance ". Good. Moving on.
The last important piece we need for setup is the idea of prior knowledge. A habitual expectation of how big food usually is, before it sees anything at all. This is the bit that makes it Bayesian rather than just "trust the wonky sensor blindly".
For simplicity we assume the animal expects size to be normally distributed too, with mean and variance , where the subscript stands for prior:
So now we have both parts that Bayes needs: a likelihood (how observations relate to causes) and a prior (what we believe before looking).
2.2 Stating the result
Here's where we're headed, so you know what the destination looks like before we start the journey.
The exact answer to "given what I saw, how likely is each possible size?" is the posterior , delivered by Bayes' theorem:
That's the honest, complete, textbook-correct answer. And as we'll see in a second, it contains a landmine in the denominator. So instead of computing the whole posterior, we'll settle for finding the single most likely size (the peak of the posterior) which we'll call . The quantity we maximise to find turns out to be (the log of) the numerator of Bayes' rule, and that quantity is what gets called Free Energy:
Maximise , find your best guess. If your head hurts, don't worry, mine did too — first deriving it myself, then trying to think of how to write about how I derived it. I've got double your headaches...loser.
To make "the peak of the posterior" concrete, here's that curve actually plotted — the exact posterior for the food-size example, computed by solving Bogacz Exercise 1 (Python in Appendix C). This is the whole target: the single value of sitting under the peak is , our best-guess size, and everything from here on is about reaching that peak without computing the whole curve.

2.3 Derivation
The exact solution, and the integral that we run away from
To work out how likely the range of different sizes is, given the observed input , we use Bayes' theorem, equation (4) above. The numerator, , is just prior multiplied by likelihood. The problem is the denominator, .
The denominator is the normaliser. It guarantees that the posterior probabilities over all possible sizes integrate to 1. To achieve it, you have to integrate the numerator over every possible size:
Extra: Why do we integrate the numerator to find ?
is the probability of receiving that amount of light across every possible cause (in this case size of food ). Importantly, the numerator only covers a single food size. The denominator runs across every possible food size, hence the integration. It appears circular, but it's not.
This is the landmine. For our manufactured simple case with friendly Gaussians, it's doable. Try Exercise 1 in the Bogacz paper; the Python solution is in Appendix C if you get stuck. In any realistic model (many variables, non-linear ) this integral is intractable (meaning very difficult or impossible to control, manage, or solve). It's the wall the entire free-energy framework exists to climb over. So rather than smashing our heads against it, we change the question.
Finding the most likely value (the MAP shortcut)
Instead of finding the whole curve , we go after the single value of that maximises . We call that value , our best-guess size, and we call the posterior probability density at that point.
Bogacz notes (and it's a fairly heavy assumption) that it's reasonable to think the brain represents, at any given moment, only the most likely values of features rather than full distributions. The single best guess, not the entire belief. Bogacz uses binocular rivalry as evidence of this assumption. If you don't know what that is, expand the section below.
Extra: Binocular rivalry
Here's a rundown of the experiment. You show each eye a different image, say, vertical stripes to the left eye and horizontal stripes to the right eye, at the same time. Crucially, the two images cannot both be true of the same patch of the world. Your brain is now stuck with contradictory evidence and has to make sense of it.
Now, if your brain were tracking the full distribution you'd expect to perceive some sensible average of the two. A blurry grey chequerboard.
That is not what happens. What people actually report is that perception flips. For a few seconds you see only the vertical stripes, then (without doing anything) it switches and you see only the horizontal stripes, then back again, continuously. You never see the averaged image. Your brain literally picks a winner, commits to it fully, then changes its mind.
That flipping is the tell. It's exactly what you'd expect from a system that represents the single most likely interpretation rather than the whole probability distribution. When the evidence is genuinely ambiguous, there are two roughly-equally-good "best guesses", and the brain ping-pongs between them. It only ever holds one at a time.
This next bit is the pivot the whole derivation hinges on, so I'm going to lay it out slowly. Here's the logic, lightly paraphrased and then broken down:
We look for the value which maximises the posterior . By equation (4), that posterior depends on a ratio of two quantities, but the denominator does not depend on . Therefore the value of that maximises the posterior is the same value that maximises the numerator. We denote the logarithm of that numerator by (it
's related to negative free energy, as we'll see).
Let me unpack that, because it's doing three separate clever things at once:
-
"We look for a value which maximises..." — The animal wants its single best guess for food size. This is the peak of the curve from before. Instead of calculating the whole curve, it just wants to know where the top is.
-
"The posterior depends on a ratio, but the denominator doesn't depend on ." — Bayes' rule is
prior x likelihood / normaliser. That normaliser, , is the integral. But it does not depend on the guess . It's a fixed number once is observed. -
"Thus the value which maximises it is the same one which maximises the numerator." — This is the crux. Dividing everything by the same constant just rescales the curve; it doesn't move where the peak is. Like that one friend at a party that's a downer. They don't change the location of the party but they bring everybody down. So we can completely ignore that friend, I mean, the hard-to-compute denominator , and just maximise the numerator, keeping the party alive. The location of the maximum is unchanged.
Then one final convenience: we take the log of the numerator before maximising. This is safe because log is monotonic (a fancy way of saying it never reorders things, so it also doesn't move the peak). Like everyone wearing a hat at the party...something something...worn out party analogy. If then , always. Taking logs also turns all our products into sums (via ), which is going to make the calculus nicer in about thirty seconds.
In summary: the denominator is fixed scaling that does not affect where the peak is, so we throw it away. We take the log of what's left (products become sums, peak stays put), and we call the result , for Free Energy, and we maximise that.
That gives us equation (7):
Two terms: the log-prior and the log-likelihood. That's the whole objective. Now we substitute in the actual Gaussians and grind.
Substituting in the Gaussians (a wall of maths, honestly)
The plan: Take equation (7), sub in the prior (3) and the likelihood (1), each of which is the Gaussian density (2), and simplify using log rules until something clean falls out. Bogacz basically says "do this" and then shows a wall of algebra. So here's that wall. Feel free to try it out yourself.
Starting from (7) and substituting the two Gaussian densities:
Each of a product splits into a log of the front fraction plus a log of the exponential. The log of an exponential is just its exponent, so the and annihilate and leave the quadratic behind:
Now look at those two leftover log terms, and . We're eventually going to differentiate with respect to . Neither of those terms contain a . So when we differentiate, they vanish.
So Bogacz groups them into a single constant term :
Extra: If you want to see where that and the bits come from
, and the part is a pure constant that disappears into . The terms stick around in the expression but, since the variances are fixed in our case, they're also constant with respect to . The full expanded form, with the -logs written out properly, lives in Appendix A for everyone who wants every step.
That's our objective function. A log-prior penalty, a log-likelihood penalty, and a constant we'll never think about again. Well done, go grab a drink.
Confession: the first time I did this I tried to expand all the terms out fully — splitting them into , then into , and so on, dragging every along for the ride. I filled half a page, wrote "Wrong, started again" in big letters, and underlined it. It wasn't actually wrong, it was just a waste of effort, because all those bits are about to get swept into a constant anyway. Learn from my pain: don't expand things you're about to throw away.
Differentiating to get the update rule
We've got as a function of our guess . To find the that maximises it, we need the gradient . The constant goes away. The two terms also go, since they don't contain a either. That leaves the two quadratics.
Differentiating with respect to gives , which flips to .
The second term, , needs the chain rule, because is a function tucked inside another function.
The inner function is , so its derivative gets pulled out front. Working it through gives .
Putting both together:
Quick note on that — it matters enormously. Remember the animal's sensor model was . So:
This is the only place the specific shape of the sensor enters the update. Swap in a different and only changes — everything else in the equation is structural.
Gradient ascent: letting the guess move
Equation (9) tells us the slope of at our current guess. To actually find the peak, we apply gradient ascent. Bogacz writes it about as simple as it can be written:
In English this means "let your guess change over time at the rate of the gradient". If slopes upwards at your current guess, is positive and moves in the upward direction. At the peak, , so , and the guess stops moving. It's settled on the best estimate.

Worth flagging: this is gradient ascent, not descent — notice there's no minus sign. But hang on, doesn't everyone bang on about minimising free energy? They do, and there's no contradiction; it's a naming mismatch that we addressed in an earlier paragraph. The quantity we've been building, , came straight out of logging the numerator of Bayes' rule, so it's a goodness score we want to push up (most probable guess = top of the hill). The free energy the literature tells you to minimise is defined the other way up, as a proxy for surprise, which you obviously want down. The two are just negatives of each other: . Climbing our hill is descending their valley. Bogacz didn't flip his terms to match the convention because his derivation hands him this version for free; negating everything by hand just to agree with a sign convention would've added minus signs to every equation that follows, for zero benefit. So: we ascend, and we call it negative free energy to keep our consciences clear.
The two terms and their meaning
We're going to take a break from the algebra for a second and go back to the narrative. The update to is driven by two terms in equation (9). The first pulls the guess towards the prior; the second pulls it according to the sensory input. Let's take a minute to recap each.
Term 1 — the pull towards prior expectation
- — the prior mean. What the animal expected the size to be before "seeing" anything. It's habitual beliefs.
- — the current guess.
- — how far the current guess is from what we expected. A discrepancy, sometimes referred to as a distance in the literature.
- — the prior variance, i.e. the uncertainty in that prior expectation.
So Term 1 says: if your current guess has drifted away from your habitual belief, get pulled back towards it. The strength of that pull is scaled with how confident the prior is. A tight prior (small ) has a larger influence; a vague prior (big ) has less influence.
Term 2 — the pull towards the observation.
- — the actual sensory observation. The light intensity the animal really observed.
- — what the animal would expect to observe if its current guess were true. The sensory model is .
- — the prediction error. What it saw minus what it predicted it would see given its best guess. In predictive coding, this discrepancy between reality and expectation is the signal that drives learning.
- — the sensory variance, i.e. how noisy the sensor is. Large -> the sensor is bad, low trust; small -> the sensor is good, high trust.
- — the chain-rule factor ( in our case). It does a units conversion: the prediction error lives in observation units (luminance, for example), and translates it into a correction in size units (, for example).
So Term 2 says: look at the gap between what you observed and what you predicted, scale it down if your sensor is unreliable, convert it into size-language, and influence your guess in that direction.
That's the whole engine. The animal's belief is a tug-of-war between "this is what I usually expect" (the prior) and "this is what I'm actually seeing right now" (the evidence), each weighted by how much it trusts the respective source. Inference is just letting that tug-of-war settle. Stitch the two terms back together and you've got the monstrosity from the very top of the post:
Appendix A — The full algebra
For completeness, here's the full expansion I shortcut in the main text. If you want every accounted for rather than swept into , this is for you.
Start from equation (7) with both Gaussians substituted in. Take one term at a time. Using on the prior term:
Now expand that front log. Rewrite the fraction as a power, , and pull the exponent out with :
Split the product inside with again:
The piece contains no and no variance worth tracking — it's a pure constant. The same expansion applies identically to the likelihood term with and in place of and . Doing both and collecting:
The two terms are constants. Roll them — and only them — into , factor out the , and you land exactly on the boxed result from the main text:
The moral, again: the -logs survive the simplification but disappears under differentiation (they're constant in when precisions are fixed), and the junk was never worth carrying. Expand only what you need.
Appendix B — Bogacz Exercises
Bogacz peppers the tutorial with exercises, and they're genuinely worth doing. They're the difference between following along and actually building the thing. I left them out of the main flow to not repeat their work and to keep this easier to read. But I would recommend taking a look at them yourself. They live in the relevant chapters of the tutorial:
- Exercise 1 (Chapter 2) — plotting the exact posterior for the food-size example. This is the curve whose peak we spent the whole post chasing.
- Exercise 2 (Chapter 2.2 / 2.3) — implementing the gradient ascent on and watching it converge.
My solutions, written in Python, can be seen in Appendix C
Appendix C - Python solutions to Bogacz Exercises
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
def exercise_1():
v_p = 3
sigma_p = 1
sigma_u = 1
u = 2
vrange = np.arange(0.01,5,0.01)
numerator = norm.pdf(vrange, v_p, sigma_p) * norm.pdf(u, vrange**2, sigma_u) # Posterior = Prior * likelihood
denominator = sum(numerator*0.01)
p = numerator/denominator
plt.plot(vrange, p)
plt.ylim(ymin=0)
plt.xlabel("v (size)")
plt.ylabel("posterior probability")
plt.show()
exercise_1()
import matplotlib.pyplot as plt
import numpy as np
def exercise_2():
v_p = 3
sigma_p = 1
sigma_u = 1
u = 2
phi=[]
phi: list[int|float] = [v_p]
Dt = 0.01
max_t = 5
n_steps = int(max_t/Dt)
for _ in range(n_steps):
current = phi[-1]
prior_expectation_pull = (v_p - current)/sigma_p
observation_pull = ((u - current**2)/sigma_u)*2*current
# Using Euler's method phi(t+Dt) = phi(t) + Dt*(dF/dphi)
phi.append(current + Dt*(prior_expectation_pull+observation_pull))
time = np.arange(len(phi)) * Dt
plt.plot(time, phi, "k")
plt.ylim(ymin=0)
plt.xlabel("time")
plt.ylabel(r"$\phi$ (guess of v)")
plt.show()
exercise_2()
Notation Summary
As promised up top, here's the appendix for the symbols and acronyms. Skim it once now if you like, or just flick back whenever a piece of Greek catches you out.
Acronyms
| Acronym | Stands for |
|---|---|
| VFE | Variational Free Energy — the quantity this whole post derives |
| ActInf | Active Inference (my abbreviation, to avoid clashing with "AI") |
| POMDP | Partially Observable Markov Decision Process |
| CGM | Continuous Generative Model |
| MGM | Mixed Generative Model |
| MAP | Maximum A Posteriori — the single most-likely estimate (our ) |
| pymdp | The Python active-inference toolbox I build models with |
Symbols
| Symbol | Meaning |
|---|---|
| True size of the food item (the hidden cause we want to infer) | |
| Observed (noisy) light intensity — the sensory input | |
| The animal's current best guess at the size; the value that maximises the posterior | |
| Rate of change of the guess over time (gradient ascent) | |
| The sensory/generative model relating size to expected light intensity; here | |
| Derivative of the sensory model; here . The chain-rule factor converting prediction error from observation units to size units | |
| Prior mean — the size the animal habitually expects before observing anything | |
| Prior variance — uncertainty in the prior expectation | |
| Sensory variance — how noisy the sensor is (large = untrustworthy, small = reliable) | |
| Sensory noise, | |
| Density of a normal distribution with mean and variance | |
| Prior over size | |
| Likelihood of an observation given a size | |
| Posterior over size given the observation (what Bayes gives us) | |
| Model evidence / normaliser — the intractable integral we avoid | |
| Free Energy — the log-numerator of Bayes' rule that we maximise |
References
-
Bogacz, R. (2017). A tutorial on the free-energy framework for modelling perception and learning. Journal of Mathematical Psychology, 76, 198–211.
-
Da Costa, L., Parr, T., Sajid, N., Veselic, S., Neacsu, V., & Friston, K. (2020). Active inference on discrete state-spaces: A synthesis. Journal of Mathematical Psychology, 99, 102447.
-
Smith, R., Friston, K. J., & Whyte, C. J. (2022). A step-by-step tutorial on active inference and its application to empirical data. Journal of Mathematical Psychology, 107, 102632.