ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从数据到模型:基于GAM与随机森林的海盐气溶胶-云关系建模实战

2026/8/22 5:45:18 拓冰建站 浏览量
从数据到模型:基于GAM与随机森林的海盐气溶胶-云关系建模实战 1. 项目概述与核心问题拆解“云中的海盐”这个题目听起来就很有画面感也带着点物理和化学交叉的味道。这其实是2024年“认证杯”数学建模网络挑战赛第二阶段C题一个典型的基于实际观测数据的环境科学建模问题。简单来说就是给你一堆关于海盐气溶胶简单理解就是被风带到空中的微小海盐颗粒和云特性的数据让你去挖掘它们之间隐藏的关系并建立一个能够预测或解释这种关系的数学模型。海盐气溶胶是大气中非常重要的凝结核云滴的形成往往离不开它们。题目考察的核心就是如何从看似杂乱的数据中提炼出有效的数学关系并用严谨的模型将其表达出来。这不仅仅是一个编程实现问题更是一个完整的“问题分析 - 模型构建 - 算法实现 - 结果解释”的科研流程模拟。对于参赛队伍而言清晰的思路、合理的模型选择、以及稳健的代码实现是拿下高分的关键。无论是用Matlab的矩阵运算和丰富工具箱还是用Python的Pandas、Scikit-learn等强大生态工具只是手段背后的数学思想和物理洞察才是灵魂。接下来我会以一个过来人的视角拆解这道题的解题脉络并分享在Matlab和Python两种环境下实现核心步骤的代码与心得。我会重点讲清楚“为什么这么做”而不仅仅是“怎么做”。2. 解题核心思路与模型选型分析面对这类数据驱动的赛题第一步永远是理解数据和问题本质而不是急着写代码。题目通常会提供多个数据文件可能包括不同高度、不同时间点的海盐气溶胶浓度、云滴数浓度、云液态水含量、风速风向等。2.1 数据探索与关系初判拿到数据后首要任务是进行探索性数据分析。我们的目标是寻找海盐气溶胶与云参数如云滴有效半径、云光学厚度之间的潜在关系。这种关系可能是线性的也可能是非线性的可能受其他气象条件如相对湿度、垂直速度的调制。常用手段包括散点图矩阵快速可视化所有变量两两之间的关系初步判断相关性。相关系数分析计算皮尔逊相关系数、斯皮尔曼秩相关系数等量化线性或单调关系的强度。这里要注意高相关系数不代表因果关系但能提供重要的建模线索。条件筛选分析例如分别分析在高相对湿度和低相对湿度条件下海盐浓度与云滴浓度的关系有何不同。注意大气数据往往存在较强的自相关性和时空相关性。直接使用普通最小二乘回归可能会严重低估参数的不确定性甚至得到虚假关系。必须考虑数据的特性。2.2 模型选型策略根据探索分析的结果我们可以选择不同的建模路径路径一统计回归模型如果关系相对明确且希望模型具有较好的可解释性统计回归是首选。多元线性回归假设关系是线性的。这是基础但往往过于简单。广义加性模型当怀疑存在非线性关系时GAM允许对每个预测变量使用平滑函数如样条形式为Y β0 f1(X1) f2(X2) ... ε。它能以非参数的方式捕捉复杂关系结果依然可解释。这是本题一个非常有力的候选模型。分位数回归不仅关注条件均值还关注条件分布的不同分位数如中位数、90%分位数。这对于研究极端情况例如高海盐浓度下云特性的上限特别有用。路径二机器学习模型如果关系非常复杂、非线性且交互作用强或者预测精度是首要目标可以考虑机器学习方法。随机森林能自动处理非线性关系和特征交互提供特征重要性排序帮助理解哪些变量最关键。梯度提升机如XGBoost、LightGBM预测精度通常很高同样能输出特征重要性。神经网络对于极高维、极度复杂的关系有强大拟合能力但需要更多数据且可解释性差在数学建模中需谨慎使用必须有充分的物理依据支撑。路径三机理模型参数化这是最高阶的思路。即建立一个简化的云微物理过程方程然后用数据去反演或校准方程中的关键参数。例如建立云滴数浓度与气溶胶浓度、垂直速度之间的参数化关系。这需要更深厚的大气物理知识但一旦做成论文的创新性和深度会非常突出。我的建议对于大多数队伍采用“探索性分析 GAM建模 随机森林辅助验证/特征选择”的组合拳是比较稳妥且能体现层次感的策略。先用GAM获得可解释的平滑关系曲线再用随机森林验证这些关系的预测能力并确认关键变量。3. 数据预处理与特征工程实操详解模型未动数据先行。原始数据几乎不可能直接扔进模型预处理和特征工程的质量直接决定了模型的天花板。3.1 数据清洗与合并通常数据分多个文件第一步是正确读取并按照时间、站点、高度等关键维度进行对齐合并。Python (Pandas) 示例import pandas as pd import numpy as np # 假设有两个CSV文件 df_aerosol pd.read_csv(sea_salt_aerosol.csv) df_cloud pd.read_csv(cloud_properties.csv) # 查看数据基本信息、缺失值 print(df_aerosol.info()) print(df_aerosol.isnull().sum()) # 假设通过‘time’和‘altitude’列进行合并 df_merged pd.merge(df_aerosol, df_cloud, on[time, altitude], howinner) # 内连接只保留同时有数据的时刻 print(f“合并后数据形状{df_merged.shape}”) # 处理缺失值 - 根据情况选择方法 # 方法1删除缺失行若缺失不多 df_cleaned df_merged.dropna() # 方法2填充缺失值需谨慎 # 例如用同一高度层的前后时刻均值填充 df_merged[concentration] df_merged.groupby(altitude)[concentration].transform(lambda x: x.fillna(x.rolling(window3, min_periods1, centerTrue).mean()))Matlab 示例% 读取数据 aerosol_table readtable(sea_salt_aerosol.csv); cloud_table readtable(cloud_properties.csv); % 查看数据 summary(aerosol_table); % 查找缺失值 missing_sum sum(ismissing(aerosol_table)); % 基于关键列合并表格 merged_table innerjoin(aerosol_table, cloud_table, Keys, {time, altitude}); disp([合并后数据大小, num2str(size(merged_table))]); % 处理缺失值 - 删除 merged_table_cleaned rmmissing(merged_table); % 或者进行填充移动平均示例 altitudes unique(merged_table.altitude); for alt altitudes idx merged_table.altitude alt; conc merged_table.concentration(idx); conc_filled fillmissing(conc, movmean, 3); % 3点移动平均填充 merged_table.concentration(idx) conc_filled; end3.2 特征工程创造价值原始变量可能不够我们需要创造更有预测力的特征。物理衍生变量垂直积分量将对流层内各高度的海盐浓度积分得到柱浓度这可能与整层云的效应关联更强。梯度/差分计算浓度随高度的梯度 (dC/dz)可能反映输送或沉降过程。相对湿度调整浓度海盐气溶胶的吸湿增长效应显著可尝试用相对湿度函数如Köhler理论简化式对浓度进行标校。交互项如果怀疑风速和浓度共同影响云可以创建风速 * 浓度作为新特征。时间序列特征如果数据是时间序列可以引入滞后项前一时次的浓度、移动平均等。实操心得特征工程不是越多越好。每创建一个新特征都要思考其物理意义。可以先基于物理直觉创建一批然后通过后续的特征重要性分析进行筛选避免维度灾难和过拟合。4. 广义加性模型GAM的构建与解读我们以GAM作为核心模型进行详细演示。GAM的优点在于它能用平滑曲线拟合每个变量的效应让我们“看到”数据中的非线性模式。4.1 Python实现使用pygam库首先安装库pip install pygamfrom pygam import LinearGAM, s, f import matplotlib.pyplot as plt # 假设我们的数据 # X: 特征矩阵包含‘sea_salt_conc’ ‘RH’ ‘wind_speed’ ‘altitude’ # y: 目标变量如‘cloud_droplet_number’ X df_cleaned[[sea_salt_conc, RH, wind_speed, altitude]].values y df_cleaned[cloud_droplet_number].values # 构建GAM模型 # s() 表示平滑项样条f() 表示因子项分类变量。这里假设altitude是连续变量。 gam LinearGAM(s(0) s(1) s(2) s(3)) # 对4个特征都使用平滑项 gam.gridsearch(X, y) # 自动搜索最佳的平滑项惩罚参数lam防止过拟合 # 模型摘要 print(gam.summary()) # 绘制部分依赖图Partial Dependence Plot——这是理解GAM的关键 fig, axs plt.subplots(1, 4, figsize(16, 4)) titles [Sea Salt Conc, Relative Humidity, Wind Speed, Altitude] for i, ax in enumerate(axs): XX gam.generate_X_grid(termi) # 为第i个特征生成网格数据 ax.plot(XX[:, i], gam.partial_dependence(termi, XXX)) ax.plot(XX[:, i], gam.partial_dependence(termi, XXX, width.95)[1], cr, ls--) # 绘制置信区间 ax.set_title(titles[i]) ax.set_xlabel(titles[i]) ax.set_ylabel(Partial Dependence) plt.tight_layout() plt.show() # 预测与评估 from sklearn.metrics import r2_score, mean_squared_error y_pred gam.predict(X) r2 r2_score(y, y_pred) rmse np.sqrt(mean_squared_error(y, y_pred)) print(fR²: {r2:.3f}, RMSE: {rmse:.3f})代码解读gridsearch是关键步骤它通过交叉验证寻找每个平滑项的最佳平滑度参数lam平衡拟合优度与模型复杂度。绘制的部分依赖图直观展示了在控制其他变量不变时目标变量随单个特征变化的“纯”效应。如果曲线是直线说明是线性关系如果是曲线则揭示了非线性。4.2 Matlab实现使用fitrgam函数Matlab的Statistics and Machine Learning Toolbox提供了fitrgam函数非常方便。% 准备数据 tbl merged_table_cleaned; % 清理后的表 predictorNames {sea_salt_conc, RH, wind_speed, altitude}; responseName cloud_droplet_number; % 将分类变量如果有指定为categorical % tbl.altitude categorical(tbl.altitude); % 如果高度是离散层可作为分类变量 % 拟合GAM模型 % ‘PredictorsForSmooth’指定哪些预测变量使用平滑项 gamMdl fitrgam(tbl, responseName, ... PredictorNames, predictorNames, ... PredictorsForSmooth, [1 2 3 4], ... % 对第1,2,3,4个预测变量使用平滑项 OptimizeHyperparameters, auto, ... % 自动优化平滑参数和交互项 HyperparameterOptimizationOptions, struct(Verbose, 0, ShowPlots, false)); % 查看模型详情 disp(gamMdl) % 绘制部分依赖图 figure; subplot(2,2,1); plotPartialDependence(gamMdl, 1); % 第一个预测变量 title(Partial Dependence on Sea Salt Conc); xlabel(Sea Salt Conc); ylabel(Partial Dependence); subplot(2,2,2); plotPartialDependence(gamMdl, 2); title(Partial Dependence on RH); subplot(2,2,3); plotPartialDependence(gamMdl, 3); title(Partial Dependence on Wind Speed); subplot(2,2,4); plotPartialDependence(gamMdl, 4); title(Partial Dependence on Altitude); % 预测与评估 y_pred predict(gamMdl, tbl); y_true tbl.(responseName); r2 1 - sum((y_true - y_pred).^2) / sum((y_true - mean(y_true)).^2); rmse sqrt(mean((y_true - y_pred).^2)); fprintf(R²: %.3f, RMSE: %.3f\n, r2, rmse);代码解读Matlab的fitrgam自动化程度很高‘OptimizeHyperparameters’选项可以自动寻找最佳模型结构包括是否添加交互项。plotPartialDependence函数能直接生成美观的部分依赖图是分析模型结果的神器。5. 随机森林模型用于验证与特征分析GAM给了我们可解释的关系但我们还需要验证这些关系的预测能力并确认特征的重要性。随机森林非常适合这个任务。5.1 Python实现使用scikit-learnfrom sklearn.ensemble import RandomForestRegressor from sklearn.inspection import permutation_importance from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 训练随机森林模型 rf_model RandomForestRegressor(n_estimators100, random_state42, n_jobs-1) rf_model.fit(X_train, y_train) # 评估 train_score rf_model.score(X_train, y_train) test_score rf_model.score(X_test, y_test) print(f训练集R²: {train_score:.3f}) print(f测试集R²: {test_score:.3f}) # 特征重要性基于基尼不纯度减少 importances rf_model.feature_importances_ feature_names [sea_salt_conc, RH, wind_speed, altitude] plt.figure(figsize(8,5)) plt.barh(feature_names, importances) plt.xlabel(Feature Importance (Gini)) plt.title(Random Forest Feature Importance) plt.tight_layout() plt.show() # 排列重要性更可靠衡量特征对模型性能的实际影响 perm_result permutation_importance(rf_model, X_test, y_test, n_repeats10, random_state42, n_jobs-1) sorted_idx perm_result.importances_mean.argsort() plt.figure(figsize(8,5)) plt.boxplot(perm_result.importances[sorted_idx].T, vertFalse, labelsnp.array(feature_names)[sorted_idx]) plt.xlabel(Permutation Importance (decrease in R²)) plt.title(Permutation Importance on Test Set) plt.tight_layout() plt.show()5.2 Matlab实现% 划分数据需要Statistics and Machine Learning Toolbox rng(42); % 设置随机种子保证可重复性 cv cvpartition(height(tbl), HoldOut, 0.2); idxTrain training(cv); idxTest test(cv); tblTrain tbl(idxTrain, :); tblTest tbl(idxTest, :); % 训练随机森林 rfMdl fitrensemble(tblTrain, responseName, ... Method, Bag, ... % Bagging即随机森林 NumLearningCycles, 100, ... Learners, tree); % 评估 y_pred_train predict(rfMdl, tblTrain); y_pred_test predict(rfMdl, tblTest); y_true_train tblTrain.(responseName); y_true_test tblTest.(responseName); r2_train 1 - sum((y_true_train - y_pred_train).^2) / sum((y_true_train - mean(y_true_train)).^2); r2_test 1 - sum((y_true_test - y_pred_test).^2) / sum((y_true_test - mean(y_true_test)).^2); fprintf(训练集R²: %.3f\n, r2_train); fprintf(测试集R²: %.3f\n, r2_test); % 特征重要性OOB Permuted Predictor Importance [imp, oobPred] oobPermutedPredictorImportance(rfMdl); figure; barh(imp); set(gca, YTickLabel, predictorNames); xlabel(Out-of-Bag Feature Importance); title(Random Forest OOB Feature Importance);结果解读与联动分析 比较GAM的部分依赖图和随机森林的特征重要性可以得到强有力的结论。一致性验证如果某个变量如海盐浓度在GAM的部分依赖图中表现出强烈的非线性效应同时在随机森林的排列重要性中排名很高那么我们就非常有信心认为该变量是影响云特性的关键因素。发现交互作用随机森林能天然捕捉交互作用。如果两个变量一起使用比单独使用能带来更大的重要性提升暗示它们可能存在交互。此时可以在GAM中尝试加入交互项如s(x1, x2)看看模型是否显著改善。模型稳健性检查随机森林在测试集上的表现R²可以作为模型预测能力的基准。如果GAM的预测性能与之接近说明我们基于物理的可解释模型并没有损失太多预测精度这是非常理想的结果。6. 模型诊断、优化与结果可视化模型建好后不能只看R²必须进行严格的诊断。6.1 残差分析检查残差预测值与真实值之差是否随机分布是检验模型是否捕捉到所有系统信息的黄金标准。# Python 残差分析 residuals y_test - y_pred_test fig, axes plt.subplots(1, 2, figsize(12, 4)) # 残差 vs 预测值 axes[0].scatter(y_pred_test, residuals, alpha0.5) axes[0].axhline(y0, colorr, linestyle--) axes[0].set_xlabel(Predicted Values) axes[0].set_ylabel(Residuals) axes[0].set_title(Residuals vs. Predicted) # 残差分布QQ图 import scipy.stats as stats stats.probplot(residuals, dist“norm”, plotaxes[1]) axes[1].set_title(Q-Q Plot of Residuals) plt.tight_layout() plt.show()理想情况下残差应随机分布在0线周围无明显的趋势或异方差性即散点图的“漏斗”形状。Q-Q图上的点应大致落在对角线上表明残差近似正态分布。6.2 模型优化与对比如果发现残差存在模式说明模型有改进空间。添加交互项在GAM中可以尝试s(sea_salt_conc, RH)来捕捉海盐效应随湿度的变化。尝试不同的分布族如果目标变量是计数数据如云滴数可以考虑使用泊松GAM (PoissonGAM) 或负二项式GAM。变量变换对高度偏态的特征如浓度取对数可能使关系更接近线性改善模型性能。可以建立一个简单的模型对比表格模型特征交互项测试集R²残差诊断解释性线性回归原始变量无0.65存在非线性趋势高GAM (基础)平滑项无0.78随机轻微异方差高GAM (带交互)平滑项海盐*湿度0.82随机无异方差中高随机森林原始变量自动0.85随机低6.3 高级可视化三维部分依赖图对于重要的交互项可以用三维图可视化。# 绘制海盐浓度和相对湿度对云滴数的交互效应假设我们有一个支持交互的GAM模型 # 这里使用预测网格的方法 import numpy as np from mpl_toolkits.mplot3d import Axes3D # 创建网格 x1_grid np.linspace(X[:,0].min(), X[:,0].max(), 30) # 海盐浓度 x2_grid np.linspace(X[:,1].min(), X[:,1].max(), 30) # 相对湿度 xx1, xx2 np.meshgrid(x1_grid, x2_grid) # 为了预测需要其他变量的平均值 X_grid np.zeros((xx1.ravel().shape[0], X.shape[1])) X_grid[:, 0] xx1.ravel() X_grid[:, 1] xx2.ravel() X_grid[:, 2] X[:,2].mean() # 风速固定为均值 X_grid[:, 3] X[:,3].mean() # 高度固定为均值 # 使用GAM模型预测 Z gam.predict(X_grid).reshape(xx1.shape) # 绘图 fig plt.figure(figsize(10,7)) ax fig.add_subplot(111, projection3d) surf ax.plot_surface(xx1, xx2, Z, cmapviridis, alpha0.8) ax.set_xlabel(Sea Salt Concentration) ax.set_ylabel(Relative Humidity) ax.set_zlabel(Predicted Cloud Droplet Number) ax.set_title(Interaction Effect: Sea Salt and RH on Cloud Droplets) fig.colorbar(surf, shrink0.5, aspect5) plt.show()这样的三维图能清晰展示在高湿度和高海盐浓度的共同作用下云滴数可能呈现指数增长这符合云物理的直觉高湿度下海盐颗粒吸湿增长更容易活化成为云滴。7. 赛题论文写作要点与代码整合建议数学建模竞赛模型和代码只占一半论文写作是另一半。针对“云中的海盐”这类题目论文需要突出以下几点清晰的科学问题开篇明义指出本研究旨在量化海盐气溶胶对云微物理特性的影响并探究其非线性关系及环境调制因素。数据驱动的分析流程用流程图展示“数据预处理 - 探索性分析 - 模型选型与构建 - 验证与诊断 - 结论”的完整链条。模型的物理解释不要只展示数学公式和代码结果。重点解释部分依赖图的形状为什么海盐浓度在低值时效应增长快高值时饱和为什么相对湿度存在一个阈值效应将这些与Köhler理论、气溶胶活化等云物理知识联系起来。不确定性讨论承认模型的局限性。例如数据时空代表性、未考虑的潜在混杂因子如其他类型气溶胶、模型的泛化能力等。代码附录将核心、简洁、可读性高的代码放在附录。切忌粘贴全部代码。只放关键步骤如数据合并、GAM拟合、特征重要性计算和主要可视化代码。加上必要的注释。代码整合与提交建议Python建议使用Jupyter Notebook或Python脚本将分析过程模块化数据加载、预处理、建模、可视化分别写成函数或类。最终提交一个.ipynb文件或一个包含main.py和requirements.txt的文件夹。Matlab建议使用Live Script (.mlx)它能将代码、输出和说明文字完美结合非常适合撰写报告。也可以编写多个.m函数文件和一个主脚本。版本控制使用Git如Github Desktop管理代码版本这是一个加分的好习惯。8. 常见问题与避坑指南在实际操作中一定会遇到各种问题。这里记录几个典型的“坑”和解决办法。问题1数据量纲差异大导致模型不稳定或特征重要性有偏。现象风速m/s和浓度μg/m³数值范围差几个数量级影响基于距离的模型如SVM、KNN和基于树的模型的分裂。解决进行特征标准化。对于线性/广义加性模型标准化可以使系数具有可比性。对于树模型虽然理论上不需要但实践中标准化有时能加速训练。使用sklearn.preprocessing.StandardScaler(Python) 或zscore函数 (Matlab)。问题2GAM模型过拟合或欠拟合。现象部分依赖曲线锯齿状波动剧烈过拟合或几乎是一条水平线欠拟合。解决关键在于平滑参数lam的选取。务必使用交叉验证如gridsearch自动选择。pygam的gridsearch和Matlab的‘OptimizeHyperparameters’就是干这个的。也可以手动尝试增大lam值惩罚更重曲线更平滑或减小lam值。问题3随机森林在训练集上表现完美测试集上很差。现象训练集R²接近1测试集R²只有0.6。解决这是典型的过拟合。降低树的最大深度 (max_depth)。增加分裂所需的最小样本数 (min_samples_split,min_samples_leaf)。增加特征随机选择的数目max_features通常设为sqrt(n_features)。使用交叉验证调整这些超参数。问题4部分依赖图显示的关系与物理常识相悖。现象比如显示风速越大云滴数越少这与常识风大可能输送更多海盐不符。排查检查共线性风速可能与其他变量如湿度高度相关导致效应被“转移”。计算方差膨胀因子(VIF)或查看相关矩阵。检查交互作用可能风速的效应只在特定湿度条件下才显著。尝试绘制条件部分依赖图或在模型中添加交互项。检查数据质量该风速数据是否可靠是否存在大量缺失或异常值问题5Matlab和Python结果有细微差异。现象同一算法在两个平台上算出的R²或系数略有不同。原因这是正常的。随机数种子不同、算法底层实现的细微差别、浮点数计算精度等都会导致差异。只要差异不大例如R²相差小于0.01就无需担心。关键是确保分析流程和结论一致。务必在代码开头设置随机种子如random_state42in Python,rng(42)in Matlab以保证可重复性。最后再分享一个我个人的小技巧在论文中展示结果时将GAM平滑曲线与原始数据的散点图叠加在一起。这能非常直观地向评委证明你的模型不是“黑箱”而是紧密贴合数据趋势的同时又能提炼出数据背后的平滑规律这恰恰是数学建模能力的体现。