-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLinear.py
More file actions
195 lines (151 loc) · 6.39 KB
/
Copy pathLinear.py
File metadata and controls
195 lines (151 loc) · 6.39 KB
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import numpy as np
from Normalize import fit_X_scaler
from sklearn.preprocessing import PolynomialFeatures
from Dataset import get_station_features, get_station_target
# ── accumulate XtX and Xty ───────────────────────────────────────────
def accumulate_normal_equations(idx, static_ds, time_indicators,
era5_ds, madis_ds, variable,
poly, mean_, var_):
"""
Accumulate XtX and Xty over stations in idx.
Parameters
----------
idx : np.ndarray of station indices
static_ds : xr.Dataset, shape (station_id,)
time_indicators : dict from compute_time_indicators()
era5_ds : xr.Dataset, already time-sliced
madis_ds : xr.Dataset, already time-sliced
variable : str
poly : fitted PolynomialFeatures
mean_ : np.ndarray, shape (p,)
var_ : np.ndarray, shape (p,)
Returns
-------
XtX : np.ndarray, shape (p, p)
Xty : np.ndarray, shape (p,)
"""
p = len(mean_)
XtX = np.zeros((p, p))
Xty = np.zeros(p)
for count, i in enumerate(idx):
X_i = get_station_features(i, static_ds, time_indicators, variable)
X_i_poly = poly.transform(X_i)
X_i_sc = (X_i_poly - mean_) / np.sqrt(var_)
y_i = get_station_target(i, era5_ds, madis_ds, variable)
XtX += X_i_sc.T @ X_i_sc
Xty += X_i_sc.T @ y_i
if (count + 1) % 100 == 0:
print(f'Accumulate {variable}: {count + 1}/{len(idx)} stations', flush=True)
return XtX, Xty
# ── solve ridge ───────────────────────────────────────────────────────────────
def solve_ridge(XtX, Xty, alpha):
"""
Solve β = (XtX + α*I)^{-1} Xty.
Parameters
----------
XtX : np.ndarray, shape (p, p)
Xty : np.ndarray, shape (p,)
alpha : float, regularization strength
Returns
-------
beta : np.ndarray, shape (p,)
"""
p = XtX.shape[0]
A = XtX + alpha * np.eye(p)
beta = np.linalg.solve(A, Xty)
return beta
# ── evaluate on a set of stations ────────────────────────────────────────────
def eval_stations_linear(idx, static_ds, time_indicators, era5_ds, madis_ds,
variable, poly, mean_, var_, beta):
"""
Compute RMSE over stations in idx.
Parameters
----------
idx : np.ndarray of station indices
beta : np.ndarray, shape (p,)
Returns
-------
rmse : float
"""
sq_err = []
for i in idx:
X_i = get_station_features(i, static_ds, time_indicators, variable)
X_i_poly = poly.transform(X_i)
X_i_sc = (X_i_poly - mean_) / np.sqrt(var_)
y_i = get_station_target(i, era5_ds, madis_ds, variable)
e_hat_i = X_i_sc @ beta
residual = y_i - e_hat_i
sq_err.append((residual ** 2).mean())
return np.sqrt(np.mean(sq_err))
# ── train ─────────────────────────────────────────────────────────────────────
def train_linear(train_idx, val_idx, static_ds, time_indicators,
era5_ds, madis_ds, variable):
"""
Full incremental Ridge training pipeline with alpha selection on val set.
Parameters
----------
train_idx : np.ndarray of station indices
val_idx : np.ndarray of station indices
static_ds : xr.Dataset, shape (station_id,)
time_indicators : dict from compute_time_indicators()
era5_ds : xr.Dataset, already time-sliced
madis_ds : xr.Dataset, already time-sliced
variable : str
Returns
-------
poly : fitted PolynomialFeatures
mean_ : np.ndarray, shape (p,)
var_ : np.ndarray, shape (p,)
beta : np.ndarray, shape (p,) — best solution
"""
alphas = [0.01, 0.1, 1.0]
poly = PolynomialFeatures(degree=2, include_bias=True)
# pass 1: fit poly + accumulate scaler stats on train stations
print(f'[{variable}] Pass 1: fitting scaler...', flush=True)
mean_, var_ = fit_X_scaler(
train_idx, static_ds, time_indicators, variable, poly
)
# pass 2: accumulate XtX and Xty on train stations
print(f'[{variable}] Pass 2: accumulating normal equations...', flush=True)
XtX, Xty = accumulate_normal_equations(
train_idx, static_ds, time_indicators,
era5_ds, madis_ds, variable,
poly, mean_, var_
)
# alpha selection on validation set
print(f'[{variable}] Selecting alpha on validation set...', flush=True)
best_alpha, best_rmse, best_beta = None, np.inf, None
for alpha in alphas:
beta = solve_ridge(XtX, Xty, alpha)
val_rmse = eval_stations_linear(
val_idx, static_ds, time_indicators,
era5_ds, madis_ds, variable,
poly, mean_, var_, beta
)
print(f'alpha={alpha} val RMSE={val_rmse}', flush=True)
if val_rmse < best_rmse:
best_rmse = val_rmse
best_alpha = alpha
best_beta = beta
print(f'[{variable}] Best alpha: {best_alpha} val RMSE: {best_rmse}', flush=True)
return poly, mean_, var_, best_beta
# ── predict ───────────────────────────────────────────────────────────────────
def predict_linear(i, static_ds, time_indicators, era5_ds,
variable, poly, mean_, var_, beta):
"""
Compute bias-corrected ERA5 for a single station i.
Parameters
----------
i : int, station index
era5_ds : xr.Dataset, already time-sliced
Returns
-------
f_hat : np.ndarray, shape (n_time,), float32
"""
X_i = get_station_features(i, static_ds, time_indicators, variable)
X_i_poly = poly.transform(X_i)
X_i_sc = (X_i_poly - mean_) / np.sqrt(var_)
e_hat = X_i_sc @ beta
f_i = era5_ds[variable].values[i].astype(np.float64)
f_hat = (f_i - e_hat).astype(np.float32)
return f_hat