Actor Critic using Kronecker-Factored Trust Region (ACKTR)
ACKTR scales natural policy gradients to deep networks by approximating the massive Fisher Information Matrix as Kronecker products of small activation and gradient covariance matrices.
Why Does This Exist?
Standard first-order policy gradient methods, like Advantage Actor-Critic (A2C), update neural network parameters along the steepest Euclidean ascent direction. Because parameters govern policy distributions non-linearly, a uniform step in weight space can collapse the policy distribution from exploratory to near-deterministic in a single update.
Natural policy gradients solve this instability by measuring distance in probability distribution space via the Fisher Information Matrix (FIM), denoted . The natural gradient direction guarantees that step sizes represent invariant Kullback-Leibler (KL) divergences regardless of network parameterization.
However, for a network with parameters, the FIM has dimension ( elements). Directly storing and inverting demands floating-point operations per step, which is computationally impossible. While Trust Region Policy Optimization (TRPO) avoids constructing by approximating with iterative Conjugate Gradient steps, it requires repeated Hessian-vector passes and only optimizes the actor, leaving the critic to first-order Adam or RMSprop.
Actor Critic using Kronecker-Factored Trust Region (ACKTR) was introduced by Wu et al. (2017) to make exact second-order optimization practical for both the actor and the critic. By applying Kronecker-factored approximate curvature (K-FAC), ACKTR decomposes each layer's Fisher block into two compact covariance matrices: one over input activations and one over output pre-activation gradients. Inverting these tiny matrices reduces computational complexity from to , achieving two to three times the sample efficiency of TRPO and A2C without iterative conjugate gradient loops.
Think of It Like This
Measuring a Building by Floor Plan and Ceiling Height
Imagine you manage an auditorium renovation and need to adjust the acoustic damping of every seat. The auditorium has rows and columns, creating 10,000 seat adjustments ().
A naive acoustician attempts to measure the pairwise acoustic interaction between every individual seat and every other seat across the room. That requires recording a matrix with 100,000,000 cross-correlations, taking days to calculate and calibrate.
A master acoustic engineer notices the geometry: sound waves disperse independently along the row axis (input sound propagation) and along the column axis (vertical ceiling reflection). Rather than measuring 100 million pairwise interactions, she measures a row-covariance matrix and a column-covariance matrix .
To compute the necessary damping correction for any seat, she inverts the row matrix and the column matrix independently, then sandwiches the target adjustment between them: . She turns an intractable 100-million-parameter inversion problem into two split calibrations that execute in seconds.
Where the analogy stops: In a real auditorium, physical sound interactions across rows and columns can have higher-order cross-dependencies (e.g., sound bouncing diagonally off corners). K-FAC explicitly treats activations and output gradients as statistically independent when forming the expectation , accepting a slight structural approximation in exchange for massive computational tractability.
How It Actually Works
Natural Gradients and Kronecker-Factored Approximate Curvature
Consider a fully-connected layer in the actor or critic network mapping input activation to pre-activation outputs :
Let denote the reinforcement learning surrogate objective, and define the pre-activation gradient . The gradient with respect to the weight matrix is an outer product:
Vectorizing the weight gradient gives:
The Fisher Information Matrix block corresponding to layer weights is the expectation of the outer product of vectorized gradients:
Computing the expectation of this Kronecker product directly couples activations and backward gradients. The K-FAC approximation assumes statistical independence between the activation covariance and the gradient covariance:
where:
- is the uncentered second moment of activations.
- is the uncentered second moment of pre-activation derivatives.
The crucial mathematical property that makes K-FAC scalable is that the inverse of a Kronecker product is the Kronecker product of the individual inverses:
To obtain the vectorized natural gradient , we leverage the matrix-vector identity . Because is symmetric (), the natural gradient in weight matrix form simplifies to:
Instead of inverting a matrix, ACKTR inverts one matrix and one matrix, followed by two fast matrix multiplications.
Trust Region Step and Covariance Running Averages
-
Running Statistics: Across parallel environment workers, empirical covariances are tracked with an exponential moving average (momentum parameter ):
-
Damped Inverses: To guarantee invertibility and numerical stability, Tikhonov damping is added:
-
Trust Region Constraint: Let be the concatenation of all natural gradients across layers, and be the concatenated standard gradients. The KL divergence constraint per update is enforced by scaling the natural gradient step:
where is the maximum allowed average KL divergence (typically ).
Worked numerical example
Let us trace a concrete layer () step-by-step.
Suppose the accumulated activation covariance and pre-activation gradient covariance (including damping) are diagonal matrices:
The first-order gradient matrix is:
-
Invert Covariances:
-
Compute Matrix-Form Natural Gradient:
-
Verify Equivalence with Full Kronecker Inverse: The full Kronecker product is:
Its inverse is:
The column-major vectorized gradient is . Applying the inverse:
Reshaping back to a matrix yields exactly .
-
Apply Trust-Region Scaling: Let the KL constraint limit be . The inner product between the natural gradient and the original gradient is:
The scaling coefficient is:
The resulting parameter step safely limits policy divergence.
Code
import numpy as np
class KFACLinearOptimizer: """Computes Kronecker-factored natural gradients and trust-region updates
for a linear feedforward layer W in R^{d_out x d_in}. """
def __init__( self, d_in: int, d_out: int, damping: float = 1e-3, kl_clip: float = 0.01, alpha: float = 0.95, ) -> None: self.d_in = d_in self.d_out = d_out self.damping = damping self.kl_clip = kl_clip self.alpha = alpha
# Covariance matrices initialized to identity self.cov_a = np.eye(d_in, dtype=np.float64) self.cov_s = np.eye(d_out, dtype=np.float64)
def update_covariances( self, activations: np.ndarray, pre_grads: np.ndarray, ) -> None: """Updates running exponential moving averages of activation and gradient covariances.
activations: shape (batch_size, d_in) pre_grads: shape (batch_size, d_out) """ batch_size = activations.shape[0]
# Empirical batch covariances: E[a a^T] and E[s s^T] batch_cov_a = (activations.T @ activations) / batch_size batch_cov_s = (pre_grads.T @ pre_grads) / batch_size
self.cov_a = self.alpha * self.cov_a + (1.0 - self.alpha) * batch_cov_a self.cov_s = self.alpha * self.cov_s + (1.0 - self.alpha) * batch_cov_s
def compute_natural_gradient( self, grad_w: np.ndarray, ) -> tuple[np.ndarray, np.ndarray]: """Computes natural gradient using fast matrix sandwich product S^{-1} G A^{-1}
and compares it against the full Kronecker product inverse. """ # Add Tikhonov damping a_damped = self.cov_a + self.damping * np.eye(self.d_in) s_damped = self.cov_s + self.damping * np.eye(self.d_out)
# Invert d_in x d_in and d_out x d_out matrices inv_a = np.linalg.inv(a_damped) inv_s = np.linalg.inv(s_damped)
# Fast matrix form: G_tilde = S^{-1} @ grad_w @ A^{-1} nat_grad_matrix = inv_s @ grad_w @ inv_a
# Full Kronecker inverse verification kron_fim = np.kron(a_damped, s_damped) inv_kron_fim = np.linalg.inv(kron_fim) vec_grad = grad_w.flatten(order="F") vec_nat_grad = inv_kron_fim @ vec_grad nat_grad_kron = vec_nat_grad.reshape((self.d_out, self.d_in), order="F")
return nat_grad_matrix, nat_grad_kron
def step(self, grad_w: np.ndarray) -> tuple[np.ndarray, float]: """Calculates trust-region scaled natural gradient step.""" nat_grad, _ = self.compute_natural_gradient(grad_w)
# Inner product g^T * g_tilde across weights g_dot_gtilde = float(np.sum(grad_w * nat_grad))
# Trust region factor beta = min(1, sqrt(2 * delta / (g^T g_tilde))) if g_dot_gtilde > 1e-12: scale = min(1.0, np.sqrt(2.0 * self.kl_clip / g_dot_gtilde)) else: scale = 1.0
step_direction = scale * nat_grad return step_direction, scale
if __name__ == "__main__": np.set_printoptions(precision=4, suppress=True)
# Initialize 2x2 layer optimizer opt = KFACLinearOptimizer(d_in=2, d_out=2, damping=0.0, kl_clip=0.01)
# Manually assign covariance matrices matching the worked numerical example opt.cov_a = np.diag([2.0, 1.0]) opt.cov_s = np.diag([4.0, 2.0])
grad_w = np.ones((2, 2), dtype=np.float64)
nat_grad_mat, nat_grad_kron = opt.compute_natural_gradient(grad_w) step_delta, scale_factor = opt.step(grad_w)
print("Matrix Natural Gradient:") print(nat_grad_mat) # -> Matrix Natural Gradient: # -> [[0.125 0.25 ] # -> [0.25 0.5 ]]
print("\nVectorized Kronecker Gradient:") print(nat_grad_kron) # -> Vectorized Kronecker Gradient: # -> [[0.125 0.25 ] # -> [0.25 0.5 ]]
print(f"\nTrust Region Scaling Factor: {scale_factor:.6f}") # -> Trust Region Scaling Factor: 0.133333
print("\nStep Direction:") print(step_delta) # -> Step Direction: # -> [[0.0167 0.0333] # -> [0.0333 0.0667]]
# Ensure matrix form matches Kronecker form assert np.allclose(nat_grad_mat, nat_grad_kron) print("\nVerification Passed: Matrix form matches exact Kronecker inverse.") # -> Verification Passed: Matrix form matches exact Kronecker inverse.Watch Out For
Inversion Frequency Overhead and Damping Mismatch
Inverting and at every micro-step degrades computational throughput. In practice, ACKTR decouples the covariance update from matrix inversion: covariance running averages are accumulated on every mini-batch, but the costly matrix inversions and are recomputed only once every to 100 steps.
Furthermore, if the Tikhonov damping parameter is chosen too small, empirical covariances can become near-singular, generating exploding gradient steps. Conversely, if is excessively large, , collapsing the natural gradient back into standard vanilla gradient descent and sacrificing all second-order curvature benefits. Keep calibrated to the variance of the policy distribution.
The Quick Version
- Kronecker Factorization: ACKTR replaces the intractable Fisher Information Matrix with layer-wise Kronecker products of input activations and output gradient covariances ().
- Inversion Scalability: Leveraging reduces inversion complexity from to , allowing natural gradients to run on modern GPUs.
- Actor and Critic Optimization: Unlike TRPO, which applies second-order updates only to the actor, ACKTR applies K-FAC natural gradient updates to both policy and value networks.
- No Conjugate Gradients: Eliminates the iterative Hessian-vector multiplication loops required by TRPO, delivering higher sample efficiency with consistent wall-clock throughput.