ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TS模糊神经网络原理与代码实现:从规则到混合学习

TS模糊神经网络原理与代码实现:从规则到混合学习

TS模糊神经网络这个名词,第一眼确实劝退——又是模糊又是神经又是网络,感觉像是三个领域硬凑在一起。但把它拆开看就清楚了:“模糊”负责用规则描述不确定性,“神经网络”负责从数据里学习参数,两者结合的结果是:系统既能像老师傅一样用“如果……那么……”的规则来工作,又能像机器学习模型一样自动调参,解决传统模糊系统“只会拍脑袋不会学习”的问题。

我最初被拉进这个坑,是因为一个非线性系统的建模需求。当时的对象是一个有明显强非线性的闭环装置,输入输出数据很干净,但PID参数调来调去始终不理想;换成纯BP神经网络,训练集表现尚可,验证集却频繁露馅。后来换成TS模糊神经网络——先把输入空间划分成几个子区域,每个区域用一条“如果模糊条件成立,就用这个线性函数计算输出”的规则去逼近,再用神经网络把隶属度参数和结论参数一起学出来,结果才真正稳定下来。

这篇文章适合两类人:一类是刚接触模糊神经网络的初学者,希望搞懂TS型规则和网络结构到底是怎么回事;另一类是做回归、辨识或控制相关工作的工程师,想把这套方法落地到自己的数据上。我会从原理讲到代码,再讲到我实际踩过的坑,尽量少说废话,多给能直接抄作业的东西。

1. 为什么需要TS模糊神经网络:模糊逻辑与神经网络的互补性

1.1 传统模糊控制的瓶颈在哪

传统模糊控制,大多数人接触的是Mamdani型:输入先模糊化,经过规则推理得到模糊输出,最后还要去模糊化。这套方案的优点是语义清晰,比如“温度高就加大冷水量”这种规则,人一看就懂。但它有两个硬伤:

第一,规则库完全依赖专家经验。系统复杂一点,规则数量就蹭蹭往上涨,经验不足的人根本不知道从哪里开始,更别提自动从数据里挖掘规则。

第二,参数难调整。隶属度函数是三角形的还是梯形的、中心放在哪、宽度取多少,几乎全靠试错。Mamdani输出是模糊集合,去模糊化环节还会损失精度,调了半天往往发现只是“看起来合理”,实际误差并没有降下去。

我早先试过一个温度控制场景,规则库写了二十多条,隶属度函数也精心设计过,可系统换一个工况后误差立刻放大。本质原因就是这批参数没有一个自适应的出口,所有优化都只能靠人工手动去拧。

1.2 神经网络的短板又是什么

神经网络天生擅长学习。BP、径向基、深度学习等等,都能逼近任意连续非线性函数,这是万能逼近定理保证的。但神经网络也有让人头疼的地方:它是一个黑箱。

训练完之后,你只知道权重数值,却说不清模型内部到底学到了什么规律。对很多工业场景来说,“说不清理由”意味着不敢用。你不可能在安全相关的控制回路里放一个谁都解释不了的黑盒。另外,纯神经网络需要大量数据支撑,数据少的时候泛化就是碰运气;它也很难利用工程师已有的领域知识,你明明知道“温度高了该降温”,但没法把这个先验知识塞进BP里。

所以现实情况是这样的:模糊系统懂规则但不会学习,神经网络会学习但不懂规则。二者天然互补,把它们结合成模糊神经网络,是水到渠成的事。

1.3 TS模型的核心思想:分片线性化

TS模型和Mamdani最大的区别在输出端。Mamdani规则的后件是模糊集合,而TS规则的后件是输入变量的线性函数。例如:

如果 x1 是 A1 且 x2 是 A2,那么 y = a0 + a1·x1 + a2·x2

很多人第一次看到这个形式会觉得奇怪:不是说模糊吗?输出怎么是精确的线性函数了?关键在于,这条规则只在局部区域生效。整个输入空间被模糊划分成若干子区间,每个子区间内用一个线性模型去逼近,最终输出是所有局部线性模型的加权融合。

打个比方:一条弯曲的公路,你没法用一条直线代表整条路,但你可以把它分成几段,每段用一条直线去近似,段与段之间平滑过渡。整体上这是一个非线性模型,局部上又是简单直观的线性关系。

这就是TS模型“分片线性化”的思想。它比纯模糊模型更精确,比纯神经网络更可解释——每个局部线性模型就是一条可读的规则,工程师能理解每个区域里输入和输出的关系。

2. TS模糊神经网络的数学原理:从规则到网络结构

2.1 模糊规则的形式

TS模糊神经网络的基础是一组TS规则。假设输入是x = [x1, x2, ..., xn],规则库里有K条规则,第i条规则写成:

如果 x1 是 A_i1 且 x2 是 A_i2 且 ... 且 xn 是 A_in 那么 y_i = a_i0 + a_i1·x1 + a_i2·x2 + ... + a_in·xn

前件是模糊条件,描述“这个输入向量落在哪个局部区域”;后件是线性函数,描述“在这个区域内输出和输入的关系”。最终输出不是简单选一条规则,而是所有规则输出的加权平均。

我常用的文字表述是:每条规则就是一把“局部秤”,几把秤共同把全局非线性称出来。

2.2 隶属度函数与输入空间划分

隶属度函数是用来刻画“x_j 属于 A_ij 的程度”。工程里最常用的是高斯隶属度函数:

μ_ij(xj) = exp(-(xj - c_ij)^2 / (2·σ_ij^2))

这里有两个参数:中心c_ij和宽度σ_ij。中心决定这个模糊子集靠在哪个位置,宽度决定它的覆盖范围。高斯函数的好处是光滑、处处可导,这对后续用梯度法训练非常关键;而且参数含义直观,初始值也容易根据数据分布来定。

输入空间划分方式一般有三种:

  • 网格划分:每个维度划分成若干区间,笛卡尔积生成规则。简单但维度一高就爆炸。
  • 聚类划分:用KMeans或模糊C均值对输入样本聚类,每个聚类中心对应一条规则的前件中心,数据密的地方规则多,数据疏的地方规则少。这是实践中最常用的方式。
  • 自适应划分:边训练边调整中心,相当于把“规则的边界”也交给学习。

对高维输入,网格划分是不可行的,但聚类划分可以很好地随数据分布变化调整规则密度。

2.3 网络拓扑:前件网络与后件网络

把TS规则展开成神经网络的层,结构非常清晰:

  • 输入层:接收原始输入向量。
  • 模糊化层:每个输入分量x_j经过各条规则的隶属度函数,输出隶属度μ_ij,相当于把数值映射成“属于某个模糊集合的程度”。
  • 规则层:每个节点对应一条规则,把所有输入分量的隶属度相乘,得到规则的激活强度w_i。这里的乘积运算对应规则前件的“且”。
  • 归一化层:对激活强度做归一化,得到ŵ_i = w_i / ∑w_i,保证所有权重加起来为1。
  • 后件层:每条规则计算自己的线性输出y_i = a_i0 + ∑a_ij·x_j。
  • 输出层:加权求和,y = ∑ŵ_i·y_i。

如果用表格表达这几层的输入输出关系,大概是这样的:

层输入输出参数
模糊化x_jμ_ijc, σ
规则μw_i无
归一化wŵ_i无
后件xy_ia
输出ŵ, yy无

从这张表能看出,整个模型只有两类参数:前件参数(决定模糊划分)和后件参数(决定局部线性函数)。记住这一点,后面看训练算法就简单了。

2.4 参数辨识任务量初步估算

一个维度为n、规则数为K的TS模糊神经网络,前件参数有2·K·n个(中心和宽度各算一个),后件参数有K·(n+1)个。比如2维输入、6条规则,前件参数24个,后件参数18个,总共42个参数。这个规模放在神经网络里算是很轻量级了,普通数据集几千个样本足够训练。

参数规模之所以能压在这么小的范围,正是因为结构里带了“线性后件”这个强先验。如果是纯BP网络,在同等复杂度下通常需要更多参数才能拟合同样的非线性,而TS模糊神经网络把大部分非线性拆分到了局部线性映射里。

3. 训练算法与参数学习:为什么不能只靠反向传播

3.1 单纯梯度下降的困境

如果直接把整个网络丢进BP里用梯度下降训练,会发现收敛又慢又不稳定。原因是后件参数和前件参数在优化难度上差别很大。

后件参数对输出是线性的。固定前件参数后,整个输出等于一堆带权重的线性函数求和,目标函数关于后件参数就是一个二次函数。对这种问题,最优解可以一步用最小二乘法直接算出来,根本不需要梯度下降慢慢磨。

前件参数则完全相反。高斯函数里还套着指数运算,梯度经过多层链式传导后非常容易饱和——σ太大或太小时,隶属度会趋于平坦或者尖锐,梯度都会消失。再加上归一化层,任一权重变化都会影响其他所有权重,梯度更新变得很敏感。

所以工程里几乎没人用纯BP从头训TS模糊神经网络,标准做法是混合学习。

3.2 混合学习:先聚类,再最小二乘,最后微调

混合学习的思路很直白:谁适合线性求解,就用线性方法解;谁适合梯度优化,就用梯度法调。

第一步是规则初始化。用KMeans或减聚类对输入样本聚类,得到K个聚类中心,直接作为高斯隶属度函数的中心c。每个聚类的内部平均距离可以作为宽度σ的初始值。这一步保证了初始时刻规则覆盖的是数据密集区,不会出现规则全空转的情况。

第二步是固定前件参数,估计后件参数。这时候输出关于后件参数是线性的,可以把所有样本的所有规则展开到一个大线性系统,用最小二乘一步求出全局最优的后件系数。

第三步是前件微调。后件参数已经锁定在接近最优的位置,再用BP或Adam这类梯度优化器去调整c和σ,让模糊划分边界进一步贴合数据。梯度下降只需要跑少量迭代,因为大头已经被最小二乘吃掉了。

需要说明的是,这个过程可以循环:微调完前件,再回头重估后件,交替迭代几次,精度往往还能继续提升。

3.3 规则数怎么定:减聚类与交叉验证

规则数K是整个建模过程中最需要人工干预的超参数。K太小,模型不够灵活,拟合偏差大;K太大,参数过多,过拟合风险高,规则之间也容易重复冗余。

实用方法之一是减聚类。它不指定K,只设定一个半径参数,算法根据每个数据点周围邻居的密度自动选择聚类中心和规则数。密度高的地方会多出规则,密度低的地方自动稀疏,兼顾覆盖和简洁。

另一个办法是干脆用交叉验证扫K。我通常在二维输入、两三千样本的规模下,从K=2扫到K=12,画一条RMSE随K变化的曲线。一般来说,K增大会先快速下降,然后在某个位置进入平台期,超过平台期后开始微涨——那个点附近就是合适的规则数。

经验上,输入维度低且非线性不是特别剧烈时,K取3到8就够了,没必要动辄几十条规则。

3.4 输入归一化、宽度下限和早停

这些工程细节看起来不起眼,但直接影响成败。

输入归一化建议放到第一位。把各维度数据都缩放到[0,1]或[-1,1]附近,否则高斯宽度σ的尺度会受不同维度量纲影响,出现某些维度隶属度极窄、某些维度隶属度极宽的问题。

σ要设一个下限,比如0.05。训练中如果不加约束,宽度可能被优化到接近0,此时隶属度函数变成一根针,梯度非常不稳定,模型略微扰动就会剧烈抖动。这本质上和神经网络的梯度爆炸同源。

训练过程要留验证集。TS模糊神经网络参数总量不大,如果规则数偏多,过拟合很容易发生。验证集的RMSE不再下降甚至回升,就果断停掉。

4. 代码实现:从零搭建TS模糊神经网络

4.1 纯NumPy实现核心框架

理解算法最可靠的方式是自己写一遍。我用纯NumPy实现了一个精简版,只保留最关键的计算路径。代码量不大,核心是一个类和两个方法,先看整体框架:

import numpy as np class TSFNN: def __init__(self, c, sigma): self.c = np.asarray(c, dtype=float) # shape (K, n) self.sigma = np.asarray(sigma, dtype=float) # shape (K, n) self.A = None # 后件参数,训练后填充 def _activation_weight(self, X): # X: (N, n) -> w: (N, K) dx = X[:, np.newaxis, :] - self.c[np.newaxis, :, :] # (N, K, n) mu = np.exp(-dx ** 2 / (2.0 * self.sigma ** 2)) w = np.prod(mu, axis=2) # 按输入维度相乘 return w def _normalize_weight(self, w): return w / (w.sum(axis=1, keepdims=True) + 1e-12) def _design_matrix(self, X, w_hat): # 每个样本、每条规则都扩展出 [w, w*x1, ..., w*xn] 列 cols = [] for k in range(w_hat.shape[1]): cols.append(w_hat[:, k:k+1]) cols.append(w_hat[:, k:k+1] * X) return np.hstack(cols)

_activation_weight计算每条规则的激活强度,_design_matrix把归一化权重和原始输入拼成一个线性回归设计矩阵。这两步是整个网络前向路径的核心。

4.2 最小二乘估计后件参数

由于固定前件后,输出对后件参数是线性的,直接用np.linalg.lstsq求解:

def fit_lse(self, X, y): w = self._activation_weight(X) w_hat = self._normalize_weight(w) Phi = self._design_matrix(X, w_hat) sol, _, _, _ = np.linalg.lstsq(Phi, y, rcond=None) self.A = sol.reshape(-1, X.shape[1] + 1) return self.predict(X) def predict(self, X): w = self._activation_weight(X) w_hat = self._normalize_weight(w) Phi = self._design_matrix(X, w_hat) return Phi @ self.A.ravel()

这段代码看着简单,但“线性回归设计矩阵”的构造方式是理解TS模糊神经网络的关键。每一列的物理意义是:第k条规则对样本的贡献,乘上该样本的原始特征。

4.3 跑一个非线性拟合实验

用一个公开思路生成的仿真数据来验证:取二维输入,目标函数为两个输入乘积的正弦,加上少量噪声。

from sklearn.cluster import KMeans rng = np.random.default_rng(42) X = rng.uniform(-3, 3, size=(400, 2)) y = np.sin(X[:, 0] * X[:, 1]) + 0.1 * rng.standard_normal(400) # 用KMeans初始化前件中心,宽度取0.5 kmeans = KMeans(n_clusters=6, random_state=0).fit(X) c = kmeans.cluster_centers_ sigma = np.ones_like(c) * 0.5 model = TSFNN(c, sigma) train_pred = model.fit_lse(X, y) rmse = np.sqrt(np.mean((train_pred - y) ** 2)) print(f"RMSE = {rmse:.4f}")

在我本机的测试里,这个数据规模下RMSE大约能压到0.08到0.12之间。注意这里只用一步最小二乘,并没有做前件梯度微调,已经能逼近噪声底限,这就是“线性后件+合理前件划分”的威力。

如果想进一步压误差,可以用scipy.optimize.minimize以c和σ为优化变量、以fit_lse后的RMSE为损失做一轮迭代,或者把前件参数放进PyTorch/Adam里优化。实际项目中我推荐PyTorch方案,自动求导省心,也更方便加正则项。

4.4 训练流程建议

我梳理了一份可以直接照做的流程:

1. 数据归一化 2. 用KMeans或减聚类粗略估计规则数和前件中心 3. 初始宽度设为同类样本平均距离或固定经验值 4. fit_lse 估计后件参数,记录训练RMSE和验证RMSE 5. 用梯度优化器微调前件参数,固定后件 6. 重复:后件LSE -> 前件梯度,交替2到3轮 7. 如果验证集不再下降,停止

大多数案例走完前四步就已经能拿到可用的精度,后续微调是锦上添花。

5. 实际应用场景:从系统辨识到软测量

5.1 非线性系统辨识与控制

TS模糊神经网络最经典的应用是系统辨识。工业对象,比如温度过程、液压系统、机器人关节,往往有强非线性。传统方法用单一线性模型去描述,在不同工况下精度差异巨大;直接在全局上建一个非线性模型,又很难用于控制器设计。

TS模型天然适合解决这个问题:它本来就是若干局部线性模型的加权组合,每个局部线性模型对应一个工作点。模型辨识出来后,工程师可以直接基于局部线性模型设计局部控制器,再按照当前工况的权重融合控制量。这在工程上叫增益调度,是TS模糊控制受欢迎的重要原因。

5.2 软测量与质量预测

在很多流程工业场景里,关键质量指标无法在线实时测量,比如某些成分浓度需要实验室化验,时间滞后严重。但温度、压力、流量这些过程变量是可以实时采集的。用TS模糊神经网络做软测量,就是用易测变量预测难测变量。

这种场景对模型可解释性有要求。TS模型的每条规则都可以翻译成“当操作参数落在某个区间时,质量指标与哪些变量是近似线性关系”,现场工程师看了规则能判断模型是否合理,这一点纯粹的黑箱模型做不到。

5.3 时间序列预测与模式识别

TS模糊神经网络也适合做时间序列预测。先通过滑动窗口把时序数据转成监督学习格式,再用聚类初始化规则,模型可以学到不同运行模式下各自的局部趋势。相比直接上LSTM,它的优势是形式简单、可解释、数据需求少,在小规模时序预测任务上甚至稳定性更好。

我个人的经验是:如果你的预测对象存在明显的工况切换或模式切换,比如设备在不同负载区间有不同响应特性,TS模糊神经网络往往比单模型硬套表现更好,因为它本质上是“多个局部模型自动切换”。

5.4 与主流方法的横向对比

给你一张我经常拿来对比的表格:

方法可解释性数据需求量规则/结构自动学习在线更新适合场景
PID高低无简单线性或弱非线性系统
纯BP网络低高有中等大样本回归/分类
SVM/RBF低中只选中心较难中规模回归
TS模糊神经网络高中有(规则中心可聚类)可用RLS非线性、可解释需求高

纯BP在大数据量下有更高上限,但TS模糊神经网络在数据不充裕、可解释性有要求、模型要嵌入控制回路这些条件叠加时,是更有性价比的选择。

6. 常见问题与排查技巧实录

6.1 规则爆炸与维度灾难

输入维度升高后,如果还用网格划分,规则数会指数级增长。3维输入每维分3段就是27条规则,5维就是243条,这谁受得了。

应对办法是把规则和样本分布绑定。用减聚类或KMeans把规则只放在数据密集区,维度高也没关系,规则数依然由数据复杂性决定,而不是由维度乘积决定。另外可以先用主成分分析降维,把无关维度剔除。

6.2 宽度σ过小导致梯度异常

这是我在自己实验里踩过最典型的坑。σ初值设得太小,比如0.01,隶属度函数几乎变成一根针,训练过程中误差基本不下降。检查方法是打印归一化权重w_hat的分布,如果大量值接近0或1,说明隶属度过窄或过宽,规则之间几乎没有重叠。

解决方式有两个:把σ初值设成同类样本到聚类中心平均距离的一半;或者在训练中对σ加下限约束。注意不要让它被优化到趋近0。

6.3 后件参数过拟合

规则数一多,后件参数数量跟着涨,局部区域样本又少,很容易出现局部线性函数疯狂拟合噪声。最直接的信号是训练RMSE很低但验证RMSE很高。

解法是在最小二乘里加正则项,也就是把lstsq换成岭回归求解:

def fit_lse_ridge(self, X, y, lam=1e-3): w = self._activation_weight(X) w_hat = self._normalize_weight(w) Phi = self._design_matrix(X, w_hat) A = Phi.T @ Phi + lam * np.eye(Phi.shape[1]) b = Phi.T @ y sol = np.linalg.solve(A, b) self.A = sol.reshape(-1, X.shape[1] + 1) return self.predict(X)

lambda从1e-4到1e-1之间调,通常能有效压住后件过拟合。

6.4 初始化对结果影响很大,怎么办

TS模糊神经网络对前件初始化比较敏感。不同初始化可能得到完全不同的局部划分,所以要像对待KMeans一样处理:多跑几个随机种子,挑验证集最优的模型。聚类中心的选择不建议用随机点,直接用KMeans的聚类中心基本不会跑偏。

如果发现某些规则几乎没有激活,也就是该规则的w_hat对所有样本都接近0,说明这条规则是废的。检查是否有聚类中心落在数据稀疏的边缘区,把它删掉并重新训练往往效果更好。

6.5 在线更新与部署细节

TS模糊神经网络的推断非常轻量,就是一个矩阵乘法和几个指数函数,部署在嵌入式设备或者PLC里毫无压力。结构可以预先展开成固定的计算流程,不需要运行时动态建图。

如果需要在线学习,后件参数建议用递归最小二乘更新,而不是每次重新全量最小二乘。前件参数一般固定不动,只有工况漂移明显时才考虑重新聚类。这样既保证在线适应速度,又不会因为参数同时乱动导致输出跳变。

6.6 一个实用的调试顺序

给新手一个我自己反复使用的顺序:先做数据归一化,再用KMeans选4到6条规则,宽度给0.5,直接跑一次LSE看RMSE曲线。如果这个基线都差得离谱,问题大概率在数据预处理而不是模型结构;如果基线不错但验证集差,再加正则;如果训练集误差曲线迟迟不降,再往前件微调走。按这个顺序排查,很少会把时间浪费在错误的方向上。

TS模糊神经网络最大的魅力,在于它保留了工程师能看懂的规则,同时又把“调参”这件事交给数据和算法去完成。对做非线性建模和控制的同行来说,这套方法值得放进自己的工具箱——哪怕最后没采用,在对比实验里也能帮你更准确地判断其他黑箱模型的真实水平。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进