fix: MLXFilter fit/predict에 nan-safe 정규화 적용 (nanmean + nan_to_num)
Made-with: Cursor
This commit is contained in:
@@ -140,9 +140,12 @@ class MLXFilter:
|
|||||||
X_np = X[FEATURE_COLS].values.astype(np.float32)
|
X_np = X[FEATURE_COLS].values.astype(np.float32)
|
||||||
y_np = y.values.astype(np.float32)
|
y_np = y.values.astype(np.float32)
|
||||||
|
|
||||||
self._mean = X_np.mean(axis=0)
|
# nan-safe 정규화: nanmean/nanstd로 통계 계산 후 nan → 0.0 대치
|
||||||
self._std = X_np.std(axis=0) + 1e-8
|
# (z-score 후 0.0 = 평균값, 신경망에 줄 수 있는 가장 무난한 결측 대치값)
|
||||||
|
self._mean = np.nanmean(X_np, axis=0)
|
||||||
|
self._std = np.nanstd(X_np, axis=0) + 1e-8
|
||||||
X_np = (X_np - self._mean) / self._std
|
X_np = (X_np - self._mean) / self._std
|
||||||
|
X_np = np.nan_to_num(X_np, nan=0.0)
|
||||||
|
|
||||||
w_np = sample_weight.astype(np.float32) if sample_weight is not None else None
|
w_np = sample_weight.astype(np.float32) if sample_weight is not None else None
|
||||||
|
|
||||||
@@ -186,6 +189,7 @@ class MLXFilter:
|
|||||||
X_np = X[FEATURE_COLS].values.astype(np.float32)
|
X_np = X[FEATURE_COLS].values.astype(np.float32)
|
||||||
if self._trained and self._mean is not None:
|
if self._trained and self._mean is not None:
|
||||||
X_np = (X_np - self._mean) / self._std
|
X_np = (X_np - self._mean) / self._std
|
||||||
|
X_np = np.nan_to_num(X_np, nan=0.0)
|
||||||
x = mx.array(X_np)
|
x = mx.array(X_np)
|
||||||
self._model.eval()
|
self._model.eval()
|
||||||
logits = self._model(x)
|
logits = self._model(x)
|
||||||
|
|||||||
@@ -65,6 +65,31 @@ def test_mlx_filter_fit_and_predict():
|
|||||||
assert np.all((proba >= 0.0) & (proba <= 1.0))
|
assert np.all((proba >= 0.0) & (proba <= 1.0))
|
||||||
|
|
||||||
|
|
||||||
|
def test_fit_with_nan_features():
|
||||||
|
"""oi_change 피처에 nan이 포함된 경우 학습이 정상 완료되어야 한다."""
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from src.mlx_filter import MLXFilter
|
||||||
|
from src.ml_features import FEATURE_COLS
|
||||||
|
|
||||||
|
n = 300
|
||||||
|
np.random.seed(42)
|
||||||
|
X = pd.DataFrame(
|
||||||
|
np.random.randn(n, len(FEATURE_COLS)).astype(np.float32),
|
||||||
|
columns=FEATURE_COLS,
|
||||||
|
)
|
||||||
|
# oi_change 앞 절반을 nan으로
|
||||||
|
X["oi_change"] = np.where(np.arange(n) < n // 2, np.nan, X["oi_change"])
|
||||||
|
y = pd.Series((np.random.rand(n) > 0.5).astype(np.float32))
|
||||||
|
|
||||||
|
model = MLXFilter(input_dim=len(FEATURE_COLS), hidden_dim=32, epochs=3)
|
||||||
|
model.fit(X, y) # nan 있어도 예외 없이 완료되어야 함
|
||||||
|
|
||||||
|
proba = model.predict_proba(X)
|
||||||
|
assert not np.any(np.isnan(proba)), "예측 확률에 nan이 없어야 함"
|
||||||
|
assert proba.min() >= 0.0 and proba.max() <= 1.0
|
||||||
|
|
||||||
|
|
||||||
def test_mlx_filter_save_load(tmp_path):
|
def test_mlx_filter_save_load(tmp_path):
|
||||||
"""저장 후 로드한 모델이 동일한 예측값을 반환해야 한다."""
|
"""저장 후 로드한 모델이 동일한 예측값을 반환해야 한다."""
|
||||||
from src.mlx_filter import MLXFilter
|
from src.mlx_filter import MLXFilter
|
||||||
|
|||||||
Reference in New Issue
Block a user