feat: 光谱黄金区间过滤 — 训练波长统一收缩到 400-1000nm

- 新增 WAVELENGTH_RANGE = (400.0, 1000.0) 类常量
- 新增 _is_wavelength_in_range() 范围感知波长列判断
- _extract_train_wavelengths() 自动过滤超出区间的波长
- _extract_feature_columns / train_single_model / pre-scan 三处调用点
  统一切换为范围感知判断
- 效果: 308 波段 → 300 波段 (裁掉 <400nm 的 4 个 + >1000nm 的 4 个)
- 推理端通过 metadata train_wavelengths 自动继承过滤后的网格
This commit is contained in:
duxin
2026-07-30 13:12:14 +08:00
parent b19cb427b9
commit f0b9f178da

View File

@ -250,6 +250,9 @@ class WaterQualityModelingBatch:
self.results = {}
self.best_models = {}
# ★ 黄金光谱区间:统一收缩到 400-1000nm消除边缘噪声和跨传感器不一致
WAVELENGTH_RANGE = (400.0, 1000.0)
@staticmethod
def _is_wavelength_column(col_name: str) -> bool:
"""判断列名是否为波长值(纯数字字符串,如 '374.285004'"""
@ -259,22 +262,35 @@ class WaterQualityModelingBatch:
except (ValueError, TypeError):
return False
@classmethod
def _is_wavelength_in_range(cls, col_name: str) -> bool:
"""判断列名是否为波长值且在黄金区间 [400, 1000] nm 内。"""
try:
wl = float(str(col_name))
lo, hi = cls.WAVELENGTH_RANGE
return lo <= wl <= hi
except (ValueError, TypeError):
return False
@staticmethod
def _is_wqi_column(col_name: str) -> bool:
"""判断列名是否为 WQI 水质指数列('WQI_' 前缀)"""
return str(col_name).startswith('WQI_')
@staticmethod
def _extract_train_wavelengths(columns) -> List[float]:
"""从列名列表中提取波长值float 列表)
@classmethod
def _extract_train_wavelengths(cls, columns) -> List[float]:
"""从列名列表中提取波长值float 列表),自动过滤到黄金区间。
遍历列名,将所有可转为 float 的列名提取为波长列表。
遍历列名,将所有可转为 float 且落在 [400, 1000] nm 的列名提取为波长列表。
用于写入模型 metadata['train_wavelengths'],供推理端光谱重采样。
"""
lo, hi = cls.WAVELENGTH_RANGE
wl_list = []
for c in columns:
try:
wl_list.append(float(str(c)))
wl = float(str(c))
if lo <= wl <= hi:
wl_list.append(wl)
except (ValueError, TypeError):
pass
return wl_list
@ -327,8 +343,8 @@ class WaterQualityModelingBatch:
# 跳过元数据列
if col_lower in _meta_patterns:
continue
# 保留波长列
if self._is_wavelength_column(col):
# 保留波长列(仅黄金区间 400-1000nm
if self._is_wavelength_in_range(col):
feature_indices.append(i)
# 保留 WQI 列
elif self._is_wqi_column(col):
@ -613,9 +629,9 @@ class WaterQualityModelingBatch:
# ★ DualStream_MNF只取纯光谱列WQI 由 Pipeline 内 PhysicalExtractor 动态计算
# ═══════════════════════════════════════════════════════════
if preprocess_method in ("DualStream_MNF", "Physical_Only"):
_spec_cols = [c for c in X_raw.columns if self._is_wavelength_column(c)]
_spec_cols = [c for c in X_raw.columns if self._is_wavelength_in_range(c)]
X_raw = X_raw[_spec_cols]
print(f"[{preprocess_method}] 精简为纯光谱: {X_raw.shape[1]}"
print(f"[{preprocess_method}] 精简为纯光谱(400-1000nm): {X_raw.shape[1]}"
f"({X_raw.columns[0]} ~ {X_raw.columns[-1]} nm)")
# 使用指定方法分割训练集和测试集
@ -975,9 +991,9 @@ class WaterQualityModelingBatch:
X_clean = X_raw[mask]
# 只保留纯光谱列(与 DualStream_MNF Pipeline 内的逻辑一致)
# 只保留纯光谱列(与 DualStream_MNF Pipeline 内的逻辑一致400-1000nm
_spec_cols = [c for c in X_clean.columns
if self._is_wavelength_column(c)]
if self._is_wavelength_in_range(c)]
X_spec = X_clean[_spec_cols].values.astype(np.float64)
try: