Skip to content
AI360Xpert
Beta

Coarse Coding in RL

Coarse coding represents continuous state spaces using overlapping binary receptive fields, mapping any state to the set of regions containing it. The geometric shape, size, and orientation of these receptive fields directly sculpt how value knowledge generalizes across the state space.

Coarse coding covers continuous state spaces with overlapping receptive fields, where field shape dictates generalization patterns.
Coarse coding covers continuous state spaces with overlapping receptive fields, where field shape dictates generalization patterns.

Why Does This Exist?

In reinforcement learning with continuous state spaces, linear function approximation v^(s,w)=wTx(s)\hat{v}(s, \mathbf{w}) = \mathbf{w}^T \mathbf{x}(s) is widely favored for its theoretical stability, rapid computation, and absence of local optima. However, a linear model can only represent complex, non-linear value surfaces if the feature vector x(s)\mathbf{x}(s) captures non-linear properties of the underlying state space.

If an agent simply passes raw state coordinates (such as x(s)=[x,y]T\mathbf{x}(s) = [x, y]^T), the linear model can only represent a flat, tilted plane. It cannot represent peaks, valleys, boundaries, or sharp cliffs. Conversely, if the state space is discretized into rigid, non-overlapping grid cells (a tabular histogram), the agent loses all ability to generalize between adjacent cells.

Coarse coding solves this feature engineering challenge by covering the continuous state space with an ensemble of overlapping geometric regions, known as receptive fields.

Each receptive field corresponds to a single binary feature in the representation:

xi(s)={1if s∈Regioni0otherwisex_i(s) = \begin{cases} 1 & \text{if } s \in \text{Region}_i \\ 0 & \text{otherwise} \end{cases}

Because the regions overlap, any continuous state activates a whole subset of receptive fields. Updating the weight of an active feature following a transition at state ss transfers value to every other state that shares that receptive field. Crucially, coarse coding reveals a foundational machine learning insight: the geometric shape, size, and orientation of the receptive fields directly determine how value knowledge generalizes across the state space.

Think of It Like This

Cellular Tower Coverage Zones

Imagine tracking the geographic location of a smartphone across a city:

  • The Tabular Approach (GPS Coordinate Memorization): You record the exact latitude and longitude down to six decimal places (e.g., (37.774929, -122.419416)). If the phone takes a single step forward, the coordinate changes, and a tabular system treats it as an entirely unknown location with zero signal history.
  • The Coarse Coding Approach (Overlapping Cellular Towers): The city is covered by overlapping broadcast circles emitted by cellular towers. At any given physical street corner, your phone's receiver detects signals from a specific combination of towers—for example, Tower 3 (Downtown North), Tower 7 (Financial District), and Tower 12 (Bridge Span).
  • Your Location Code: Your position is encoded as a binary bitmask indicating active towers: x(location)=[Tower 1: 0,…,Tower 3: 1,…,Tower 7: 1,…,Tower 12: 1,… ]\mathbf{x}(\text{location}) = [\text{Tower 1: 0}, \dots, \text{Tower 3: 1}, \dots, \text{Tower 7: 1}, \dots, \text{Tower 12: 1}, \dots]
  • Generalization: If lightning strikes and impairs Tower 7, the parameter weights associated with Tower 7 are updated. Anyone standing nearby who also connects to Tower 7 immediately inherits that updated estimate—even if they are standing blocks away on an unvisited side street. The degree of shared signal between two citizens is strictly proportional to how many cell tower circles they share.

Where the analogy breaks down: Cellular radio power falls off smoothly as a continuous inverse-square function of distance. Classic coarse coding uses hard binary cutoff boundaries (xi∈{0,1}x_i \in \{0, 1\})—either you are inside the receptive field or outside it. (Smoothing these boundaries yields Radial Basis Functions). Furthermore, cell towers exist in physical 2D or 3D space, whereas coarse coding can be constructed over arbitrary multi-dimensional physical state spaces (e.g., position, velocity, angle, and angular velocity).

How It Actually Works

Binary Receptive Fields and Generalization Geometry

Coarse coding transforms a continuous state vector s∈S⊆Rks \in \mathcal{S} \subseteq \mathbb{R}^k into a high-dimensional, sparse binary feature vector x(s)∈{0,1}d\mathbf{x}(s) \in \{0, 1\}^d.

1. Mathematical Formulation

Let {R1,R2,…,Rd}\{R_1, R_2, \dots, R_d\} denote a collection of dd receptive fields defined over the continuous state space S\mathcal{S}. The ii-th feature is defined as:

xi(s)≐I(s∈Ri)={1if s∈Ri0otherwisex_i(s) \doteq \mathbb{I}(s \in R_i) = \begin{cases} 1 & \text{if } s \in R_i \\ 0 & \text{otherwise} \end{cases}

The approximate value function v^(s,w)\hat{v}(s, \mathbf{w}) is the inner product of the weight vector w∈Rd\mathbf{w} \in \mathbb{R}^d and feature vector x(s)\mathbf{x}(s):

v^(s,w)≐wTx(s)=∑i=1dwixi(s)=∑i∈active(s)wi\hat{v}(s, \mathbf{w}) \doteq \mathbf{w}^T \mathbf{x}(s) = \sum_{i=1}^d w_i x_i(s) = \sum_{i \in \text{active}(s)} w_i

where active(s)≐{i∈{1,…,d}∣s∈Ri}\text{active}(s) \doteq \{i \in \{1, \dots, d\} \mid s \in R_i\} is the set of indices of all receptive fields containing state ss. The value of state ss is simply the sum of weights of all receptive fields covering that state.

2. The Linear Semi-Gradient Update

When an agent observes an experience target UtU_t (such as a Monte Carlo return GtG_t or a one-step TD target Rt+1+γv^(St+1,w)R_{t+1} + \gamma \hat{v}(S_{t+1}, \mathbf{w})), the semi-gradient stochastic gradient descent update is:

w←w+α[Ut−v^(St,w)]∇wv^(St,w)\mathbf{w} \leftarrow \mathbf{w} + \alpha \left[ U_t - \hat{v}(S_t, \mathbf{w}) \right] \nabla_{\mathbf{w}} \hat{v}(S_t, \mathbf{w})

Because v^\hat{v} is linear, the gradient with respect to w\mathbf{w} is simply the binary feature vector itself: ∇wv^(St,w)=x(St)\nabla_{\mathbf{w}} \hat{v}(S_t, \mathbf{w}) = \mathbf{x}(S_t).

Denoting the TD error as δt≐Ut−v^(St,w)\delta_t \doteq U_t - \hat{v}(S_t, \mathbf{w}):

  • For inactive features (i∉active(St)i \notin \text{active}(S_t), where xi=0x_i = 0): wi←wi+0w_i \leftarrow w_i + 0 (no update).
  • For active features (i∈active(St)i \in \text{active}(S_t), where xi=1x_i = 1): wi←wi+αδtw_i \leftarrow w_i + \alpha \delta_t

(Optionally, the step size can be normalized by the number of active features: wi←wi+α∣active(St)∣δtw_i \leftarrow w_i + \frac{\alpha}{|\text{active}(S_t)|} \delta_t, ensuring that total value adjustments remain invariant to feature count).

3. How Receptive Field Geometry Dictates Generalization

The geometric characteristics of the receptive fields determine how the learning update at StS_t spreads to other states s′s':

LARGE CIRCLES                  SMALL CIRCLES                 ELONGATED ELLIPSES    ╭─────╮                       ╭─╮   ╭─╮                    ╭───────────╮  ╭─┤     ├─╮                    ╭┤ ├─╮╭┤ ├─╮                 ╭┤           ├─╮  │ │  s  │ │                    │╰─╯ ││╰─╯ │                 ││     s     ││  ╰─┤     ├─╯                    ╰─┬──╯╰──┬─╯                 ╰┤           ├─╯    ╰─────╯                        ╰─╮   ╭─╯                   ╰───────────╯Broad Isotropic                Sharp Spatial                  Directional BiasGeneralization                 Discrimination                 (e.g., Position vs Velocity)
  1. Large Receptive Fields (Broad Generalization): When fields have a large radius rr, states that are far apart still share many common receptive fields. An update at ss significantly alters the value of distant neighbors. This delivers rapid early learning of broad value trends, but limits the agent's ability to discriminate fine, sharp spatial details.
  2. Small Receptive Fields (Fine Discrimination): When fields have a small radius, neighboring states quickly cease to share active fields. Updates remain tightly localized. This allows the agent to represent sharp cliffs and narrow value peaks, but requires substantially more samples because value information diffuses slowly across space.
  3. Elongated / Elliptical Fields (Directional Generalization): If the receptive fields are elongated ellipses where the radius along feature 1 is much larger than along feature 2 (r1≫r2r_1 \gg r_2), the representation generalizes broadly along feature 1 while discriminating sharply along feature 2. In physical control tasks like the Mountain Car problem (position vs. velocity), value functions typically vary smoothly along position but change rapidly with velocity; elliptical fields aligned with these dynamics dramatically accelerate convergence.

Worked numerical example

Consider a 2D continuous state space covered by 5 receptive fields:

  • F1F_1: Circle centered at (2.0,3.0)(2.0, 3.0) with radius r=1.0r = 1.0
  • F2F_2: Circle centered at (2.5,3.2)(2.5, 3.2) with radius r=1.0r = 1.0
  • F3F_3: Circle centered at (2.8,2.7)(2.8, 2.7) with radius r=0.8r = 0.8
  • F4F_4: Circle centered at (3.2,3.5)(3.2, 3.5) with radius r=0.8r = 0.8
  • F5F_5: Circle centered at (1.0,1.0)(1.0, 1.0) with radius r=0.5r = 0.5

Let current parameter weights be: w=[w1=1.5,  w2=2.0,  w3=−0.5,  w4=0.8,  w5=−1.2]T\mathbf{w} = [w_1 = 1.5, \; w_2 = 2.0, \; w_3 = -0.5, \; w_4 = 0.8, \; w_5 = -1.2]^T

Step 1: Feature Extraction at Query State s=(2.5,3.0)s = (2.5, 3.0)

We check the Euclidean distance from ss to each field center:

  • For F1F_1: d2=(2.5−2.0)2+(3.0−3.0)2=0.25+0.00=0.25≤1.02  ⟹  Actived^2 = (2.5 - 2.0)^2 + (3.0 - 3.0)^2 = 0.25 + 0.00 = 0.25 \le 1.0^2 \implies \mathbf{Active} (x1=1x_1 = 1)
  • For F2F_2: d2=(2.5−2.5)2+(3.0−3.2)2=0.00+0.04=0.04≤1.02  ⟹  Actived^2 = (2.5 - 2.5)^2 + (3.0 - 3.2)^2 = 0.00 + 0.04 = 0.04 \le 1.0^2 \implies \mathbf{Active} (x2=1x_2 = 1)
  • For F3F_3: d2=(2.5−2.8)2+(3.0−2.7)2=0.09+0.09=0.18≤0.82=0.64  ⟹  Actived^2 = (2.5 - 2.8)^2 + (3.0 - 2.7)^2 = 0.09 + 0.09 = 0.18 \le 0.8^2 = 0.64 \implies \mathbf{Active} (x3=1x_3 = 1)
  • For F4F_4: d2=(2.5−3.2)2+(3.0−3.5)2=0.49+0.25=0.74>0.64  ⟹  Inactived^2 = (2.5 - 3.2)^2 + (3.0 - 3.5)^2 = 0.49 + 0.25 = 0.74 > 0.64 \implies \text{Inactive} (x4=0x_4 = 0)
  • For F5F_5: d2=(2.5−1.0)2+(3.0−1.0)2=2.25+4.00=6.25>0.25  ⟹  Inactived^2 = (2.5 - 1.0)^2 + (3.0 - 1.0)^2 = 2.25 + 4.00 = 6.25 > 0.25 \implies \text{Inactive} (x5=0x_5 = 0)

The binary feature vector for state ss is: x(s)=[1,1,1,0,0]T\mathbf{x}(s) = [1, 1, 1, 0, 0]^T

Step 2: Compute Initial Value Prediction

v^(s,w)=∑i∈{1,2,3}wi=1.5000+2.0000+(−0.5000)=3.0000\hat{v}(s, \mathbf{w}) = \sum_{i \in \{1, 2, 3\}} w_i = 1.5000 + 2.0000 + (-0.5000) = 3.0000

Step 3: Weight Update Following Experience Target

The agent experiences target return U=4.0U = 4.0. The TD error is: δ=U−v^(s,w)=4.0−3.0=+1.0000\delta = U - \hat{v}(s, \mathbf{w}) = 4.0 - 3.0 = +1.0000

Using learning rate α=0.1\alpha = 0.1, we update the active weights: Δwi=αδ=0.1×1.0=+0.1000(for i∈{1,2,3})\Delta w_i = \alpha \delta = 0.1 \times 1.0 = +0.1000 \quad (\text{for } i \in \{1, 2, 3\})

Updated weight vector: w1←1.5+0.1=1.6000w_1 \leftarrow 1.5 + 0.1 = 1.6000 w2←2.0+0.1=2.1000w_2 \leftarrow 2.0 + 0.1 = 2.1000 w3←−0.5+0.1=−0.4000w_3 \leftarrow -0.5 + 0.1 = -0.4000 w4←0.8000(unchanged)w_4 \leftarrow 0.8000 \quad (\text{unchanged}) w5←−1.2000(unchanged)w_5 \leftarrow -1.2000 \quad (\text{unchanged})

The new prediction at ss is: v^(s,wnew)=1.6000+2.1000−0.4000=3.3000\hat{v}(s, \mathbf{w}_{\text{new}}) = 1.6000 + 2.1000 - 0.4000 = 3.3000

Step 4: Generalization to an Unvisited Adjacent State

Now consider an adjacent, unvisited state sadj=(2.8,3.6)s_{\text{adj}} = (2.8, 3.6). Checking field containment:

  • In F1F_1: (2.8−2.0)2+(3.6−3.0)2=0.64+0.36=1.00≤1.00  ⟹  Active(2.8-2.0)^2 + (3.6-3.0)^2 = 0.64 + 0.36 = 1.00 \le 1.00 \implies \mathbf{Active} (x1=1x_1 = 1)
  • In F2F_2: (2.8−2.5)2+(3.6−3.2)2=0.09+0.16=0.25≤1.00  ⟹  Active(2.8-2.5)^2 + (3.6-3.2)^2 = 0.09 + 0.16 = 0.25 \le 1.00 \implies \mathbf{Active} (x2=1x_2 = 1)
  • In F3F_3: (2.8−2.8)2+(3.6−2.7)2=0.00+0.81=0.81>0.64  ⟹  Inactive(2.8-2.8)^2 + (3.6-2.7)^2 = 0.00 + 0.81 = 0.81 > 0.64 \implies \text{Inactive} (x3=0x_3 = 0)
  • In F4F_4: (2.8−3.2)2+(3.6−3.5)2=0.16+0.01=0.17≤0.64  ⟹  Active(2.8-3.2)^2 + (3.6-3.5)^2 = 0.16 + 0.01 = 0.17 \le 0.64 \implies \mathbf{Active} (x4=1x_4 = 1)
  • In F5F_5: Inactive (x5=0x_5 = 0)

The feature vector for sadjs_{\text{adj}} is x(sadj)=[1,1,0,1,0]T\mathbf{x}(s_{\text{adj}}) = [1, 1, 0, 1, 0]^T.

Compare its value prediction before and after the update at ss:

  • Before update to ss: v^(sadj,wold)=w1+w2+w4=1.5000+2.0000+0.8000=4.3000\hat{v}(s_{\text{adj}}, \mathbf{w}_{\text{old}}) = w_1 + w_2 + w_4 = 1.5000 + 2.0000 + 0.8000 = 4.3000
  • After update to ss: v^(sadj,wnew)=1.6000+2.1000+0.8000=4.5000\hat{v}(s_{\text{adj}}, \mathbf{w}_{\text{new}}) = 1.6000 + 2.1000 + 0.8000 = 4.5000

Even though sadjs_{\text{adj}} was never visited, its value prediction increased by +0.2000+0.2000 purely because it shares receptive fields F1F_1 and F2F_2 with ss!

Code

Below is a self-contained, type-hinted Python script implementing 2D coarse coding with both circular and elliptical receptive fields, binary feature extraction, and automated assertions confirming inductive generalization:

import mathfrom typing import List, Tuple, Union

class CircleReceptiveField:    """Circular binary receptive field: (x - cx)^2 + (y - cy)^2 <= r^2."""
    def __init__(self, name: str, center: Tuple[float, float], radius: float) -> None:        self.name = name        self.cx, self.cy = center        self.radius = radius
    def contains(self, point: Tuple[float, float]) -> bool:        x, y = point        return (x - self.cx) ** 2 + (y - self.cy) ** 2 <= self.radius ** 2

class EllipseReceptiveField:    """Elliptical binary receptive field: ((x - cx)/rx)^2 + ((y - cy)/ry)^2 <= 1."""
    def __init__(        self,        name: str,        center: Tuple[float, float],        radius_x: float,        radius_y: float,    ) -> None:        self.name = name        self.cx, self.cy = center        self.rx = radius_x        self.ry = radius_y
    def contains(self, point: Tuple[float, float]) -> bool:        x, y = point        return ((x - self.cx) / self.rx) ** 2 + ((y - self.cy) / self.ry) ** 2 <= 1.0

ReceptiveField = Union[CircleReceptiveField, EllipseReceptiveField]

class CoarseCoding2D:    """Coarse coding linear value function approximator in 2D space."""
    def __init__(self, receptive_fields: List[ReceptiveField]) -> None:        self.fields = receptive_fields        self.weights: List[float] = [0.0] * len(receptive_fields)
    def extract_features(self, point: Tuple[float, float]) -> List[int]:        """Convert a continuous 2D coordinate into a binary feature vector."""        return [1 if field.contains(point) else 0 for field in self.fields]
    def predict(self, point: Tuple[float, float]) -> float:        """Compute v_hat(point, w) = sum of weights for active fields."""        features = self.extract_features(point)        return sum(w * f for w, f in zip(self.weights, features))
    def update(        self, point: Tuple[float, float], target: float, alpha: float    ) -> Tuple[float, List[int]]:        """Perform semi-gradient SGD update on active binary features."""        features = self.extract_features(point)        pred = sum(w * f for w, f in zip(self.weights, features))        delta = target - pred
        # Each active feature updates by alpha * delta        for i in range(len(self.weights)):            if features[i] == 1:                self.weights[i] += alpha * delta
        return delta, features

def demonstrate_coarse_coding() -> None:    # 5 receptive fields matching the worked numerical example    fields: List[ReceptiveField] = [        CircleReceptiveField("F1", (2.0, 3.0), 1.0),        CircleReceptiveField("F2", (2.5, 3.2), 1.0),        CircleReceptiveField("F3", (2.8, 2.7), 0.8),        CircleReceptiveField("F4", (3.2, 3.5), 0.8),        CircleReceptiveField("F5", (1.0, 1.0), 0.5),    ]
    model = CoarseCoding2D(fields)    # Initialize weights: [1.5, 2.0, -0.5, 0.8, -1.2]    model.weights = [1.5, 2.0, -0.5, 0.8, -1.2]
    # Target training point s: (2.5, 3.0)    point_s = (2.5, 3.0)    feat_s = model.extract_features(point_s)    print(f"Features for s={point_s}: {feat_s}")    assert feat_s == [1, 1, 1, 0, 0]
    v_init_s = model.predict(point_s)    print(f"Initial v(s): {v_init_s:.4f}")    assert abs(v_init_s - 3.0) < 1e-6
    # Update with target=4.0, alpha=0.1 -> delta = +1.0    delta, _ = model.update(point_s, target=4.0, alpha=0.1)    print(f"Update TD error delta: {delta:.4f}")    print(f"Updated weights: {[round(w, 4) for w in model.weights]}")    assert abs(model.weights[0] - 1.6) < 1e-6    assert abs(model.weights[1] - 2.1) < 1e-6    assert abs(model.weights[2] - (-0.4)) < 1e-6    assert abs(model.weights[3] - 0.8) < 1e-6    assert abs(model.weights[4] - (-1.2)) < 1e-6
    v_new_s = model.predict(point_s)    print(f"Updated v(s): {v_new_s:.4f}")    assert abs(v_new_s - 3.3) < 1e-6
    # Evaluate unvisited adjacent point s_adj = (2.8, 3.6)    point_adj = (2.8, 3.6)    feat_adj = model.extract_features(point_adj)    print(f"\nFeatures for unvisited s_adj={point_adj}: {feat_adj}")    assert feat_adj == [1, 1, 0, 1, 0]
    v_adj = model.predict(point_adj)    print(f"Value for unvisited s_adj: {v_adj:.4f} (inherited +0.2 from shared fields F1, F2!)")    assert abs(v_adj - 4.5) < 1e-6

if __name__ == "__main__":    demonstrate_coarse_coding()
# -> Expected output:# -> Features for s=(2.5, 3.0): [1, 1, 1, 0, 0]# -> Initial v(s): 3.0000# -> Update TD error delta: 1.0000# -> Updated weights: [1.6, 2.1, -0.4, 0.8, -1.2]# -> Updated v(s): 3.3000# -> # -> Features for unvisited s_adj=(2.8, 3.6): [1, 1, 0, 1, 0]# -> Value for unvisited s_adj: 4.5000 (inherited +0.2 from shared fields F1, F2!)

Watch Out For

The Dead Zone Hazard and Anisotropic Mismatch

Two critical architectural traps frequently undermine coarse coding systems:

1. Dead Zones (Coverage Gaps): If receptive fields are scattered irregularly across the state space, gaps can emerge where no receptive fields overlap. When the agent visits a state ss in a dead zone:

  • The feature vector is completely empty: x(s)=0\mathbf{x}(s) = \mathbf{0}.
  • The value prediction collapses to zero: v^(s,w)=0.0\hat{v}(s, \mathbf{w}) = 0.0.
  • The gradient is identically zero: ∇wv^(s,w)=0\nabla_{\mathbf{w}} \hat{v}(s, \mathbf{w}) = \mathbf{0}. Gradient descent completely vanishes; the agent can experience massive TD errors in dead zones without updating a single parameter! The Fix: Use structured overlapping coverings such as Tile Coding, where multiple shifted regular grids guarantee that every coordinate in the state space activates exactly kk features (where kk is the number of tilings).

2. Anisotropic Dimensional Mismatch: Using standard circular receptive fields assumes that a Euclidean distance of 0.10.1 along dimension 1 has the same semantic importance as a distance of 0.10.1 along dimension 2. In physical systems, state dimensions differ by orders of magnitude (e.g., in Mountain Car, position ∈[−1.2,0.6]\in [-1.2, 0.6] while velocity ∈[−0.07,0.07]\in [-0.07, 0.07]). A circular field that covers a reasonable slice of position will span the entire velocity range, completely destroying the agent's ability to discriminate velocity. The Fix: Normalize all state variables to [0,1][0, 1] before feature extraction, or deploy elongated elliptical receptive fields whose radii match the characteristic scale of each physical dimension.

The Quick Version

  • Binary Receptive Fields: Coarse coding covers continuous state spaces with overlapping geometric regions, mapping any continuous coordinate into a sparse binary feature vector x(s)∈{0,1}d\mathbf{x}(s) \in \{0, 1\}^d.
  • Sum-of-Weights Value Surface: The approximate state value v^(s,w)=∑i∈active(s)wi\hat{v}(s, \mathbf{w}) = \sum_{i \in \text{active}(s)} w_i is simply the sum of weights associated with all receptive fields covering state ss.
  • Geometry Dictates Generalization: Large fields generate broad isotropic generalization with fast learning; small fields provide fine spatial discrimination; elongated ellipses produce directional generalization along primary physical axes.
  • Overlap-Proportional Knowledge Transfer: Generalization between two states is strictly proportional to the intersection volume of their active receptive fields, enabling zero-shot value estimation on unvisited coordinates.