☰
自来水厂水质预测与评估:LSTM与XGBoost双模型实战指南
2026/10/27 15:06:20 网站建设 项目流程

简介:2026亚太杯数学建模竞赛A题完整解决方案包,聚焦自来水厂水质预测与风险评估场景,面向参赛队长、编程新手与冲奖团队,提供从思路拆解到论文成稿的全链路材料。包内含特等奖规范格式的Word无水印论文,覆盖摘要、问题重述、模型假设、符号说明、模型建立与求解、灵敏度分析等完整章节;另附Python与MATLAB双版本硬核源码,涵盖数据清洗、模型训练与启发式寻优,模块化设计并带逐行中文注释,配合一键运行脚本与结果可视化绘图,编程小白也能复现。资源共77个文件,以py脚本、csv与xlsx数据表、docx论文和png可视化图表为主,辅以txt配置说明、md运行摘要、pdf附则及bat一键启动工具,压缩包整体仅1.59MB,轻量却覆盖数据、代码、论文、图表全流程,目录结构清晰。已有139人学习使用,可有效帮助建模团队快速产出高分论文并深入理解赛题数学本质。

1. 2026亚太杯A题自来水厂水质预测:这套资源到底在解决什么

第一次看到“2026亚太杯数学建模竞赛A题”这个标题时,我脑子里闪过的第一个念头是:这题是不是又把“预测”和“评估”两件事揉在一起了?等看完题面才发现,果然如此——自来水厂水质预测与评估,表面上是两个任务,实际上是一条完整的技术链路:先做特征分析,再做预测建模,最后把预测结果落到“水质是否达标”的评估结论上。这类题在近几年的数模竞赛里出现频率极高,因为它足够贴近工程实际,又有充足的数据处理空间,适合考察参赛者的数据清洗、特征工程和模型调参能力。

这套资源的核心价值,不在于“给了你多少份文件”,而在于帮你把从原始水质数据到最终评估报告的每一步都跑通。很多队伍卡在中间某个环节——要么数据预处理不规范导致后续模型全崩,要么选了不合适的算法导致精度上不去,要么论文写得像实验报告、缺乏建模逻辑。这篇笔记我会从建模思路、数据处理、核心代码、论文组织到资源选型,完整拆一遍,让你拿到这套资源后能直接用起来,而不是看着一堆代码文件不知道从哪下手。

适合谁看?准备参赛的本科生和研究生、想做水质预测但没摸清套路的新手,以及那些想快速复现一套完整“数据—模型—论文”流程的老手。下面按我实际做这类题的习惯顺序来拆。

2. 题目拆解与建模思路:先分清“预测”和“评估”两条主线

2.1 从题面提取关键任务:你要交付的到底是什么

亚太杯A题这类题目,通常会给出一个自来水厂的历史水质监测数据集,包含pH值、浊度、余氯、总硬度、溶解氧、化学需氧量(COD)、氨氮等常见指标,还可能附带温度、流量、季节、天气等环境变量。题面一般会提出两到三个问题,最典型的组合是:

  • 对某些关键水质指标进行未来若干天的预测;
  • 根据预测结果和现有标准,评估水质是否符合饮用水标准;
  • 可能再加一个附加题,比如找出影响水质的主控因子,或者做异常预警。

我一般拿到题先做一件事:把所有任务列成一张表格,明确每个任务的输入是什么、输出是什么、用什么标准去评价。比如预测任务,输入是历史序列和协变量,输出是未来N个时间点的预测值,评价标准是预测误差(RMSE、MAPE);评估任务,输入是预测值和国标限值,输出是“达标/不达标”的判定及超标时段定位。

这里有个常被忽略的点:预测和评估是串联关系的。预测的误差会直接传导到评估结论中。如果预测值本身就偏大或偏小,评估结果可能把达标水判成不达标。所以整套方案的误差控制要从前端数据清洗就开始,而不是等到模型阶段才补救。

2.2 特征工程与数据规范化:为什么热词里都在问“要数据规范化处理吗”

2026数学建模E题里有人问“需要数据规范化处理吗”,这个问题在A题里同样关键。几乎所有水质指标的量纲都不一致——pH在6到9之间,浊度可能到几NTU,COD可能几十mg/L,氨氮可能是零点几。如果不做规范化处理,树模型还能勉强应付,但神经网络、距离类模型(KNN、SVM)会直接因为数值范围差异而失灵。

我的处理顺序是这样的:先做缺失值处理,再做异常值检测,然后做量纲统一,最后根据模型需要决定是否做降维或特征筛选。规范化的具体手段不是随便选的——如果后续用LSTM这类深度模型,一般用Min-Max缩放把数据压到[0,1]区间,避免梯度爆炸;如果后续用XGBoost这类树模型,实际上不做缩放也行,但我通常还是做一下,因为后期可能要对比多种模型,统一数据预处理能让对比更公平。

特征工程上,水质数据有个独特优势:变量之间存在明显的物理关联。比如水中余氯含量和细菌总数呈负相关,浊度和COD有正相关趋势。这些先验知识可以直接用来构造特征,比如计算“余氯/浊度比”“氨氮/溶解氧比”等派生变量。另外,时间特征很重要——供水系统往往有昼夜节律和季节性周期,所以小时、星期、月份这些时间戳一定要拆出来,作为特征喂给模型。

3. 数据预处理与探索性分析:用代码把原始表“洗干净”

3.1 读取原始水质表:从字段检查到数据清洗的完整流水线

拿到水质数据集的第一步,不是急着建模,而是先把数据读进来、看结构、查质量。下面这段代码是我每次做这类题都会先跑一遍的“开场白”。

import pandas as pd import numpy as np import matplotlib.pyplot as plt # 读取CSV文件,注意指定编码,水质数据常有中文字段名 df = pd.read_csv('water_quality.csv', encoding='gbk') # 概览:行数列数、字段名、缺失情况 print('数据形状:', df.shape) print('字段列表:', df.columns.tolist()) print('缺失值统计:') print(df.isnull().sum()) # 对数值列做描述性统计,快速发现离谱的极值 desc = df.describe(percentiles=[.01, .05, .95, .99]) print(desc)

逻辑说明:df.shape告诉你数据规模,这在后面决定能不能直接用LSTM——如果只有几十条数据,深度学习模型根本没得玩。df.isnull().sum()是第一个关卡,水质表因为传感器故障或人工录入遗漏,缺值非常常见。df.describe()里的百分位数特别有用,如果某项指标的99%分位和最大值差距大到离谱,那基本可以断定存在异常值,比如浊度偶尔飙到几百,可能是传感器被污染了,不代表真实水质。

参数说明:encoding='gbk'要按实际文件编码调整,拿到的数据源如果是UTF-8就改成utf-8,读不进来时不要来回猜,用chardet库自动检测一次最稳妥。percentiles=[.01,.05,.95,.99]是我个人习惯,比默认的四分位数更能暴露尾部风险。

3.2 缺失值与异常值处理:不要放过任何一个“脏点”

缺失值的处理方式不能一刀切。水质指标的缺失有随机缺失和连续缺失两种:随机缺失一两行,可以用线性插值;如果某一天整个站点的记录全没了,那多半是设备停机,线性插值会失真,我倾向于用前一天同一时刻的值来补,保持时序的连续性。

异常值的判定我用最原始也最可靠的方法:物理范围检查叠加统计范围检查。

# 物理范围:超出国标或传感器量程的直接标为异常 bounds = { 'pH': (6.0, 9.5), 'turbidity_NTU': (0, 20), 'free_chlorine_mgL': (0, 5), 'COD_mgL': (0, 30) } for col, (low, high) in bounds.items(): mask = (df[col] < low) | (df[col] > high) print(f'{col}: 超范围 {mask.sum()} 行') # 统计学检测:3倍标准差法,适合无明显物理上限的指标 from scipy import stats z_scores = np.abs(stats.zscore(df['turbidity_NTU'].fillna(df['turbidity_NTU'].mean()))) outliers = np.where(z_scores > 3) print('浊度Z-Score异常:', df.iloc[outliers[0]]['turbidity_NTU'].head())

逻辑说明:物理范围检查是第一道防线,比如pH不可能小于0也不可能大于14,饮用水的pH标准更是在6.5到8.5之间,超出这个范围的直接判定为传感器异常。Z-Score法适合那些没有固定物理界的指标,但要注意——它默认数据近似正态分布,偏态强的指标(比如浊度)不建议直接套3倍标准差,可以用分位数法,取99%分位值以上为异常。

参数说明:Z-Score阈值取3,意味着数据在正态假设下有99.7%的置信区间,超过这个区间的就认为是异常。实际操作中,水质数据往往右偏严重,我会先做log变换再算Z-Score,效果比直接用原始值好很多。

4. 预测模型构建:用LSTM与XGBoost双轨并跑,找到最稳的方案

4.1 为什么我不只押一个模型:双模型对比的设计逻辑

水质预测问题,选模型首先要看数据量。以亚太杯A题历年的习惯来看,数据量大概在几千到几万条之间,这是一个“深度学习勉强能跑、传统机器学习表现良好”的尴尬区间。我的做法是双轨建模:LSTM负责捕捉时序上的长期依赖,XGBoost负责用特征交叉挖掘变量间非线性关系,最后对比两者的误差,择优用于评估环节。

这套做法的好处很明显:一是形成模型对比表,论文里有了“我们尝试了两种方法并比较”的说服力;二是某个模型翻车时还有后悔药,不会在比赛截止前发现预测结果不理想却无路可退。LSTM适合数据量够、时间序列节奏稳定的情况;XGBoost适合特征较多、样本量中等或偏小的场景,它对抗过拟合和缺失值的能力更强。

4.2 LSTM预测代码:滑窗构造监督学习样本

import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping from sklearn.preprocessing import MinMaxScaler # 假设已有处理好的DataFrame,只取数值列并规范化 values = df[['pH', 'turbidity_NTU', 'COD_mgL', 'free_chlorine_mgL']].values scaler = MinMaxScaler(feature_range=(0, 1)) scaled = scaler.fit_transform(values) # 滑窗构造序列样本:用过去72小时的多个指标预测未来24小时的浊度 def make_sequences(data, input_steps=72, pred_steps=24, target_idx=1): X, y = [], [] for i in range(len(data) - input_steps - pred_steps): X.append(data[i:i+input_steps, :]) # 输入窗口 y.append(data[i+input_steps:i+input_steps+pred_steps, target_idx]) # 预测目标窗口 return np.array(X), np.array(y) X, y = make_sequences(scaled) split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] model = Sequential([ LSTM(64, return_sequences=True, input_shape=(X.shape[1], X.shape[2])), Dropout(0.2), LSTM(32, return_sequences=False), Dropout(0.1), Dense(24, activation='linear') ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) history = model.fit( X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=32, callbacks=[early_stop], verbose=1 )

逻辑说明:make_sequences是LSTM建模最核心的步骤,它把原始的时序数据变成监督学习所需的“X到y”结构。这里输入窗口取72小时,预测窗口取24小时,是一种常见的多对多预测结构。target_idx=1指预测目标列是第1列,也就是浊度。Dropout层的作用是防止训练数据量偏小时发生过拟合,两个LSTM层的宽度递减,让模型先提取高层抽象再输出到全连接层。

参数说明:input_steps=72不是固定的,如果你发现供水水质有周周期性,可以试试168(7天);batch_size=32在样本量几千到几万时表现均衡,太大收敛不稳、太小训练易抖;patience=10是早停的耐心值,给了模型足够尝试的次数,又不会让它一直跑空转。

4.3 XGBoost预测代码:把时序问题改造成回归问题

import xgboost as xgb from sklearn.model_selection import TimeSeriesSplit # 构造特征表:过去N天的统计量 + 时间戳分解 feature_df = pd.DataFrame(index=df.index) feature_df['hour'] = df['time'].dt.hour feature_df['weekday'] = df['time'].dt.dayofweek for col in ['pH', 'turbidity_NTU', 'COD_mgL', 'free_chlorine_mgL']: for lag in [1, 3, 7, 14, 24, 48, 72]: feature_df[f'{col}_lag{lag}'] = df[col].shift(lag) feature_df[f'{col}_mean7'] = df[col].rolling(7).mean() feature_df[f'{col}_std7'] = df[col].rolling(7).std() feature_df['target'] = df['turbidity_NTU'].shift(-24) # 预测未来24小时的浊度 feature_df = feature_df.dropna() X = feature_df.drop(columns=['target']) y = feature_df['target'] # 时间序列交叉验证,防止随机打乱破坏时序 tscv = TimeSeriesSplit(n_splits=5) model_xgb = xgb.XGBRegressor( n_estimators=200, max_depth=5, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8 ) for train_idx, val_idx in tscv.split(X): X_train_cv, X_val_cv = X.iloc[train_idx], X.iloc[val_idx] y_train_cv, y_val_cv = y.iloc[train_idx], y.iloc[val_idx] model_xgb.fit(X_train_cv, y_train_cv, eval_set=[(X_val_cv, y_val_cv)], verbose=False) # 最终以最后一次验证输出为参考预测 y_pred_xgb = model_xgb.predict(X_val_cv)

逻辑说明:XGBoost不直接处理时序,但可以通过滞后特征和滑动统计量把时间信息编码进特征空间。这里构造了三个维度的特征:时间戳分解(小时、星期几)、多阶滞后值、滑动窗口统计量。滞后步长覆盖了3小时到72小时,能同时捕获短期波动和三天内的趋势。shift(-24)构造的是“用当前及历史信息预测未来24小时”的监督学习目标。

参数说明:n_estimators=200在这个量级的样本上够用,再多就要观察是否过拟合;max_depth=5控制单棵树的复杂度,水质数据非线性关系较强但也不至于深到10层以上;learning_rate=0.05配合200棵树,属于稳妥配置。TimeSeriesSplit是时序任务的关键——如果直接用train_test_split随机切分,未来信息会泄漏进训练集,预测结果会好看得虚假。

5. 避坑指南:从数据泄漏到论文排版,这5个坑最容易翻车

5.1 时间序列泄漏:预测结果虚高但你根本没发现

现象:验证集上RMSE低得惊人,画出来的预测曲线和真实曲线几乎重合,但一到测试集或者实际应用就彻底失灵。

原因:最常见的是随机切分数据集,或者特征构造时用到了未来的信息。比如用shift(-24)的滞后特征去预测当前值,等于拿未来数据预测现在,模型当然“完美”。另一个隐蔽场景是数据规范化时用了全局均值和方差——如果把测试集的数据也纳入scaler的计算范围,就相当于让模型“偷看”了未来分布。

解决:严格按时间顺序切分训练集和测试集,用TimeSeriesSplit或直接按日期切分。规范化时先fit在训练集上,再transform测试集,任何情况下不要对全量数据一次性fit_transform。我写代码时会在scaler处加一条注释提醒自己。

5.2 预测窗口错位:论文里的图对不上数据

现象:代码运行一切正常,但画预测效果图时发现预测曲线比真实曲线整体右移了一段,或者形状相同但相位不对。

原因:滑窗函数的pred_steps和目标变量构造不匹配。比如你用shift(-24)做目标,但画图时却把预测值和第24小时之前的数据对齐了。这个问题在LSTM建模里尤其常见,因为输出维度是24,很多人直接把预测结果按顺序平铺回原时间轴,忘记了错位。

解决:画图前先打印齐日期索引,确认预测值的起点确实是“输入窗口结束后的第1小时”。我会先在代码里做一轮对齐校验,把预测结果和真实序列的日期拉平后,再画对比图。别心疼这点时间和字数,论文里一张错位的图会让评委直接怀疑你的整个建模过程。

5.3 评估标准选错:用RMSE单独评价一个波动极大的指标

现象:模型的RMSE数值很小,但在某些极端时段误差却大到让水质评估结论反转。

原因:水质数据往往在大部分时间是平稳的,少数事件驱动时段(暴雨、管网清洗)波动极大。RMSE和MAE都是全局平均指标,它们会被多数平稳时段“均摊”掉误差,掩盖极端时段的糟糕表现。

解决:在论文里用多指标评估,RMSE、MAE之外增加MAPE和P95误差。P95误差表示预测误差最大的5%时段是怎么分布的,如果P95误差远超均值,就说明模型在波动期失效,需要考虑在特征中增加天气或流量等外部协变量。

5.4 论文写作变成“代码说明书”:评委要的是建模逻辑不是源码抄录

现象:论文正文大段粘贴代码、图少、表少,每一个模型都从原理讲起,占了大半篇幅,但关键决策(为什么选这个模型、为什么设这个参数)一句带过。

原因:很多参赛队伍把“完成代码”等同于“完成论文”,忽视了数学建模论文的核心是“问题分析—模型建立—求解—验证”的完整逻辑链。评委不是来读你代码的,他们要看的是你有没有把一个模糊的实际问题转成清晰的数学模型的能力。

解决:正文里不要出现完整代码段,只放关键公式和步骤描述。流程图和数据表放在正文位置,代码整理进附录。每一节开头先用一两句话说清楚“这一步在解决什么”,结尾写“结果如何、如果不理想怎么调整”。代码数量少但我踩过的这些坑一个比一个深刻。

5.5 数据规范化参数被复用:训练好的模型在赛后“作废”

现象:比赛结束后想用训练好的模型预测新的监测数据,结果预测值全部落在0到1之间,无法还原成真实量纲。

原因:MinMaxScaler的逆变换参数只保存在当时的数据范围里。如果新数据的最小值或最大值超出了训练数据的范围,inverse_transform得到的结果就会失真。

解决:把scaler对象保存下来(pickle.dump或joblib.dump),预测时加载同一个scaler做逆变换。另外在上交项目文件时,要同时附上一份“模型复现说明”,写清楚输入数据结构、特征顺序和scaler路径,否则别人拿到你的代码根本跑不出来。

6. 资源落地与进阶验证:这套资料怎么用才能发挥最大价值

拿到这套“完美解题思路+完整核心代码+高分无水印word论文+项目文件结果图”的资源包时,我建议按下面的顺序使用,而不是直接打开论文开始抄。

第一步是看解题思路文档。重点看它的建模逻辑链:它把题目拆成了哪几个问题、每个问题用什么模型、模型之间的输入输出怎么衔接。二三等队伍和一等队伍的差距,往往不在代码技巧,而在这一步的逻辑严密程度。第二步才是对照核心代码复盘。不要直接运行完看结果就完事,要用前面讲的评价维度去判断这套代码的工程质量——它是否用了TimeSeriesSplit?scaler是否只fit在训练集上?特征构造有没有泄漏?如果这些点都有,才算一套值得依赖的代码。第三步是参考论文的结构布局,看它怎么组织图表、怎么叙述模型选择依据,而不是逐句复制。

最后说一个我个人的习惯:拿到任何一套竞赛资源和代码,我都会先做一轮“最小重写”,就是把核心模型的训练流程用自己的方式重新实现一遍,哪怕代码结构和原来一样,我也要手敲一遍。这么做一是能确认自己真的理解了每一步的含义,二是比赛现场遇到突发问题才能独立排查。依赖别人的思路和代码但自己全无手感,上了赛场一旦环境或数据稍有变化,就只能干瞪眼。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询