Skip to content
AI360Xpert
Beta

Gradient-TD Methods (TDC & GTD2)

Standard TD methods can diverge when combining function approximation, bootstrapping, and off-policy sampling. Gradient-TD methods guarantee convergence by directly minimizing the projected Bellman error using a two-timescale weight update.

Gradient-TD methods use a fast auxiliary weight vector to estimate the Bellman projection gradient, enabling stable off-policy updates in linear time.
Gradient-TD methods use a fast auxiliary weight vector to estimate the Bellman projection gradient, enabling stable off-policy updates in linear time.

Why Does This Exist?

In reinforcement learning, the combination of three elements forms the notorious Deadly Triad:

  1. Function approximation (linear features or neural networks instead of tabular lookups)
  2. Bootstrapping (updating value estimates toward downstream value estimates rather than full Monte Carlo returns)
  3. Off-policy learning (evaluating or optimizing a target policy π\pi while observing transitions generated by an exploratory behavior policy bb)

When all three conditions hold simultaneously, standard semi-gradient Temporal Difference methods like TD(0) and Q-learning can diverge to infinity. Under an off-policy distribution μb\mu_b, the transition operator PπP^\pi is no longer a contraction mapping with respect to the state distribution weighting matrix D\mathbf{D}. As a result, semi-gradient TD updates do not follow the true negative gradient of any objective function. The underlying expected update matrix A=E[xt(xt−γxt+1)⊤]\mathbf{A} = \mathbb{E}[\mathbf{x}_t (\mathbf{x}_t - \gamma \mathbf{x}_{t+1})^\top] can have eigenvalues with negative real parts, causing value weights to explode exponentially—as vividly demonstrated by Baird's counterexample.

Early attempts to fix this divergence attempted to minimize the Mean Squared Bellman Error (MSBE\text{MSBE}). However, the true gradient of MSBE\text{MSBE} contains a product of expectations, creating the notorious double-sampling problem: computing an unbiased sample gradient requires observing two independent next states s′s' from the exact same state-action pair, which is impossible in model-free online learning.

Gradient-TD methods (specifically GTD2 and TDC / TD with Gradient Correction, introduced by Sutton, Maei, Precup, and Szepesvári in 2009) resolve this fundamental impasse. They define an objective function called the Mean Squared Projected Bellman Error (MSPBE\text{MSPBE}), which projects the Bellman error back onto the linear feature subspace. By introducing a secondary, fast-moving weight vector u∈Rd\mathbf{u} \in \mathbb{R}^d, Gradient-TD decomposes the sample gradient into two coupled recursions operating on different timescales. This circumvents double-sampling and guarantees true stochastic gradient descent in O(d)\mathcal{O}(d) time and space per time step.

Think of It Like This

A Two-Scale Hydraulic Feedback Regulator

Imagine you operate a massive industrial water main valve (w\mathbf{w}). Your goal is to stabilize water pressure at downstream stations under turbulent, non-equilibrium flow conditions (an off-policy regime).

If you adjust the main valve solely based on immediate downstream pressure readings (standard semi-gradient TD), feedback waves bounce back through the pipe network. Under certain resonant pipe geometry (function approximation), each corrective adjustment amplifies the standing wave, triggering catastrophic water-hammer oscillations that rupture the pipe (divergence).

Instead of turning the main valve directly against raw pressure surges, you install a small, lightweight damper piston (u\mathbf{u}) fitted with a sensitive micro-spring. Because the damper piston is lightweight, it responds almost instantaneously (the fast timescale), absorbing and measuring the net fluid backpressure gradient.

The heavy main valve (w\mathbf{w}) moves much more deliberately (the slow timescale). At each second, the main valve operator inspects the current position of the damper piston and makes a calibrated, true-gradient adjustment. Because the damper has already filtered out the deceptive feedback oscillations, the main valve reliably settles into the target flow equilibrium.

Where the analogy stops: A mechanical damper dissipates kinetic energy as physical heat, whereas the auxiliary vector u\mathbf{u} is an algebraic estimator solving a recursive least-squares projection to bridge two non-commuting expectations.

How It Actually Works

The MSPBE Objective and the Double-Sampling Problem

Let state values be approximated by a linear combination of feature vectors: Vw(s)=w⊤x(s)V_{\mathbf{w}}(s) = \mathbf{w}^\top \mathbf{x}(s)

In vector-matrix form, let Φ∈R∣S∣×d\mathbf{\Phi} \in \mathbb{R}^{|\mathcal{S}| \times d} be the feature matrix, and D∈R∣S∣×∣S∣\mathbf{D} \in \mathbb{R}^{|\mathcal{S}| \times |\mathcal{S}|} be a diagonal matrix of state visitation probabilities under the behavior policy bb, where Dss=db(s)D_{ss} = d_b(s). The weighted projection operator Π\Pi that projects any value function onto the subspace spanned by Φ\mathbf{\Phi} is: Π=Φ(Φ⊤DΦ)−1Φ⊤D\Pi = \mathbf{\Phi} (\mathbf{\Phi}^\top \mathbf{D} \mathbf{\Phi})^{-1} \mathbf{\Phi}^\top \mathbf{D}

The Bellman operator for target policy π\pi is TπV=rπ+γPπVT^\pi V = \mathbf{r}^\pi + \gamma \mathbf{P}^\pi V. Under off-policy sampling, the Bellman error TπVw−VwT^\pi V_{\mathbf{w}} - V_{\mathbf{w}} does not lie in the column space of Φ\mathbf{\Phi}. Gradient-TD methods minimize the Mean Squared Projected Bellman Error (MSPBE\text{MSPBE}): MSPBE(w)=∥ΠTπVw−Vw∥D2=(TπVw−Vw)⊤DΦ(Φ⊤DΦ)−1Φ⊤D(TπVw−Vw)\text{MSPBE}(\mathbf{w}) = \|\Pi T^\pi V_{\mathbf{w}} - V_{\mathbf{w}}\|_{\mathbf{D}}^2 = (T^\pi V_{\mathbf{w}} - V_{\mathbf{w}})^\top \mathbf{D} \mathbf{\Phi} (\mathbf{\Phi}^\top \mathbf{D} \mathbf{\Phi})^{-1} \mathbf{\Phi}^\top \mathbf{D} (T^\pi V_{\mathbf{w}} - V_{\mathbf{w}})

Defining: A≜Eb[ρtxt(xt−γxt+1)⊤]=Φ⊤D(I−γPπ)Φ\mathbf{A} \triangleq \mathbb{E}_{b}\left[\rho_t \mathbf{x}_t (\mathbf{x}_t - \gamma \mathbf{x}_{t+1})^\top\right] = \mathbf{\Phi}^\top \mathbf{D} (\mathbf{I} - \gamma \mathbf{P}^\pi) \mathbf{\Phi} b≜Eb[ρtRt+1xt]=Φ⊤Drπ\mathbf{b} \triangleq \mathbb{E}_{b}\left[\rho_t R_{t+1} \mathbf{x}_t\right] = \mathbf{\Phi}^\top \mathbf{D} \mathbf{r}^\pi C≜Eb[xtxt⊤]=Φ⊤DΦ\mathbf{C} \triangleq \mathbb{E}_{b}\left[\mathbf{x}_t \mathbf{x}_t^\top\right] = \mathbf{\Phi}^\top \mathbf{D} \mathbf{\Phi}

where ρt=π(At∣St)b(At∣St)\rho_t = \frac{\pi(A_t \mid S_t)}{b(A_t \mid S_t)} is the importance sampling ratio. The MSPBE reduces to the quadratic form: MSPBE(w)=(b−Aw)⊤C−1(b−Aw)\text{MSPBE}(\mathbf{w}) = (\mathbf{b} - \mathbf{A}\mathbf{w})^\top \mathbf{C}^{-1} (\mathbf{b} - \mathbf{A}\mathbf{w})

The gradient with respect to w\mathbf{w} is: −12∇wMSPBE(w)=A⊤C−1(b−Aw)-\frac{1}{2} \nabla_{\mathbf{w}} \text{MSPBE}(\mathbf{w}) = \mathbf{A}^\top \mathbf{C}^{-1} (\mathbf{b} - \mathbf{A}\mathbf{w})

Notice that b−Aw=Eb[ρtδtxt]\mathbf{b} - \mathbf{A}\mathbf{w} = \mathbb{E}_b[\rho_t \delta_t \mathbf{x}_t], where δt=Rt+1+γwt⊤xt+1−wt⊤xt\delta_t = R_{t+1} + \gamma \mathbf{w}_t^\top \mathbf{x}_{t+1} - \mathbf{w}_t^\top \mathbf{x}_t is the standard TD error. The expression for the gradient involves a product of three matrix/vector terms: ∇wMSPBE(w)=−2 E[ρt(xt−γxt+1)xt⊤]C−1E[ρtδtxt]\nabla_{\mathbf{w}} \text{MSPBE}(\mathbf{w}) = -2 \, \mathbb{E}\left[\rho_t (\mathbf{x}_t - \gamma \mathbf{x}_{t+1}) \mathbf{x}_t^\top\right] \mathbf{C}^{-1} \mathbb{E}\left[\rho_t \delta_t \mathbf{x}_t\right]

Because the expected value of a product of dependent random variables is not the product of their expected values (E[XY]≠E[X]E[Y]\mathbb{E}[X Y] \neq \mathbb{E}[X] \mathbb{E}[Y]), naive sampling yields a biased gradient unless two independent transitions are drawn from the same state.

The Two-Timescale Stochastic Approximation Solution

To avoid double sampling and matrix inversion (O(d3)\mathcal{O}(d^3)), Sutton et al. introduced an auxiliary parameter vector u∈Rd\mathbf{u} \in \mathbb{R}^d that estimates: u≈C−1(b−Aw)\mathbf{u} \approx \mathbf{C}^{-1} (\mathbf{b} - \mathbf{A}\mathbf{w})

Multiplying both sides by C\mathbf{C} gives Cu=b−Aw\mathbf{C} \mathbf{u} = \mathbf{b} - \mathbf{A}\mathbf{w}, which is equivalent to finding u\mathbf{u} that minimizes the quadratic loss E[(ρtδt−xt⊤u)2]\mathbb{E}[(\rho_t \delta_t - \mathbf{x}_t^\top \mathbf{u})^2]. This is a standard linear regression problem that can be updated incrementally via Least Mean Squares (LMS) on a fast timescale step-size βt\beta_t: ut+1=ut+βt(ρtδt−xt⊤ut)xt\mathbf{u}_{t+1} = \mathbf{u}_t + \beta_t \left(\rho_t \delta_t - \mathbf{x}_t^\top \mathbf{u}_t\right) \mathbf{x}_t

Given this estimate ut\mathbf{u}_t, the algorithms update the primary weights w\mathbf{w} along the slow timescale step-size αt\alpha_t:

  1. GTD2 (Gradient TD 2): Using A⊤u=E[ρt(xt−γxt+1)xt⊤]u=E[ρt(xt−γxt+1)(xt⊤u)]\mathbf{A}^\top \mathbf{u} = \mathbb{E}[\rho_t (\mathbf{x}_t - \gamma \mathbf{x}_{t+1}) \mathbf{x}_t^\top] \mathbf{u} = \mathbb{E}[\rho_t (\mathbf{x}_t - \gamma \mathbf{x}_{t+1}) (\mathbf{x}_t^\top \mathbf{u})]: wt+1=wt+αtρt(xt−γxt+1)(xt⊤ut)\mathbf{w}_{t+1} = \mathbf{w}_t + \alpha_t \rho_t \left(\mathbf{x}_t - \gamma \mathbf{x}_{t+1}\right) \left(\mathbf{x}_t^\top \mathbf{u}_t\right)

  2. TDC (TD with Gradient Correction): TDC decomposes A⊤=(C−γE[ρtxtxt+1⊤])⊤=C−γE[ρtxt+1xt⊤]\mathbf{A}^\top = (\mathbf{C} - \gamma \mathbb{E}[\rho_t \mathbf{x}_t \mathbf{x}_{t+1}^\top])^\top = \mathbf{C} - \gamma \mathbb{E}[\rho_t \mathbf{x}_{t+1} \mathbf{x}_t^\top]. Substituting Cu≈E[ρtδtxt]\mathbf{C}\mathbf{u} \approx \mathbb{E}[\rho_t \delta_t \mathbf{x}_t]: A⊤u=E[ρtδtxt]−γE[ρtxt+1(xt⊤u)]\mathbf{A}^\top \mathbf{u} = \mathbb{E}\left[\rho_t \delta_t \mathbf{x}_t\right] - \gamma \mathbb{E}\left[\rho_t \mathbf{x}_{t+1} (\mathbf{x}_t^\top \mathbf{u})\right] Sampling this expression gives the TDC weight update: wt+1=wt+αtρtδtxt−αtγρtxt+1(xt⊤ut)\mathbf{w}_{t+1} = \mathbf{w}_t + \alpha_t \rho_t \delta_t \mathbf{x}_t - \alpha_t \gamma \rho_t \mathbf{x}_{t+1} \left(\mathbf{x}_t^\top \mathbf{u}_t\right)

TDC is generally preferred in practice: its first term is identical to standard semi-gradient TD, while the second term acts as a corrective counterforce that prevents divergence when off-policy dynamics push eigenvalues into the unstable right half-plane.

For convergence, the step sizes must satisfy the standard Robbins-Monro conditions alongside the crucial two-timescale ratio: ∑t=0∞αt=∞,∑t=0∞βt=∞,∑t=0∞αt2<∞,∑t=0∞βt2<∞,lim⁡t→∞αtβt=0\sum_{t=0}^\infty \alpha_t = \infty, \quad \sum_{t=0}^\infty \beta_t = \infty, \quad \sum_{t=0}^\infty \alpha_t^2 < \infty, \quad \sum_{t=0}^\infty \beta_t^2 < \infty, \quad \lim_{t \to \infty} \frac{\alpha_t}{\beta_t} = 0

Under these conditions, ut\mathbf{u}_t converges to its quasi-stationary target C−1(b−Awt)\mathbf{C}^{-1}(\mathbf{b} - \mathbf{A}\mathbf{w}_t) as if wt\mathbf{w}_t were stationary, while wt\mathbf{w}_t tracks the true descent direction of the MSPBE.


Worked numerical example

Let us trace a single TDC update step on a 2-dimensional linear feature system with the following parameters:

  • Discount factor: γ=0.9\gamma = 0.9
  • Slow learning rate: α=0.1\alpha = 0.1
  • Fast learning rate: β=0.5\beta = 0.5
  • Importance sampling ratio: ρt=1.5\rho_t = 1.5
  • Current weights: wt=[1.00.5]\mathbf{w}_t = \begin{bmatrix} 1.0 \\ 0.5 \end{bmatrix}
  • Auxiliary weights: ut=[0.40.1]\mathbf{u}_t = \begin{bmatrix} 0.4 \\ 0.1 \end{bmatrix}
  • Current features: xt=[1.02.0]\mathbf{x}_t = \begin{bmatrix} 1.0 \\ 2.0 \end{bmatrix}
  • Next features: xt+1=[0.51.0]\mathbf{x}_{t+1} = \begin{bmatrix} 0.5 \\ 1.0 \end{bmatrix}
  • Observed reward: Rt+1=1.0R_{t+1} = 1.0

Step 1: Compute value estimates and TD error

Compute state values: V(St)=wt⊤xt=(1.0×1.0)+(0.5×2.0)=1.0+1.0=2.0V(S_t) = \mathbf{w}_t^\top \mathbf{x}_t = (1.0 \times 1.0) + (0.5 \times 2.0) = 1.0 + 1.0 = 2.0 V(St+1)=wt⊤xt+1=(1.0×0.5)+(0.5×1.0)=0.5+0.5=1.0V(S_{t+1}) = \mathbf{w}_t^\top \mathbf{x}_{t+1} = (1.0 \times 0.5) + (0.5 \times 1.0) = 0.5 + 0.5 = 1.0

Compute the TD error δt\delta_t: δt=Rt+1+γV(St+1)−V(St)=1.0+(0.9×1.0)−2.0=1.9−2.0=−0.1\delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t) = 1.0 + (0.9 \times 1.0) - 2.0 = 1.9 - 2.0 = -0.1

Compute the importance-weighted TD error: ρtδt=1.5×(−0.1)=−0.15\rho_t \delta_t = 1.5 \times (-0.1) = -0.15

Step 2: Evaluate the auxiliary projection

Compute the inner product of current features with auxiliary weights: xt⊤ut=(1.0×0.4)+(2.0×0.1)=0.4+0.2=0.60\mathbf{x}_t^\top \mathbf{u}_t = (1.0 \times 0.4) + (2.0 \times 0.1) = 0.4 + 0.2 = 0.60

Step 3: Fast-timescale update of auxiliary weights ut+1\mathbf{u}_{t+1}

The auxiliary residual is: ρtδt−xt⊤ut=−0.15−0.60=−0.75\rho_t \delta_t - \mathbf{x}_t^\top \mathbf{u}_t = -0.15 - 0.60 = -0.75

Update u\mathbf{u}: ut+1=ut+β(ρtδt−xt⊤ut)xt\mathbf{u}_{t+1} = \mathbf{u}_t + \beta \left(\rho_t \delta_t - \mathbf{x}_t^\top \mathbf{u}_t\right) \mathbf{x}_t ut+1=[0.40.1]+0.5×(−0.75)×[1.02.0]=[0.40.1]−[0.3750.750]=[0.025−0.650]\mathbf{u}_{t+1} = \begin{bmatrix} 0.4 \\ 0.1 \end{bmatrix} + 0.5 \times (-0.75) \times \begin{bmatrix} 1.0 \\ 2.0 \end{bmatrix} = \begin{bmatrix} 0.4 \\ 0.1 \end{bmatrix} - \begin{bmatrix} 0.375 \\ 0.750 \end{bmatrix} = \begin{bmatrix} 0.025 \\ -0.650 \end{bmatrix}

Step 4: Slow-timescale update of main weights wt+1\mathbf{w}_{t+1}

The TDC weight update consists of two components:

  1. Standard semi-gradient TD step: ΔwTD=αρtδtxt=0.1×(−0.15)×[1.02.0]=[−0.015−0.030]\Delta \mathbf{w}_{\text{TD}} = \alpha \rho_t \delta_t \mathbf{x}_t = 0.1 \times (-0.15) \times \begin{bmatrix} 1.0 \\ 2.0 \end{bmatrix} = \begin{bmatrix} -0.015 \\ -0.030 \end{bmatrix}

  2. Gradient correction step: Δwcorr=−αγρtxt+1(xt⊤ut)=−0.1×0.9×1.5×[0.51.0]×0.60=−0.081×[0.51.0]=[−0.0405−0.0810]\Delta \mathbf{w}_{\text{corr}} = -\alpha \gamma \rho_t \mathbf{x}_{t+1} (\mathbf{x}_t^\top \mathbf{u}_t) = -0.1 \times 0.9 \times 1.5 \times \begin{bmatrix} 0.5 \\ 1.0 \end{bmatrix} \times 0.60 = -0.081 \times \begin{bmatrix} 0.5 \\ 1.0 \end{bmatrix} = \begin{bmatrix} -0.0405 \\ -0.0810 \end{bmatrix}

Combine the components: wt+1=wt+ΔwTD+Δwcorr=[1.00.5]+[−0.015−0.030]+[−0.0405−0.0810]=[0.94450.3890]\mathbf{w}_{t+1} = \mathbf{w}_t + \Delta \mathbf{w}_{\text{TD}} + \Delta \mathbf{w}_{\text{corr}} = \begin{bmatrix} 1.0 \\ 0.5 \end{bmatrix} + \begin{bmatrix} -0.015 \\ -0.030 \end{bmatrix} + \begin{bmatrix} -0.0405 \\ -0.0810 \end{bmatrix} = \begin{bmatrix} 0.9445 \\ 0.3890 \end{bmatrix}

Both weight vectors remain bounded, with the correction term actively pulling w\mathbf{w} toward the true gradient of the MSPBE.

Code

The following self-contained Python script benchmarks Standard Off-Policy Semi-Gradient TD(0) against TDC on Baird's 7-State Counterexample—the canonical benchmark where off-policy TD(0) diverges to infinity:

"""Comparison of Semi-Gradient TD(0) vs TDC on Baird's Counterexample.
Baird's counterexample features 7 states and 8 features.Target policy pi: always chooses the 'solid' action leading to state 6.Behavior policy b: chooses the 'dashed' action (states 0-5) with probability 1/7,and the 'solid' action (state 6) with probability 6/7.All transitions yield reward 0, with discount gamma = 0.99.Semi-gradient TD(0) diverges exponentially; TDC converges to 0."""
from typing import Tupleimport numpy as np

class BairdsCounterexample:    """7-state MDP with 8 linear features designed by Leemon Baird (1995)."""
    def __init__(self, gamma: float = 0.99) -> None:        self.num_states: int = 7        self.num_features: int = 8        self.gamma: float = gamma
        # Feature matrix Phi: shape (7, 8)        # States 0..5: feature 2 at state index, feature 1 at index 7        # State 6: feature 1 at index 6, feature 2 at index 7        self.phi: np.ndarray = np.zeros((self.num_states, self.num_features), dtype=np.float64)        for s in range(6):            self.phi[s, s] = 2.0            self.phi[s, 7] = 1.0        self.phi[6, 6] = 1.0        self.phi[6, 7] = 2.0
    def step(self, state: int) -> Tuple[int, float, float]:        """Sample transition under behavior policy b.                Returns:            next_state: The resulting state.            reward: Scalar reward (always 0.0).            rho: Importance sampling ratio pi(a)/b(a).        """        # Behavior policy: 1/7 dashed action, 6/7 solid action        # Target policy: solid action with probability 1.0        if np.random.rand() < (1.0 / 7.0):            # Dashed action: transitions uniformly to one of states 0..5            next_state = np.random.randint(0, 6)            rho = 0.0  # pi(dashed) = 0, so rho = 0 / (1/7) = 0.0        else:            # Solid action: transitions deterministically to state 6            next_state = 6            rho = 1.0 / (6.0 / 7.0)  # pi(solid)=1.0, b(solid)=6/7 -> rho = 7/6
        reward = 0.0        return next_state, reward, rho
    def get_features(self, state: int) -> np.ndarray:        return self.phi[state].copy()

def run_experiment(num_steps: int = 3000, seed: int = 42) -> Tuple[float, float]:    """Run semi-gradient TD(0) and TDC in parallel on Baird's counterexample."""    np.random.seed(seed)    env = BairdsCounterexample(gamma=0.99)
    # Initial weights: [1, 1, 1, 1, 1, 1, 1, 10] as in Sutton & Barto    w_td = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 10.0], dtype=np.float64)    w_tdc = w_td.copy()    u_tdc = np.zeros(8, dtype=np.float64)
    alpha_td = 0.01    alpha_tdc = 0.005    beta_tdc = 0.05  # beta >> alpha (two-timescale separation)
    current_state_td = np.random.randint(0, 7)    current_state_tdc = current_state_td
    for step in range(num_steps):        # 1. Semi-gradient TD(0) step        x_td = env.get_features(current_state_td)        next_s_td, r_td, rho_td = env.step(current_state_td)        x_next_td = env.get_features(next_s_td)
        delta_td = r_td + env.gamma * np.dot(w_td, x_next_td) - np.dot(w_td, x_td)        w_td += alpha_td * rho_td * delta_td * x_td        current_state_td = next_s_td
        # 2. TDC step        x_tdc = env.get_features(current_state_tdc)        next_s_tdc, r_tdc, rho_tdc = env.step(current_state_tdc)        x_next_tdc = env.get_features(next_s_tdc)
        delta_tdc = r_tdc + env.gamma * np.dot(w_tdc, x_next_tdc) - np.dot(w_tdc, x_tdc)        x_dot_u = np.dot(x_tdc, u_tdc)
        # Fast timescale LMS auxiliary update        u_tdc += beta_tdc * (rho_tdc * delta_tdc - x_dot_u) * x_tdc
        # Slow timescale TDC primary update        w_tdc += alpha_tdc * (rho_tdc * delta_tdc * x_tdc - env.gamma * rho_tdc * x_next_tdc * x_dot_u)        current_state_tdc = next_s_tdc
    max_abs_td = float(np.max(np.abs(w_td)))    max_abs_tdc = float(np.max(np.abs(w_tdc)))
    return max_abs_td, max_abs_tdc

if __name__ == "__main__":    max_td, max_tdc = run_experiment(num_steps=3000, seed=42)    print(f"Semi-gradient TD(0) max weight: {max_td:.2f}")    print(f"TDC max weight:                 {max_tdc:.2f}")
    # Confirm divergence vs stability    assert max_td > 10_000.0, f"Expected TD(0) to diverge, got {max_td}"    assert max_tdc < 15.0, f"Expected TDC to remain bounded, got {max_tdc}"    print("Verification passed: TD(0) diverged while TDC remained stably bounded.")

Expected Output

Semi-gradient TD(0) max weight: 48381.40TDC max weight:                 6.68Verification passed: TD(0) diverged while TDC remained stably bounded.

Watch Out For

Timescale Inversion and the O(d²) Matrix Inversion Fallacy

The Trap: Practitioners implementing Gradient-TD frequently commit one of two major errors:

  1. Setting βt≤αt\beta_t \le \alpha_t: If the auxiliary vector ut\mathbf{u}_t is updated at the same or slower rate than the primary vector wt\mathbf{w}_t, the quasi-stationary assumption lim⁡t→∞αtβt=0\lim_{t \to \infty} \frac{\alpha_t}{\beta_t} = 0 is violated. The auxiliary weights fail to track the quasi-equilibrium C−1(b−Aw)\mathbf{C}^{-1}(\mathbf{b} - \mathbf{A}\mathbf{w}), injecting stale or destabilizing gradient corrections that can cause TDC to diverge just as severely as standard TD.
  2. Explicitly forming C−1\mathbf{C}^{-1}: Attempting to invert the covariance matrix directly incurs O(d3)\mathcal{O}(d^3) computational cost and numerical instability under collinear features.

The Fix:

  • Maintain a conservative step-size ratio: set β\beta at least 5 to 10 times larger than α\alpha (e.g., α=0.005,β=0.05\alpha = 0.005, \beta = 0.05).
  • Never compute or invert matrix C\mathbf{C}. The auxiliary recursion ut+1=ut+βt(ρtδt−xt⊤ut)xt\mathbf{u}_{t+1} = \mathbf{u}_t + \beta_t (\rho_t \delta_t - \mathbf{x}_t^\top \mathbf{u}_t) \mathbf{x}_t computes the exact vector-matrix product implicitly, retaining strict O(d)\mathcal{O}(d) per-step time and memory complexity.

The Quick Version

  • Solves the Deadly Triad: Gradient-TD methods provide the first mathematically guaranteed convergent off-policy linear TD algorithms with O(d)\mathcal{O}(d) per-step complexity.
  • Minimizes the MSPBE: Unlike standard semi-gradient TD, which follows no objective function, GTD2 and TDC perform true stochastic gradient descent on the Mean Squared Projected Bellman Error (MSPBE\text{MSPBE}).
  • Two-Timescale Architecture: Solves the double-sampling dilemma by decoupling updates into a fast auxiliary vector ut\mathbf{u}_t estimating C−1(b−Aw)\mathbf{C}^{-1} (\mathbf{b} - \mathbf{A}\mathbf{w}) and a slow primary vector wt\mathbf{w}_t following the true gradient.
  • TDC Preferred Over GTD2: TDC augments standard TD with a gradient correction term, offering faster empirical convergence and recovering standard TD when off-policy corrections vanish.