递归模糊神经网络在污水处理溶解氧控制中的应用与工程部署
简介:这是发表于《化工学报》的一篇学术论文,面向污水处理智能控制、神经网络建模方向的研究人员与工程师。针对污水处理过程的非线性、大时变问题,论文提出基于递归模糊神经网络的控制方法。控制器在常规反向传播算法基础上引入学习率自适应学习算法和动量项来训练网络参数,避免陷入局部最优,从而自适应获得溶解氧和硝态氮等操作变量的控制精度。资源包含1个PDF文件,压缩包约960KB,内容涵盖方法原理、网络结构、基于BSM1仿真平台的动态实验,以及与PID、前馈神经网络、常规递归神经网络的对比分析。已有121人学习,适合需要借鉴智能控制算法设计、复现动态仿真实验或撰写相关论文的读者。文中还给出了网络参数更新公式和实验曲线,便于对照复现。
1. 污水处理控制为什么需要递归模糊神经网络:进水波动与DO失调的现场矛盾
污水处理厂的难处不在设备,而在进水的水质水量一天能变好几个来回:早高峰流量冲击让曝气池需氧量成倍翻动,可生化性的变化让同一套曝气量下溶解氧DO的响应完全不同。传统控制策略多是PID按当前偏差调节曝气量,遇到分钟级的负荷突变时,往往刚调过头又来一波,DO波动高企,电耗下不去。基于递归模糊神经网络的污水处理控制方法,核心思路就是用一个带记忆的模糊推理模型替代纯经验调节——把进水量、COD、氨氮、ORP这些在线仪表信号吃进去,提前估算当前工况下合理的曝气设定值。它适合从“滞后调节”转向“按需供氧”的污水厂自控改造,也适合做仿真方案对比的研究人员。
2. 递归模糊神经网络怎么适配污水场景:从对象特性到模型结构选择
2.1 先看被控对象:曝气池溶解氧的非线性、时变与大滞后特性
溶解氧控制不是一个能用固定传递函数描述的对象。曝气池里氧的传递速率与曝气量、水温、污泥浓度、微生物活性同时相关,过程增益会随季节和污泥龄漂移。更麻烦的是大滞后:鼓风机调整风量后,DO探头要经过混合和传感器响应才能反映变化,这个滞后短则三五分钟,长则十几分钟。PID的局限性在于它只能等偏差出现再动作,当进水冲击负荷到来时,DO已经掉了再补风,气量永远慢半拍。
递归模糊神经网络恰好补上这个短板。它对非线性的拟合能力强,递归结构能把过去若干时刻的工况信息保留在内部状态里,在DO实际下降之前就根据进水趋势给出前馈修正。我在做这类方案时,第一步不是调网络结构,而是先花时间理解对象的大滞后时间常数,这决定了后面滑窗长度和递归步数的设置。
2.2 模糊规则给非线性兜底,递归连接给时序记忆:RFNN的结构拆解
递归模糊神经网络有三个关键部分。第一是模糊化层,每个输入变量通过高斯隶属度函数映射到若干个模糊集合,比如进水流量可以分为“低、中、高”,COD可以分为“低、高”。第二是规则层,每条规则把多个输入条件用取小或乘积的方式组合起来,得到规则的激活强度。第三是输出层,加权平均去模糊化后得到预测值。递归连接一般加在规则层,让当前时刻的激活强度同时受上一时刻激活强度的影响,形成Elman型结构。
这种结构对污水场景的意义在于,模糊规则本身可解释——可以知道当前是“高流量+高负荷+中ORP”的规则被激活,而不是像深度网络那样完全黑匣子;递归部分则记住了过去十分钟进水负荷的变化趋势,而不是只盯着当前截面的数值。训练时用带动量的梯度下降,误差从输出层反传,同时更新隶属度参数、规则后件权重和递归权重。
2.3 为什么要选RFNN而不是LSTM或普通BP网络:部署场景里的取舍
做技术选型时,经常被问“直接上LSTM不是更好吗”。从纯精度角度,LSTM确实可能表现更好,但污水厂现场有实际约束:历史数据量不大,DCS/PLC侧对复杂模型的支持有限,运行人员需要能解释模型行为。LSTM参数多、需要大量数据、调试周期长,而普通BP网络没有记忆能力,本质上把时间序列当成独立样本,遇到水质波动时预测误差明显。
RFNN处在两者之间:参数规模适中,几十个到上百个可训练参数就能工作;模糊规则让人能看懂“什么工况下模型会给出高曝气指令”;部署上可以训练完导出规则参数,在工控上位机里用C或Python重新实现推理。下表是我常用的选型对照:
| 方案 | 结构复杂度 | 时序记忆 | 可解释性 | 最小数据量 | 部署友好度 |
|---|---|---|---|---|---|
| PID | 低 | 无 | 好 | 不需要 | 最高 |
| BP网络 | 中 | 无 | 中 | 中等 | 高 |
| LSTM | 高 | 强 | 差 | 大 | 低 |
| RFNN | 中 | 中 | 好 | 中等 | 中 |
3. 数据准备阶段:输入特征筛选与训练集的工程处理
3.1 选哪些特征进模型:进水流量、COD、氨氮、pH与ORP的组合
我在做这类建模时,不会把能拿到的几十个点位全部灌进模型。冗余特征会增加模糊规则数量,规则一多,训练参数膨胀,过拟合风险成倍上升。常用的做法是选六到八个与曝气需氧量直接相关的在线变量:进水流量、进水COD、进水氨氮、pH、ORP、水温、风机电流、当前DO。这些变量在污水厂基本都有在线仪表,不需要额外化验,采样周期统一为5分钟。
出水指标不建议作为模型输入。出水COD或出水氨氮本身是受控量,它们对曝气的指导有数小时级别的滞后,模型训练时输出和输入之间的因果关系很难对齐。特征初筛用皮尔逊相关系数看一遍与未来DO变化的相关性,保留相关度较高的几个,相关性弱的直接剔除,减少规则层的压力。
3.2 用Python做时间对齐、离群点清理与归一化
仪表原始数据不会干净。不同仪表的时钟漂移、探头清洗时的异常跳变、传输中断导致的空洞,都需要先处理。常见做法是先按5分钟重采样,用线性插值补齐小空洞,再用滑动窗口的均值与标准差做离群点剔除,最后归一化。归一化时只使用训练集的统计量,这一点非常关键,稍后会重点说。
import pandas as pd import numpy as np df = pd.read_csv("wwtp_history.csv", parse_dates=["time"]) df = df.set_index("time").resample("5min").mean().interpolate(limit=3) cols = ["inflow", "cod_in", "nh4_in", "orp", "do", "blower_current"] df = df[cols] for col in cols: mu = df[col].rolling(60, min_periods=20).mean() sigma = df[col].rolling(60, min_periods=20).std() df.loc[(df[col] - mu).abs() > 3 * sigma, col] = np.nan df = df.interpolate(method="linear", limit=5).dropna() split_idx = int(len(df) * 0.7) train_df = df.iloc[:split_idx].copy() test_df = df.iloc[split_idx:].copy() norm_stats = {} for col in cols: amin, amax = train_df[col].min(), train_df[col].max() norm_stats[col] = (amin, amax) train_df[col] = (train_df[col] - amin) / (amax - amin) test_df[col] = (test_df[col] - amin) / (amax - amin)这段代码第一段先把时间列解析并重采样到5分钟网格,limit=3控制只插补连续缺失不超过3个点的空洞,超过就保留NaN,避免把长时间断档硬填成假数据。第二段用过去60个点的滚动统计量做3σ剔除,探头清洗或信号跳变会在这里被置空,随后再次插补。最后一步切分训练集和测试集,并且只用训练集的min/max做归一化,测试集复用同一套参数,保证推理时的输入口径一致。
3.3 训练测试集按时间顺序切分:防止数据泄漏
很多初学者用train_test_split默认的随机切分方式处理时间序列,这是一个在污水场景里非常容易踩的坑。随机切分会让模型“偷看”到未来数据——测试集里的样本可能紧挨着训练集里的样本,模型等于见过几乎相同的时间段,验证结果虚高,一上现场就原形毕露。
正确做法是严格按时间先后切分:前70%做训练,后30%做测试。如果现场有季节性差异,比如冬季和夏季的微生物活性差别大,可以再多留一年跨季节的数据单独做泛化验证。另外,构造滑窗样本时也要注意窗口之间不要跨越切分点,训练集最后一个窗口结束时间必须早于测试集第一个窗口的开始时间。
4. 模型训练与参数设置:让RFNN稳定收敛的工程做法
4.1 需要人工定的初始化参数:隶属度中心、规则数与递归权重
RFNN不是完全端到端黑盒,初始化质量直接影响后面能不能收敛到好的局部最优。输入隶属度函数的中心不要随机初始化,常见做法是先对训练数据做模糊C均值聚类,聚出五到七个类别,把聚类中心作为高斯隶属度函数的初始中心,簇半径作为初始宽度。这样初始的模糊划分就大致对应了现场的工况分布,比如低负荷工况、冲击负荷工况、夜间低流量工况。
规则数优先从五条开始试。规则太少表达不了复杂工况,规则太多会过拟合并且训练变慢。递归权重的初始值一定要小,我一般取0.005到0.01,否则初始阶段状态更新过快,损失曲线直接发散。输出层权重也取小随机值。参数初始化后先跑一轮小批量数据,确认损失数值在正常范围,再做正式训练。
4.2 训练循环怎么写:带学习率衰减与提前停止的Keras思路
实际训练时我用TensorFlow/Keras实现自定义RFNN层,把隶属度参数和递归权重注册为可训练变量。训练循环不需要很复杂,关键是加提前停止和保存最优权重。下面是一个训练主循环的框架,可以照搬到自己的工程里:
import tensorflow as tf rfnn = RFNN(num_inputs=6, num_rules=7, rec_steps=6) optimizer = tf.keras.optimizers.Adam(learning_rate=0.005) mse = tf.keras.losses.MeanSquaredError() train_ds = tf.data.Dataset.from_tensor_slices((train_X, train_y)).batch(32) val_X, val_y = build_windows(val_df, window=6) best_val = float("inf") patience = 0 for epoch in range(300): for x_batch, y_batch in train_ds: with tf.GradientTape() as tape: y_pred = rfnn(x_batch, training=True) loss = mse(y_batch, y_pred) grads = tape.gradient(loss, rfnn.trainable_variables) optimizer.apply_gradients(zip(grads, rfnn.trainable_variables)) val_loss = mse(val_y, rfnn(val_X, training=False)).numpy() if val_loss < best_val: best_val = val_loss rfnn.save_weights("rfnn_best.h5") patience = 0 else: patience += 1 if patience >= 15: break这段代码里有两个容易被忽略的细节。一是batch(32)后面没有shuffle,因为递归网络处理的是连续时间序列,每个样本内部带有状态传递,随机打乱批次会破坏时间连续性,训练反而不稳。二是提前停止在验证损失连续15轮没有新低时触发,保存的是历史最优权重而不是最后一轮的权重,防止后期过拟合把参数带偏。
4.3 训练完成后的验证指标与可解释性检查
模型训练完,不要只看训练集损失。我习惯同时检查三项:均方根误差、平均绝对百分比误差、以及DO预测值与实测值的拟合曲线。对控制场景来说,更重要的是看“趋势方向对不对”:预测值是否在DO实际下降前就已经给出偏高需氧量判断。如果只是数值上接近但相位滞后,对前馈控制没有意义。
RFNN的好处是可以通过观察规则层激活强度来确认模型学到的规律是否符合工艺常识。比如进水氨氮升高时段,应该是“高负荷”规则激活并且输出相应上调曝气量;如果发现模型用“低负荷”规则去响应高氨氮,说明特征相关性有问题或训练数据里有矛盾样本。这个可解释性检查在现场很有说服力,工艺人员能看懂模型在做什么,比拿出一堆误差指标更管用。
5. 避坑:污水处理RFNN控制落地时的4个常见问题
5.1 现象一:离线预测很准,一投运就漂移
离线测试误差很好看,RMSE低到零点几,投运后发现预测值越来越偏,鼓风机跟着乱调。原因通常有两层:第一,离线测试用的是同一台仪表的同一段历史数据,传感器老化、水质季节性变化这些“看不见的变化”没有体现在测试集里;第二,模型没有在线更新机制,工况漂移后模糊规则还是旧的。
解决思路是把“训练-部署”做成闭环。常见做法是每天定时用最近一周的数据增量微调模型,或者至少每周重跑一次训练,保留历史最优权重做横纵对比。我一般会在部署初期把模型预测值记录到趋势图上,和实际DO叠着看,一旦发现连续三天的偏差方向一致,就触发重新训练流程。
5.2 现象二:进水水量突变时模型输出震荡失稳
进水流量突然翻倍,模型给出的曝气修正量跟着剧烈摆动,执行机构频繁动作,阀门定位器都受不了。这个问题的根源常在递归权重初始值偏大或输入突变被模型过度放大。递归结构本身有状态记忆,突变输入会被状态持续放大,产生类似振铃的输出。
解决分三步:递归权重初始值从0.005这个量级开始,不要贪图“学得快”设成0.1;对进水流量这类突变型变量先做一阶惯性滤波,让进入模型的信号平滑一些;输出侧加限幅,单次修正量不超过设定DO的±0.5mg/L,防止模型异常时执行机构被带飞。限幅不是掩盖模型问题,而是给控制回路留一个安全边界。
5.3 现象三:DO传感器响应慢,训练数据时间对不齐
DO探头不是瞬时响应的,气量变化后探头读数要一两分钟才能稳定。训练时如果直接把当前时刻的输入特征和当前时刻的DO读数对齐,模型学到的是“输入和输出同步变化”,但实际控制中输出天然滞后于输入,这个错位会让前馈效果大打折扣。
解决方法是做标签滞后对齐:预测目标改为未来5到15分钟的DO值,对应的输入特征取当前及过去若干时刻。滞后时长一般取对象时间常数的一半左右,具体数值可以通过互相关分析确定——试几个不同的滞后步长,看哪个步长下输入与输出的相关性最高,就用哪个。
5.4 现象四:归一化时把全样本统计量带进了训练,数据泄漏
这条看起来基础,但实际代码里非常常见。全序列算min和max做归一化,测试集的信息在训练之前就被模型看到了,测试误差虚低。问题很隐蔽,因为训练过程不会报错,只有部署时模型对超出历史范围的数据表现异常迟钝。
解决思路在前面第三章的代码里已经体现:先切分,再只用训练集计算归一化参数,并且把(min, max)保存下来,部署推理时用同一组参数变换新数据。如果现场数据范围发生变化,比如进水COD整体抬升,需要定期用滑动窗口更新归一化参数,同时重新训练模型。
6. 从预测模型到控制回路:前馈修正+DO闭环的落地验证
6.1 控制结构:RFNN输出作为DO设定值的前馈修正量
模型训练好之后,不直接让它的输出去驱动曝气阀门,那是危险的做法。常见控制架构是保留底层PID闭环,把RFNN的预测输出作为DO设定值的前馈修正量:DO_sp(t) = DO_base(t) + K_ff * ΔDO_pred(t)。其中DO_base是工艺人员设定的基础溶解氧目标值,ΔDO_pred是模型预测的需氧偏差,K_ff是前馈增益,建议从0.6开始调试,避免修正过猛。
前馈修正量必须限幅,单次修正不超过±0.5mg/L,修正变化速率也做限制,防止模型单点异常导致设定值跳变。PID的积分项要在执行器接近饱和时暂停累加,防止“积分饱和”引发超调。这套结构的价值在于:PID处理细小幅度的扰动,RFNN处理分钟级的负荷变化,各司其职,系统整体鲁棒性比直接端到端控制高得多。
6.2 旁路试运行与切换策略:先观察两周,再切自动
投运验证我习惯分两个阶段。第一阶段是旁路模式:模型与PID并行运行,模型输出只记录不接入控制回路,收集至少两周的对比数据。两周能覆盖工作日与周末的进水差异,也足够看到一次小规模冲击负荷。对比指标用两个:DO实测值的标准差,以及曝气单耗,单位是kWh/kgO2,统计口径保持一致。
第二阶段才切换到前馈修正模式,并且保留手动/自动切换按钮在中控室。切换后观察三个信号:DO实测值是否仍然稳定在目标区间、模型输出是否频繁触碰限幅、鼓风机执行机构动作频度是否正常。我在这类改造里养成了一个习惯:把模型的每一帧修正量打印到趋势图上,和实际DO画在同一个坐标系里。这个画面比任何指标都直观——哪天模型输出和DO趋势方向反了,说明特征或训练数据哪里出了问题,马上就能发现。希望帮到你。
本文还有配套的精品资源,点击获取