summaryrefslogtreecommitdiff
path: root/experiments/two_state_debias_smoke.py
blob: 9b57819182a8d3b06c64e889eb67a915828ad013 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python3
"""Strict-locality smoke test for the shared two-state SDIL filter."""

from __future__ import annotations

from pathlib import Path
import sys

import torch

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from sdil.two_state_debias import (  # noqa: E402
    LocalAffineDebiaser,
    two_state_difference,
)


def main() -> None:
    torch.manual_seed(20260806)
    templates = [torch.zeros(7), torch.zeros(3, 4)]
    centers = [0.2, -0.1]
    scales = [1.3, 0.8]
    affine = LocalAffineDebiaser(
        templates,
        feature_centers=centers,
        feature_scales=scales,
        affine=True,
    )
    constant = LocalAffineDebiaser(
        templates,
        feature_centers=centers,
        feature_scales=scales,
        affine=False,
    )
    intercepts = [torch.linspace(-0.4, 0.5, 7), torch.randn(3, 4) * 0.2]
    slopes = [torch.linspace(0.3, 1.0, 7), torch.randn(3, 4) * 0.4]

    train_features = []
    for value in torch.linspace(-1.5, 1.1, 50):
        train_features.append([
            torch.full_like(templates[0], value),
            torch.full_like(templates[1], -0.6 * value + 0.2),
        ])
    for _ in range(20):
        for features in train_features:
            measurements = [
                intercept + slope * ((feature - center) / scale)
                for intercept, slope, feature, center, scale in zip(
                    intercepts, slopes, features, centers, scales)
            ]
            affine.update_neutral(features, measurements, 0.2)
            constant.update_neutral(features, measurements, 0.2)

    held_features = [
        torch.full_like(templates[0], 1.35),
        torch.full_like(templates[1], -0.75),
    ]
    held_bias = [
        intercept + slope * ((feature - center) / scale)
        for intercept, slope, feature, center, scale in zip(
            intercepts, slopes, held_features, centers, scales)
    ]
    affine_error = torch.mean(torch.stack([
        (prediction - target).square().mean()
        for prediction, target in zip(affine.predict(held_features), held_bias)
    ])).sqrt()
    constant_error = torch.mean(torch.stack([
        (prediction - target).square().mean()
        for prediction, target in zip(constant.predict(held_features), held_bias)
    ])).sqrt()
    assert affine_error < 1e-5
    assert constant_error > 0.1

    first = [torch.randn_like(template) for template in templates]
    common = [torch.randn_like(template) for template in templates]
    second = [value + 0.03 * torch.ones_like(value) for value in first]
    difference_a = two_state_difference(first, second, 0.2)
    difference_b = two_state_difference(
        [value + offset for value, offset in zip(first, common)],
        [value + offset for value, offset in zip(second, common)],
        0.2,
    )
    common_mode_error = max(
        float((a - b).abs().max()) for a, b in zip(difference_a, difference_b)
    )
    assert common_mode_error < 2e-6

    teaching = [torch.randn_like(template) for template in templates]
    eligibility = [torch.randn_like(template) for template in templates]
    update_a = affine.replay_updates(
        held_features, teaching, eligibility, 0.07)
    downstream = torch.randn(1024, 1024)
    downstream.normal_()
    update_b = affine.replay_updates(
        held_features, teaching, eligibility, 0.07)
    assert all(torch.equal(a, b) for a, b in zip(update_a, update_b))
    assert all(not update.requires_grad for update in update_a)

    try:
        affine.predict([held_features[0].requires_grad_(), held_features[1]])
    except ValueError:
        pass
    else:
        raise AssertionError("requires-grad input was not rejected")

    print({
        "affine_heldout_rmse": float(affine_error),
        "constant_heldout_rmse": float(constant_error),
        "neutral_observations_each": affine.neutral_observations,
        "common_mode_max_float_error": common_mode_error,
        "downstream_independence_exact": True,
        "requires_grad_rejected": True,
    })


if __name__ == "__main__":
    main()