fix: MNFTransformer 数值稳定性 — 防 inf/NaN 导致 SVR 拒绝输入

- fit: 正则化按协方差总迹的1%自适应 + 截断噪声主导特征值
- fit: 输入先做 nan_to_num 清洗
- transform: 输出 clip 到 float32 安全范围,防止极端值
- 噪声协方差正则化从固定 1e-9 改为 trace*0.01,对 113 维特征更稳健
This commit is contained in:
duxin
2026-07-29 10:16:56 +08:00
parent 09f8d89b56
commit f3af45ccff

View File

@ -378,37 +378,48 @@ class MNFTransformer(TransformerMixin, BaseEstimator, _ArrayAsFloat64):
def fit(self, X, y=None):
X = self._to_ndarray(X)
X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)
n_samples, n_features = X.shape
# 1) 噪声矩阵:相邻行差分(假设输入按空间/时间连续排列)
# 1) 噪声矩阵:相邻行差分
noise = X[1:] - X[:-1] # (n-1, p)
Cn = np.cov(noise, rowvar=False) # (p, p)
Cn += np.eye(n_features) * 1e-9 # 防止奇异
# 正则化:按总方差的 1% 加对角项,强于固定 1e-9
_reg = np.trace(Cn) / n_features * 0.01
Cn += np.eye(n_features) * max(_reg, 1e-6)
# 2) 噪声白化:Cn = U diag(S) Vt — 对称矩阵用 eigh
# 2) 噪声白化:仅对显著特征值做白化,忽略噪声主导的成分
eigvals_n, eigvecs_n = np.linalg.eigh(Cn)
# eigh 返回升序排列;白化矩阵 = V @ diag(1/sqrt(s)) @ V.T
inv_sqrt = 1.0 / np.sqrt(np.maximum(eigvals_n, 1e-12))
Wn = eigvecs_n @ np.diag(inv_sqrt) @ eigvecs_n.T # (p, p)
# 截断:特征值 < max * 1e-4 的视为噪声,不参与白化(置单位矩阵)
_keep_mask = eigvals_n > eigvals_n.max() * 1e-4
if _keep_mask.sum() < 2:
_keep_mask[:2] = True # 至少保留 2 个成分
_kept_vals = np.where(_keep_mask, eigvals_n, 1.0)
inv_sqrt = np.where(_keep_mask, 1.0 / np.sqrt(np.maximum(_kept_vals, 1e-12)), 1.0)
Wn = eigvecs_n @ np.diag(inv_sqrt) @ eigvecs_n.T
# 3) 均值
self.mean_ = X.mean(axis=0, keepdims=True) # (1, p)
self.mean_ = X.mean(axis=0, keepdims=True)
# 4) 白化后的数据协方差 → PCA
X_w = (X - self.mean_) @ Wn # (n, p)
Cw = np.cov(X_w, rowvar=False) # (p, p)
# 4) 白化后 PCA
X_w = (X - self.mean_) @ Wn
Cw = np.cov(X_w, rowvar=False)
eigvals_w, eigvecs_w = np.linalg.eigh(Cw)
# 降序排列eigh 升序,翻转)
Wp = eigvecs_w[:, ::-1] # (p, p)
Wp = eigvecs_w[:, ::-1]
# 5) 最终变换矩阵
self.W_mnf_ = Wn @ Wp # (p, p)
self.W_mnf_ = Wn @ Wp
return self
def transform(self, X):
X = self._to_ndarray(X)
return (X - self.mean_) @ self.W_mnf_[:, :self.n_components]
X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)
result = (X - self.mean_) @ self.W_mnf_[:, :self.n_components]
# 防 SVR 拒绝 infclip 到 float32 安全范围
result = np.nan_to_num(result, nan=0.0, posinf=0.0, neginf=0.0)
result = np.clip(result, -1e15, 1e15)
return result
# ============================================================================