fix: 修复工作目录与步骤名不对应、回归预测虚数报错、模型加载及预处理名称转换问题,重构可视化并修正勾选联动
This commit is contained in:
654
src/core/prediction/custom_regression_prediction.py
Normal file
654
src/core/prediction/custom_regression_prediction.py
Normal file
@ -0,0 +1,654 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
自定义回归预测模块
|
||||
|
||||
该模块根据9_Custom_Regression_Modeling文件夹中的CSV信息,批量预测水质指数。
|
||||
处理流程:
|
||||
1. 读取9_Custom_Regression_Modeling文件夹中的CSV文件
|
||||
2. 根据r_squared选择最佳模型(指数公式+反演公式)
|
||||
3. 使用指数公式计算光谱指数值
|
||||
4. 使用反演公式计算水质参数值
|
||||
5. 输出包含投影经纬度和预测值的CSV文件
|
||||
|
||||
作者: Assistant
|
||||
创建时间: 2026-04-14
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Union, Tuple, Optional
|
||||
import warnings
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# 导入水质指数计算器
|
||||
try:
|
||||
from src.utils.water_index import WaterQualityIndexCalculator
|
||||
except ImportError:
|
||||
from ..utils.water_index import WaterQualityIndexCalculator
|
||||
|
||||
|
||||
class CustomRegressionPredictor:
|
||||
"""
|
||||
自定义回归预测器
|
||||
|
||||
基于9_Custom_Regression_Modeling文件夹中的回归模型CSV文件,
|
||||
进行水质参数的批量预测。
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
regression_models_dir: str = "9_Custom_Regression_Modeling",
|
||||
formula_csv_path: Optional[str] = None,
|
||||
output_dir: str = "prediction_results",
|
||||
log_level: int = logging.INFO):
|
||||
"""
|
||||
初始化预测器
|
||||
|
||||
Args:
|
||||
regression_models_dir: 回归模型CSV文件所在目录
|
||||
formula_csv_path: 公式CSV文件路径,用于查找index_formula
|
||||
output_dir: 预测结果输出目录
|
||||
log_level: 日志级别
|
||||
"""
|
||||
self.regression_models_dir = Path(regression_models_dir)
|
||||
self.formula_csv_path = formula_csv_path
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 初始化日志
|
||||
self._setup_logging(log_level)
|
||||
|
||||
# 初始化水质指数计算器
|
||||
self.index_calculator = WaterQualityIndexCalculator()
|
||||
|
||||
# 存储加载的回归模型信息
|
||||
self.regression_models = {}
|
||||
self.best_models = {}
|
||||
|
||||
# 加载公式CSV(如果提供)
|
||||
self.formula_df = None
|
||||
if self.formula_csv_path and Path(self.formula_csv_path).exists():
|
||||
self.formula_df = pd.read_csv(self.formula_csv_path)
|
||||
self.logger.info(f"加载公式CSV: {self.formula_csv_path}, 共 {len(self.formula_df)} 个公式")
|
||||
|
||||
self.logger.info(f"CustomRegressionPredictor初始化完成")
|
||||
self.logger.info(f"回归模型目录: {self.regression_models_dir}")
|
||||
self.logger.info(f"输出目录: {self.output_dir}")
|
||||
|
||||
def _setup_logging(self, log_level: int):
|
||||
"""设置日志配置"""
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.logger.setLevel(log_level)
|
||||
|
||||
# 避免重复添加处理器
|
||||
if not self.logger.handlers:
|
||||
# 创建控制台处理器
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(log_level)
|
||||
|
||||
# 创建格式器
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
self.logger.addHandler(console_handler)
|
||||
|
||||
def load_regression_models(self) -> Dict[str, pd.DataFrame]:
|
||||
"""
|
||||
加载9_Custom_Regression_Modeling文件夹中的所有CSV文件
|
||||
|
||||
支持的CSV格式:
|
||||
- 回归结果CSV包含列:y_variable, x_variable, equation, r_squared
|
||||
- 其中 x_variable 是指数名称,需要从 formula_csv 中查找对应的 Formula
|
||||
- equation 是反演公式
|
||||
|
||||
Returns:
|
||||
Dict[str, pd.DataFrame]: 参数名 -> 回归模型数据框
|
||||
"""
|
||||
if not self.regression_models_dir.exists():
|
||||
raise FileNotFoundError(f"回归模型目录不存在: {self.regression_models_dir}")
|
||||
|
||||
csv_files = list(self.regression_models_dir.glob("*.csv"))
|
||||
if not csv_files:
|
||||
raise FileNotFoundError(f"在目录 {self.regression_models_dir} 中未找到CSV文件")
|
||||
|
||||
self.logger.info(f"找到 {len(csv_files)} 个CSV文件")
|
||||
|
||||
for csv_file in csv_files:
|
||||
try:
|
||||
# 跳过all_regression_results.csv,处理单个参数的结果文件
|
||||
if csv_file.name.lower() == "all_regression_results.csv":
|
||||
self.logger.info(f"跳过汇总文件: {csv_file.name}")
|
||||
continue
|
||||
|
||||
# 从文件名推断参数名(移除_regression_results后缀)
|
||||
param_name = csv_file.stem.replace('_regression_results', '').replace('_results', '')
|
||||
self.logger.info(f"加载回归模型文件: {csv_file.name} -> 参数: {param_name}")
|
||||
|
||||
# 读取CSV文件
|
||||
df = pd.read_csv(csv_file)
|
||||
|
||||
# 检查必需的列并进行列名映射
|
||||
# 实际CSV列名: y_variable, x_variable, equation, r_squared
|
||||
# 需要的列名: index_formula, inversion_formula, r_squared
|
||||
|
||||
# 列名映射
|
||||
column_mapping = {}
|
||||
|
||||
# 检查r_squared(可能为R2, r2, R_squared等)
|
||||
r_squared_cols = ['r_squared', 'R_squared', 'R2', 'r2', 'R_squared', 'R²']
|
||||
found_r2 = None
|
||||
for col in r_squared_cols:
|
||||
if col in df.columns:
|
||||
found_r2 = col
|
||||
break
|
||||
if found_r2 and found_r2 != 'r_squared':
|
||||
column_mapping[found_r2] = 'r_squared'
|
||||
|
||||
# 检查equation(反演公式)
|
||||
if 'equation' in df.columns:
|
||||
column_mapping['equation'] = 'inversion_formula'
|
||||
elif 'inversion_formula' not in df.columns:
|
||||
self.logger.warning(f"文件 {csv_file.name} 缺少反演公式列(equation)")
|
||||
continue
|
||||
|
||||
# 检查x_variable(指数名称,需要转换为index_formula)
|
||||
if 'x_variable' in df.columns:
|
||||
# 需要从formula_csv中查找对应的公式
|
||||
if self.formula_df is not None:
|
||||
df['index_formula'] = df['x_variable'].apply(
|
||||
lambda x: self._lookup_formula_from_name(x)
|
||||
)
|
||||
else:
|
||||
# 如果没有formula_csv,直接使用x_variable作为index_formula
|
||||
column_mapping['x_variable'] = 'index_formula'
|
||||
elif 'index_formula' not in df.columns:
|
||||
self.logger.warning(f"文件 {csv_file.name} 缺少指数名称列(x_variable)")
|
||||
continue
|
||||
|
||||
# 应用列名映射
|
||||
if column_mapping:
|
||||
df = df.rename(columns=column_mapping)
|
||||
|
||||
# 验证必需的列
|
||||
required_columns = ['index_formula', 'inversion_formula', 'r_squared']
|
||||
missing_columns = [col for col in required_columns if col not in df.columns]
|
||||
if missing_columns:
|
||||
self.logger.warning(f"文件 {csv_file.name} 缺少必需列: {missing_columns}")
|
||||
continue
|
||||
|
||||
self.regression_models[param_name] = df
|
||||
self.logger.info(f"成功加载参数 {param_name} 的 {len(df)} 个回归模型")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"加载文件 {csv_file.name} 失败: {e}")
|
||||
continue
|
||||
|
||||
if not self.regression_models:
|
||||
raise ValueError("未成功加载任何回归模型")
|
||||
|
||||
return self.regression_models
|
||||
|
||||
def _lookup_formula_from_name(self, formula_name: str) -> str:
|
||||
"""
|
||||
从公式CSV中根据公式名称查找对应的公式
|
||||
|
||||
Args:
|
||||
formula_name: 公式名称(如 BGA_Am09KBBI)
|
||||
|
||||
Returns:
|
||||
公式字符串
|
||||
"""
|
||||
if self.formula_df is None:
|
||||
return formula_name
|
||||
|
||||
# 查找匹配的公式名称
|
||||
# 支持多种列名:Formula_Name, formula_name, name等
|
||||
name_cols = ['Formula_Name', 'formula_name', 'Name', 'name']
|
||||
name_col = None
|
||||
for col in name_cols:
|
||||
if col in self.formula_df.columns:
|
||||
name_col = col
|
||||
break
|
||||
|
||||
if name_col is None:
|
||||
self.logger.warning(f"公式CSV中未找到公式名称列,使用默认列名")
|
||||
name_col = self.formula_df.columns[0] if len(self.formula_df.columns) > 0 else None
|
||||
|
||||
# 查找公式列
|
||||
formula_cols = ['Formula', 'formula', 'Expression', 'expression']
|
||||
formula_col = None
|
||||
for col in formula_cols:
|
||||
if col in self.formula_df.columns:
|
||||
formula_col = col
|
||||
break
|
||||
|
||||
if formula_col is None and len(self.formula_df.columns) > 2:
|
||||
formula_col = self.formula_df.columns[2] # 通常第3列是公式
|
||||
|
||||
if name_col and formula_col:
|
||||
match = self.formula_df[self.formula_df[name_col] == formula_name]
|
||||
if not match.empty:
|
||||
return match[formula_col].iloc[0]
|
||||
|
||||
# 如果未找到,返回原始名称
|
||||
return formula_name
|
||||
|
||||
def select_best_models(self) -> Dict[str, pd.Series]:
|
||||
"""
|
||||
为每个水质参数选择r_squared最高的回归模型
|
||||
|
||||
Returns:
|
||||
Dict[str, pd.Series]: 参数名 -> 最佳模型信息
|
||||
"""
|
||||
if not self.regression_models:
|
||||
self.load_regression_models()
|
||||
|
||||
for param_name, models_df in self.regression_models.items():
|
||||
# 根据r_squared排序,选择最高的
|
||||
best_model = models_df.loc[models_df['r_squared'].idxmax()]
|
||||
self.best_models[param_name] = best_model
|
||||
|
||||
self.logger.info(f"参数 {param_name} 最佳模型:")
|
||||
self.logger.info(f" 指数公式: {best_model['index_formula']}")
|
||||
self.logger.info(f" 反演公式: {best_model['inversion_formula']}")
|
||||
self.logger.info(f" R²: {best_model['r_squared']:.4f}")
|
||||
|
||||
return self.best_models
|
||||
|
||||
def calculate_spectral_index(self,
|
||||
spectral_data: pd.DataFrame,
|
||||
index_formula: str) -> pd.Series:
|
||||
"""
|
||||
根据指数公式计算光谱指数值
|
||||
|
||||
Args:
|
||||
spectral_data: 光谱数据,列名为波长
|
||||
index_formula: 指数公式字符串
|
||||
|
||||
Returns:
|
||||
pd.Series: 计算得到的指数值
|
||||
"""
|
||||
try:
|
||||
# 检查公式是否为水质指数计算器中的标准函数
|
||||
if hasattr(self.index_calculator, index_formula):
|
||||
# 使用标准水质指数计算器
|
||||
calculator_func = getattr(self.index_calculator, index_formula)
|
||||
index_values = calculator_func(spectral_data)
|
||||
self.logger.debug(f"使用标准指数函数 {index_formula} 计算完成")
|
||||
return index_values
|
||||
|
||||
# 否则尝试解析自定义公式
|
||||
# 支持常见的指数公式格式,如 (R865-R644)/(R458+R529) 或 w681 - w665
|
||||
formula = index_formula.strip()
|
||||
|
||||
# 替换波长变量为对应的列值
|
||||
# 查找所有R或w开头的波长变量(支持 R681, w681, R_681 等格式)
|
||||
wavelength_pattern = r'[Rw](\d+)'
|
||||
wavelengths = re.findall(wavelength_pattern, formula)
|
||||
|
||||
# 构建可执行的表达式
|
||||
expression = formula
|
||||
for wl in set(wavelengths):
|
||||
wl_int = int(wl)
|
||||
# 找到最接近的波长列
|
||||
closest_col = self.index_calculator.find_closest_wavelength(
|
||||
spectral_data.columns.tolist(), wl_int
|
||||
)
|
||||
# 替换公式中的变量(支持 R数字 和 w数字 格式)
|
||||
expression = expression.replace(f'R{wl}', f'spectral_data["{closest_col}"]')
|
||||
expression = expression.replace(f'w{wl}', f'spectral_data["{closest_col}"]')
|
||||
|
||||
# 评估表达式
|
||||
index_values = eval(expression)
|
||||
self.logger.debug(f"使用自定义公式 {index_formula} 计算完成")
|
||||
|
||||
return pd.Series(index_values, index=spectral_data.index)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"计算光谱指数失败,公式: {index_formula}, 错误: {e}")
|
||||
# 返回NaN值
|
||||
return pd.Series(np.nan, index=spectral_data.index)
|
||||
|
||||
def apply_inversion_formula(self,
|
||||
index_values: pd.Series,
|
||||
inversion_formula: str) -> pd.Series:
|
||||
"""
|
||||
根据反演公式将指数值转换为水质参数值
|
||||
|
||||
Args:
|
||||
index_values: 光谱指数值
|
||||
inversion_formula: 反演公式字符串,如 "y = 11.27 + 0.82*x" 或 "11.27 + 0.82*x"
|
||||
支持的函数:ln(x)自然对数, log10(x)常用对数, exp(x)指数, sqrt(x)平方根
|
||||
|
||||
Returns:
|
||||
pd.Series: 水质参数预测值
|
||||
"""
|
||||
try:
|
||||
# 处理公式中的常见数学符号
|
||||
formula = inversion_formula.strip()
|
||||
|
||||
# 去除 "y = " 或 "y=" 前缀
|
||||
if formula.lower().startswith('y='):
|
||||
formula = formula[2:].strip()
|
||||
elif formula.lower().startswith('y = '):
|
||||
formula = formula[4:].strip()
|
||||
|
||||
# 替换数学符号
|
||||
formula = formula.replace('^', '**') # 幂运算
|
||||
|
||||
# 定义数学函数环境
|
||||
import math
|
||||
math_env = {
|
||||
'ln': math.log, # 自然对数
|
||||
'log': math.log, # 自然对数(别名)
|
||||
'log10': math.log10, # 常用对数
|
||||
'exp': math.exp, # 指数函数
|
||||
'sqrt': math.sqrt, # 平方根
|
||||
'abs': abs, # 绝对值
|
||||
'pow': pow, # 幂函数
|
||||
}
|
||||
|
||||
self.logger.debug(f"处理后的反演公式: {formula}")
|
||||
|
||||
# 评估每个指数值
|
||||
predicted_values = []
|
||||
for x_val in index_values:
|
||||
try:
|
||||
if pd.isna(x_val):
|
||||
predicted_values.append(np.nan)
|
||||
else:
|
||||
# 评估公式,将 x 作为局部变量传递,同时提供数学函数
|
||||
local_vars = {'x': x_val}
|
||||
local_vars.update(math_env)
|
||||
result = eval(formula, {"__builtins__": {}}, local_vars)
|
||||
|
||||
# 如果是复数,取实部
|
||||
if isinstance(result, complex):
|
||||
result = result.real
|
||||
|
||||
predicted_values.append(result)
|
||||
except Exception as eval_err:
|
||||
self.logger.debug(f"评估公式失败,x={x_val}: {eval_err}")
|
||||
predicted_values.append(np.nan)
|
||||
|
||||
self.logger.debug(f"使用反演公式 {inversion_formula} 计算完成")
|
||||
return pd.Series(predicted_values, index=index_values.index)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"应用反演公式失败,公式: {inversion_formula}, 错误: {e}")
|
||||
return pd.Series(np.nan, index=index_values.index)
|
||||
|
||||
def predict_single_parameter(self,
|
||||
spectral_data: pd.DataFrame,
|
||||
coordinate_data: pd.DataFrame,
|
||||
param_name: str) -> pd.DataFrame:
|
||||
"""
|
||||
预测单个水质参数
|
||||
|
||||
Args:
|
||||
spectral_data: 光谱数据(不包含坐标列)
|
||||
coordinate_data: 坐标数据(包含经纬度或投影坐标)
|
||||
param_name: 水质参数名
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 包含坐标和预测值的结果
|
||||
"""
|
||||
if param_name not in self.best_models:
|
||||
raise ValueError(f"未找到参数 {param_name} 的最佳模型")
|
||||
|
||||
best_model = self.best_models[param_name]
|
||||
index_formula = best_model['index_formula']
|
||||
inversion_formula = best_model['inversion_formula']
|
||||
|
||||
self.logger.info(f"开始预测参数: {param_name}")
|
||||
|
||||
# 步骤1: 计算光谱指数
|
||||
self.logger.info("步骤1: 计算光谱指数")
|
||||
index_values = self.calculate_spectral_index(spectral_data, index_formula)
|
||||
|
||||
# 步骤2: 应用反演公式
|
||||
self.logger.info("步骤2: 应用反演公式")
|
||||
predicted_values = self.apply_inversion_formula(index_values, inversion_formula)
|
||||
|
||||
# 步骤3: 组合结果
|
||||
result_df = coordinate_data.copy()
|
||||
result_df[f'{param_name}_predicted'] = predicted_values
|
||||
result_df[f'{param_name}_index'] = index_values
|
||||
|
||||
# 添加元数据
|
||||
result_df.attrs = {
|
||||
'parameter': param_name,
|
||||
'index_formula': index_formula,
|
||||
'inversion_formula': inversion_formula,
|
||||
'r_squared': best_model['r_squared'],
|
||||
'prediction_time': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
valid_predictions = result_df[f'{param_name}_predicted'].notna().sum()
|
||||
total_points = len(result_df)
|
||||
self.logger.info(f"参数 {param_name} 预测完成: {valid_predictions}/{total_points} 个有效预测")
|
||||
|
||||
return result_df
|
||||
|
||||
def predict_all_parameters(self,
|
||||
spectral_data: pd.DataFrame,
|
||||
coordinate_data: pd.DataFrame) -> Dict[str, pd.DataFrame]:
|
||||
"""
|
||||
批量预测所有水质参数
|
||||
|
||||
Args:
|
||||
spectral_data: 光谱数据(不包含坐标列)
|
||||
coordinate_data: 坐标数据(包含经纬度或投影坐标)
|
||||
|
||||
Returns:
|
||||
Dict[str, pd.DataFrame]: 参数名 -> 预测结果DataFrame
|
||||
"""
|
||||
if not self.best_models:
|
||||
self.select_best_models()
|
||||
|
||||
prediction_results = {}
|
||||
|
||||
for param_name in self.best_models.keys():
|
||||
try:
|
||||
self.logger.info(f"正在预测参数: {param_name}")
|
||||
result_df = self.predict_single_parameter(
|
||||
spectral_data, coordinate_data, param_name
|
||||
)
|
||||
prediction_results[param_name] = result_df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"预测参数 {param_name} 失败: {e}")
|
||||
continue
|
||||
|
||||
self.logger.info(f"批量预测完成,成功预测 {len(prediction_results)} 个参数")
|
||||
return prediction_results
|
||||
|
||||
def _convert_complex_to_real(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
将DataFrame中的复数转换为实数(取实部)
|
||||
|
||||
Args:
|
||||
df: 输入DataFrame
|
||||
|
||||
Returns:
|
||||
处理后的DataFrame
|
||||
"""
|
||||
df_clean = df.copy()
|
||||
for col in df_clean.columns:
|
||||
if df_clean[col].dtype == 'object' or 'complex' in str(df_clean[col].dtype):
|
||||
# 检查是否包含复数
|
||||
try:
|
||||
df_clean[col] = df_clean[col].apply(
|
||||
lambda x: x.real if isinstance(x, complex) else x
|
||||
)
|
||||
except:
|
||||
pass
|
||||
return df_clean
|
||||
|
||||
def save_prediction_results(self,
|
||||
prediction_results: Dict[str, pd.DataFrame],
|
||||
filename_prefix: str = "custom_regression_prediction") -> Dict[str, str]:
|
||||
"""
|
||||
保存预测结果为CSV文件
|
||||
|
||||
Args:
|
||||
prediction_results: 预测结果字典
|
||||
filename_prefix: 文件名前缀
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: 参数名 -> 保存的文件路径
|
||||
"""
|
||||
saved_files = {}
|
||||
|
||||
|
||||
for param_name, result_df in prediction_results.items():
|
||||
try:
|
||||
# 构建文件名
|
||||
filename = f"{filename_prefix}_{param_name}.csv"
|
||||
filepath = self.output_dir / filename
|
||||
|
||||
# 处理复数类型,转换为实数
|
||||
result_df_clean = self._convert_complex_to_real(result_df)
|
||||
|
||||
# 保存CSV文件
|
||||
result_df_clean.to_csv(filepath, index=False, encoding='utf-8-sig')
|
||||
saved_files[param_name] = str(filepath)
|
||||
|
||||
self.logger.info(f"参数 {param_name} 预测结果已保存: {filepath}")
|
||||
|
||||
# 打印统计信息
|
||||
predicted_col = f'{param_name}_predicted'
|
||||
if predicted_col in result_df.columns:
|
||||
valid_count = result_df[predicted_col].notna().sum()
|
||||
mean_value = result_df[predicted_col].mean()
|
||||
std_value = result_df[predicted_col].std()
|
||||
|
||||
self.logger.info(f" 有效预测: {valid_count} 个")
|
||||
self.logger.info(f" 平均值: {mean_value:.4f}")
|
||||
self.logger.info(f" 标准差: {std_value:.4f}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"保存参数 {param_name} 结果失败: {e}")
|
||||
continue
|
||||
|
||||
return saved_files
|
||||
|
||||
def run_batch_prediction(self,
|
||||
input_csv_path: str,
|
||||
coordinate_columns: List[str] = None,
|
||||
filename_prefix: str = "custom_regression_prediction") -> Dict[str, str]:
|
||||
"""
|
||||
运行完整的批量预测流程
|
||||
|
||||
Args:
|
||||
input_csv_path: 输入的光谱采样CSV文件路径
|
||||
coordinate_columns: 坐标列名列表,默认为['longitude', 'latitude']
|
||||
filename_prefix: 输出文件名前缀
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: 参数名 -> 保存的文件路径
|
||||
"""
|
||||
if coordinate_columns is None:
|
||||
coordinate_columns = ['longitude', 'latitude']
|
||||
|
||||
self.logger.info("="*50)
|
||||
self.logger.info("开始自定义回归批量预测")
|
||||
self.logger.info("="*50)
|
||||
|
||||
# 1. 加载回归模型
|
||||
self.logger.info("步骤1: 加载回归模型")
|
||||
self.load_regression_models()
|
||||
|
||||
# 2. 选择最佳模型
|
||||
self.logger.info("步骤2: 选择最佳模型")
|
||||
self.select_best_models()
|
||||
|
||||
# 3. 读取输入数据
|
||||
self.logger.info("步骤3: 读取输入数据")
|
||||
input_df = pd.read_csv(input_csv_path)
|
||||
self.logger.info(f"读取输入数据: {len(input_df)} 行, {len(input_df.columns)} 列")
|
||||
|
||||
# 4. 分离坐标和光谱数据
|
||||
self.logger.info("步骤4: 分离坐标和光谱数据")
|
||||
|
||||
# 检查坐标列是否存在
|
||||
missing_coords = [col for col in coordinate_columns if col not in input_df.columns]
|
||||
if missing_coords:
|
||||
# 尝试自动识别坐标列
|
||||
potential_coord_cols = []
|
||||
for col in input_df.columns:
|
||||
if any(coord_name in col.lower() for coord_name in ['lon', 'lat', 'x', 'y']):
|
||||
potential_coord_cols.append(col)
|
||||
|
||||
if len(potential_coord_cols) >= 2:
|
||||
coordinate_columns = potential_coord_cols[:2]
|
||||
self.logger.info(f"自动识别坐标列: {coordinate_columns}")
|
||||
else:
|
||||
raise ValueError(f"未找到坐标列: {missing_coords}")
|
||||
|
||||
coordinate_data = input_df[coordinate_columns].copy()
|
||||
spectral_data = input_df.drop(columns=coordinate_columns)
|
||||
|
||||
self.logger.info(f"坐标数据: {len(coordinate_data)} 行, {len(coordinate_data.columns)} 列")
|
||||
self.logger.info(f"光谱数据: {len(spectral_data)} 行, {len(spectral_data.columns)} 列")
|
||||
|
||||
# 5. 批量预测
|
||||
self.logger.info("步骤5: 执行批量预测")
|
||||
prediction_results = self.predict_all_parameters(spectral_data, coordinate_data)
|
||||
|
||||
# 6. 保存结果
|
||||
self.logger.info("步骤6: 保存预测结果")
|
||||
saved_files = self.save_prediction_results(prediction_results, filename_prefix)
|
||||
|
||||
self.logger.info("="*50)
|
||||
self.logger.info(f"批量预测完成!共生成 {len(saved_files)} 个结果文件")
|
||||
self.logger.info("="*50)
|
||||
|
||||
return saved_files
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数,用于命令行调用"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='自定义回归预测模块')
|
||||
parser.add_argument('--input_csv', required=True, help='输入的光谱采样CSV文件路径')
|
||||
parser.add_argument('--models_dir', default='9_Custom_Regression_Modeling',
|
||||
help='回归模型CSV文件目录')
|
||||
parser.add_argument('--output_dir', default='prediction_results',
|
||||
help='预测结果输出目录')
|
||||
parser.add_argument('--coord_cols', nargs='+', default=['longitude', 'latitude'],
|
||||
help='坐标列名')
|
||||
parser.add_argument('--prefix', default='custom_regression_prediction',
|
||||
help='输出文件名前缀')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 创建预测器
|
||||
predictor = CustomRegressionPredictor(
|
||||
regression_models_dir=args.models_dir,
|
||||
output_dir=args.output_dir
|
||||
)
|
||||
|
||||
# 运行预测
|
||||
saved_files = predictor.run_batch_prediction(
|
||||
input_csv_path=args.input_csv,
|
||||
coordinate_columns=args.coord_cols,
|
||||
filename_prefix=args.prefix
|
||||
)
|
||||
|
||||
print("\n预测结果文件:")
|
||||
for param_name, filepath in saved_files.items():
|
||||
print(f" {param_name}: {filepath}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -609,6 +609,11 @@ class WaterQualityScatterBatch:
|
||||
split_method = best_row['划分方法']
|
||||
preprocess_method = best_row['预处理方法']
|
||||
model_name = best_row['建模方法']
|
||||
|
||||
# 处理 nan/NaN/None 值,转换为 "None" 字符串
|
||||
if pd.isna(preprocess_method) or str(preprocess_method).lower() in ['nan', 'none', '']:
|
||||
preprocess_method = "None"
|
||||
|
||||
best_combination = f"{split_method}_{preprocess_method}_{model_name}"
|
||||
else:
|
||||
# 简化结果文件格式(英文列名)
|
||||
@ -617,11 +622,15 @@ class WaterQualityScatterBatch:
|
||||
parts = best_combination.split('_')
|
||||
if len(parts) < 3:
|
||||
raise ValueError(f"无效的模型组合名称格式: {best_combination}")
|
||||
|
||||
|
||||
split_method = parts[0]
|
||||
preprocess_method = parts[1]
|
||||
model_name = '_'.join(parts[2:])
|
||||
|
||||
# 处理 nan/NaN/None 值,转换为 "None" 字符串
|
||||
if pd.isna(preprocess_method) or str(preprocess_method).lower() in ['nan', 'none', '']:
|
||||
preprocess_method = "None"
|
||||
|
||||
print(f"最佳模型组合: {best_combination}")
|
||||
print(f" 划分方法: {split_method}")
|
||||
print(f" 预处理方法: {preprocess_method}")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -58,6 +58,8 @@ from src.core.glint_removal.Hedley import Hedley
|
||||
from src.core.glint_removal.SUGAR import SUGAR, correction_iterative
|
||||
from src.utils.water_index import WaterQualityIndexCalculator
|
||||
from src.core.modeling.regression import SingleVariableRegressionAnalysis
|
||||
# 导入新的自定义回归预测模块
|
||||
from src.core.prediction.custom_regression_prediction import CustomRegressionPredictor
|
||||
# 导入hdr文件处理函数
|
||||
try:
|
||||
from src.utils.util import write_fields_to_hdrfile, get_hdr_file_path, find_band_number
|
||||
@ -106,9 +108,9 @@ class WaterQualityInversionPipeline:
|
||||
self.processed_data_dir = self.work_dir / "4_processed_data"
|
||||
self.training_spectra_dir = self.work_dir / "5_training_spectra"
|
||||
self.indices_dir = self.work_dir / "6_water_quality_indices"
|
||||
self.models_dir = self.work_dir / "7_models"
|
||||
self.non_empirical_models_dir = self.work_dir / "8_non_empirical_models"
|
||||
self.custom_regression_dir = self.work_dir / "9_custom_regression"
|
||||
self.models_dir = self.work_dir / "7_Supervised_Model_Training"
|
||||
self.non_empirical_models_dir = self.work_dir / "8_Regression_Modeling"
|
||||
self.custom_regression_dir = self.work_dir / "9_Custom_Regression_Modeling"
|
||||
self.sampling_dir = self.work_dir / "10_sampling"
|
||||
self.prediction_dir = self.work_dir / "11_12_13_predictions"
|
||||
self.visualization_dir = self.work_dir / "14_visualization"
|
||||
@ -3379,10 +3381,10 @@ class WaterQualityInversionPipeline:
|
||||
else:
|
||||
# 如果output_dir为空,使用工作目录
|
||||
if hasattr(self, 'work_dir') and self.work_dir is not None:
|
||||
non_empirical_dir = Path(self.work_dir) / "8_non_empirical_models"
|
||||
non_empirical_dir = Path(self.work_dir) / "8_Regression_Modeling"
|
||||
else:
|
||||
# 如果没有工作目录,使用当前目录
|
||||
non_empirical_dir = Path.cwd() / "8_non_empirical_models"
|
||||
non_empirical_dir = Path.cwd() / "8_Regression_Modeling"
|
||||
non_empirical_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 设置默认参数
|
||||
@ -3592,9 +3594,17 @@ class WaterQualityInversionPipeline:
|
||||
|
||||
# 应用预处理 - 使用spectral_Preprocessing模块
|
||||
from src.preprocessing.spectral_Preprocessing import Preprocessing
|
||||
|
||||
# 调用预处理函数
|
||||
processed_spectral = Preprocessing(preprocess_method, spectral_data)
|
||||
|
||||
# 为SS预处理提供scaler保存路径,保存在工作目录的7_Supervised_Model_Training中
|
||||
save_path = None
|
||||
if preprocess_method == 'SS':
|
||||
models_dir = output_dir.parent.parent / "7_Supervised_Model_Training" # 向上两级到工作目录
|
||||
models_dir.mkdir(parents=True, exist_ok=True)
|
||||
save_path = str(models_dir / "scaler_params.pkl")
|
||||
print(f"SS预处理: scaler模型将保存到 {save_path}")
|
||||
|
||||
# 调用预处理函数(为SS方法传递save_path)
|
||||
processed_spectral = Preprocessing(preprocess_method, spectral_data, save_path=save_path)
|
||||
|
||||
# 重新组合数据
|
||||
if isinstance(processed_spectral, pd.DataFrame):
|
||||
@ -3712,7 +3722,7 @@ class WaterQualityInversionPipeline:
|
||||
if non_empirical_models_dir is not None:
|
||||
final_models_dir = non_empirical_models_dir
|
||||
else:
|
||||
default_models_dir = str(self.work_dir / "8_non_empirical_models")
|
||||
default_models_dir = str(self.work_dir / "8_Regression_Modeling")
|
||||
if Path(default_models_dir).exists():
|
||||
final_models_dir = default_models_dir
|
||||
else:
|
||||
@ -3812,28 +3822,31 @@ class WaterQualityInversionPipeline:
|
||||
raise
|
||||
|
||||
def step8_75_predict_with_custom_regression(self, sampling_csv_path: str,
|
||||
formula_csv_file: str,
|
||||
custom_regression_dir: Optional[str] = None,
|
||||
formula_names: Optional[List[str]] = None,
|
||||
prediction_column: str = 'prediction',
|
||||
formula_csv_path: Optional[str] = None,
|
||||
coordinate_columns: Optional[List[str]] = None,
|
||||
output_dir: Optional[str] = None,
|
||||
filename_prefix: str = "custom_regression_prediction",
|
||||
enabled: bool = True,
|
||||
skip_dependency_check: bool = False) -> Dict[str, str]:
|
||||
"""
|
||||
步骤8.75: 使用自定义回归模型进行参数预测
|
||||
|
||||
使用步骤6.75的自定义回归结果中的all_regression_results.csv文件,
|
||||
选择每个y_variable中r_squared最高的equation,
|
||||
使用采样点光谱数据计算水质指数,然后进行预测
|
||||
使用新的CustomRegressionPredictor模块,基于9_Custom_Regression_Modeling文件夹中的CSV,
|
||||
根据r_squared选择最佳模型,批量预测水质参数
|
||||
|
||||
Args:
|
||||
sampling_csv_path: 采样点光谱数据CSV路径(来自步骤7)
|
||||
formula_csv_file: 公式CSV文件路径,包含水质指数计算公式
|
||||
custom_regression_dir: 自定义回归结果目录(如果为None,使用步骤6.75的结果)
|
||||
formula_names: 要计算的公式名称列表,如果为None则计算所有公式
|
||||
prediction_column: 预测结果列名
|
||||
custom_regression_dir: 自定义回归模型目录(9_Custom_Regression_Modeling)
|
||||
formula_csv_path: 公式CSV文件路径,用于查找index_formula
|
||||
coordinate_columns: 坐标列名列表,默认为['longitude', 'latitude']或自动识别
|
||||
output_dir: 输出目录,默认为prediction_dir
|
||||
filename_prefix: 输出文件名前缀
|
||||
enabled: 是否启用
|
||||
skip_dependency_check: 是否跳过依赖检查
|
||||
|
||||
Returns:
|
||||
预测结果文件路径字典(键为y_variable名)
|
||||
预测结果文件路径字典(键为参数名)
|
||||
"""
|
||||
print("\n" + "="*80)
|
||||
print("步骤8.75: 使用自定义回归模型进行参数预测")
|
||||
@ -3848,135 +3861,54 @@ class WaterQualityInversionPipeline:
|
||||
self._record_step_time("步骤8.75: 自定义回归模型预测", step_start_time, step_end_time, status="skipped")
|
||||
return {}
|
||||
|
||||
# 检查公式CSV文件是否存在
|
||||
if not Path(formula_csv_file).exists():
|
||||
raise FileNotFoundError(f"公式CSV文件不存在: {formula_csv_file}")
|
||||
# 检查采样点CSV文件是否存在
|
||||
if not Path(sampling_csv_path).exists():
|
||||
raise FileNotFoundError(f"采样点CSV文件不存在: {sampling_csv_path}")
|
||||
|
||||
# 确定自定义回归结果目录
|
||||
# 确定自定义回归模型目录
|
||||
if custom_regression_dir is not None:
|
||||
final_regression_dir = custom_regression_dir
|
||||
else:
|
||||
default_regression_dir = str(self.custom_regression_dir)
|
||||
if Path(default_regression_dir).exists():
|
||||
final_regression_dir = default_regression_dir
|
||||
else:
|
||||
final_regression_dir = str(self.custom_regression_dir)
|
||||
if not Path(final_regression_dir).exists():
|
||||
if skip_dependency_check:
|
||||
raise ValueError("必须提供custom_regression_dir参数才能独立运行步骤8.75")
|
||||
else:
|
||||
raise ValueError("请先执行步骤6.75: 自定义回归分析,或提供custom_regression_dir参数")
|
||||
|
||||
# 读取all_regression_results.csv文件
|
||||
regression_results_path = Path(final_regression_dir) / "all_regression_results.csv"
|
||||
if not regression_results_path.exists():
|
||||
raise FileNotFoundError(f"未找到自定义回归结果文件: {regression_results_path}")
|
||||
|
||||
# 读取回归结果
|
||||
regression_df = pd.read_csv(regression_results_path)
|
||||
# 确定输出目录
|
||||
if output_dir is None:
|
||||
prediction_output_dir = str(self.prediction_dir)
|
||||
else:
|
||||
prediction_output_dir = output_dir
|
||||
|
||||
# 使用band_math.py计算水质指数
|
||||
print("正在使用采样点光谱数据计算水质指数...")
|
||||
from src.utils.band_math import BandMathCalculator
|
||||
|
||||
# 创建计算器实例
|
||||
calculator = BandMathCalculator(sampling_csv_path)
|
||||
|
||||
# 计算所有公式
|
||||
indices_df = calculator.process_formulas_from_csv(
|
||||
formula_csv_file,
|
||||
formula_names=formula_names,
|
||||
output_file=str(self.prediction_dir / "water_quality_indices.csv")
|
||||
# 创建CustomRegressionPredictor实例
|
||||
predictor = CustomRegressionPredictor(
|
||||
regression_models_dir=final_regression_dir,
|
||||
formula_csv_path=formula_csv_path,
|
||||
output_dir=prediction_output_dir
|
||||
)
|
||||
|
||||
if indices_df is None:
|
||||
raise ValueError("水质指数计算失败")
|
||||
# 运行批量预测
|
||||
print(f"开始使用自定义回归模块进行批量预测...")
|
||||
print(f" 采样点数据: {sampling_csv_path}")
|
||||
print(f" 回归模型目录: {final_regression_dir}")
|
||||
print(f" 输出目录: {prediction_output_dir}")
|
||||
|
||||
# 读取采样点数据(包含坐标信息)
|
||||
sampling_df = pd.read_csv(sampling_csv_path)
|
||||
|
||||
# 获取所有唯一的y_variable
|
||||
y_variables = regression_df['y_variable'].unique()
|
||||
|
||||
prediction_files = {}
|
||||
|
||||
for y_var in y_variables:
|
||||
try:
|
||||
# 筛选当前y_variable的所有回归结果
|
||||
y_var_results = regression_df[regression_df['y_variable'] == y_var]
|
||||
|
||||
# 找到r_squared最高的记录
|
||||
best_result = y_var_results.loc[y_var_results['r_squared'].idxmax()]
|
||||
|
||||
# 解析equation
|
||||
equation = best_result['equation']
|
||||
x_variable = best_result['x_variable']
|
||||
|
||||
print(f"为 {y_var} 选择最佳方程: {equation} (R² = {best_result['r_squared']:.4f})")
|
||||
|
||||
# 检查x_variable是否在水质指数数据中存在
|
||||
if x_variable not in indices_df.columns:
|
||||
print(f"警告: x_variable '{x_variable}' 不在水质指数数据中,跳过 {y_var}")
|
||||
continue
|
||||
|
||||
# 合并采样点坐标和水质指数数据
|
||||
# 假设采样点数据和水质指数数据有相同的行数和顺序
|
||||
if len(sampling_df) != len(indices_df):
|
||||
print(f"警告: 采样点数据({len(sampling_df)}行)和水质指数数据({len(indices_df)}行)行数不一致")
|
||||
# 只取前min(len(sampling_df), len(indices_df))行
|
||||
min_rows = min(len(sampling_df), len(indices_df))
|
||||
merged_df = pd.concat([
|
||||
sampling_df.iloc[:min_rows].reset_index(drop=True),
|
||||
indices_df.iloc[:min_rows].reset_index(drop=True)
|
||||
], axis=1)
|
||||
else:
|
||||
merged_df = pd.concat([sampling_df, indices_df], axis=1)
|
||||
|
||||
# 应用回归方程进行预测
|
||||
# 使用eval函数安全地计算表达式
|
||||
try:
|
||||
# 创建局部命名空间,包含需要的变量和数学函数
|
||||
local_vars = {x_variable: merged_df[x_variable].values}
|
||||
# 添加数学函数到命名空间
|
||||
import math
|
||||
local_vars.update({
|
||||
'exp': math.exp,
|
||||
'log': math.log,
|
||||
'log10': math.log10,
|
||||
'sqrt': math.sqrt,
|
||||
'sin': math.sin,
|
||||
'cos': math.cos,
|
||||
'tan': math.tan,
|
||||
'pi': math.pi,
|
||||
'e': math.e
|
||||
})
|
||||
|
||||
# 替换方程中的变量名为实际值
|
||||
# 这里需要确保方程格式正确,例如: "a*x + b"
|
||||
prediction_values = eval(equation, {"__builtins__": {}}, local_vars)
|
||||
|
||||
# 创建预测结果DataFrame
|
||||
result_df = merged_df[['UTM_X', 'UTM_Y']].copy()
|
||||
result_df[prediction_column] = prediction_values
|
||||
|
||||
# 保存预测结果
|
||||
output_filename = f"custom_regression_{y_var}.csv"
|
||||
output_path = str(self.prediction_dir / output_filename)
|
||||
result_df.to_csv(output_path, index=False)
|
||||
|
||||
prediction_files[y_var] = output_path
|
||||
print(f"成功为 {y_var} 生成预测结果: {output_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"应用方程 {equation} 进行预测时出错: {e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理 y_variable {y_var} 时出错: {e}")
|
||||
continue
|
||||
saved_files = predictor.run_batch_prediction(
|
||||
input_csv_path=sampling_csv_path,
|
||||
coordinate_columns=coordinate_columns,
|
||||
filename_prefix=filename_prefix
|
||||
)
|
||||
|
||||
step_end_time = time.time()
|
||||
self._record_step_time("步骤8.75: 自定义回归模型预测", step_start_time, step_end_time)
|
||||
|
||||
return prediction_files
|
||||
print(f"自定义回归预测完成,生成 {len(saved_files)} 个预测文件:")
|
||||
for param_name, filepath in saved_files.items():
|
||||
print(f" {param_name}: {filepath}")
|
||||
|
||||
return saved_files
|
||||
|
||||
except Exception as e:
|
||||
step_end_time = time.time()
|
||||
@ -4113,6 +4045,14 @@ def main():
|
||||
'prediction_column': 'prediction',
|
||||
'enabled': True # 是否启用非经验模型预测
|
||||
},
|
||||
'step8_75': {
|
||||
'custom_regression_dir': None, # 自定义回归模型目录(None表示使用9_Custom_Regression_Modeling)
|
||||
'formula_csv_path': None, # 公式CSV文件路径,用于查找index_formula(如water_quality_formulas.csv)
|
||||
'coordinate_columns': None, # 坐标列名(None表示自动识别)
|
||||
'output_dir': None, # 输出目录(None表示使用prediction_dir)
|
||||
'filename_prefix': 'custom_regression_prediction', # 输出文件名前缀
|
||||
'enabled': True # 是否启用自定义回归预测
|
||||
},
|
||||
'step9': {
|
||||
'boundary_shp_path': r"D:\BaiduNetdiskDownload\yaobao\roi\roi.shp" ,
|
||||
'resolution': 30,
|
||||
|
||||
Reference in New Issue
Block a user