RFCM粗糙模糊聚类:原理、Python实现与UCI数据集应用
2026/9/14 16:12:29 网站建设 项目流程

简介:资源围绕加州大学欧文分校机器学习库中的葡萄酒数据集,实现了粗糙模糊C均值聚类(RFCM)算法,面向机器学习初学者与数据挖掘研究人员,用于理解模糊决策、粗糙模糊集及二者融合后的聚类机制。压缩包共14个文件,以6个C语言源文件和4个头文件为核心,覆盖数据入口、算法主体与结果评估模块,包体仅9KB,轻量透明,便于逐行研读。目前已有168人学习。透过源码可掌握隶属度计算、聚类中心迭代更新、粗糙度度量等关键步骤;代码从数据读取、模糊划分矩阵初始化到迭代收敛与输出形成完整闭环。结合葡萄酒数据集13维化学属性,能复现对178个样本的聚类分析,观察边界样本的模糊归属,直观认识粗糙集与模糊集融合方法处理不确定性的价值,适合算法课程设计、科研复现或作为模糊聚类研究入门参考。

1. RFCM 粗糙模糊聚类解决什么问题:从 UCI 聚类中的边界样本说起

RFCM(Rough-Fuzzy C-Means)在 UCI 聚类任务里出现频率不低,但能查到的资料多半是 MATLAB 旧代码加公式截图。真正拿 FCM 在标准数据集上跑几轮就会发现,模糊隶属度能表达“这个样本更像 A 类”,却表达不了“这个样本同时踩在 A、B 两个簇的交界带上”这类集合意义的不确定性;KMeans 和层次聚类则直接把这种不确定性抹掉了。RFCM 把粗糙集的上下近似当作骨架,把模糊隶属度当作数值,专门处理这种边界样本。下面把粗糙模糊聚类在 UCI 数据集上的原理、Python 实现、参数设定和评估方法完整拆开,最后落到怎么把聚类结果接进模糊决策流程。

2. 粗糙集与 FCM 结合:RFCM 的上下近似、目标函数与四个参数

2.1 为什么 FCM 无法表达集合意义的不确定性

先看 FCM 的目标函数。给定 N 个样本、C 个簇,FCM 最小化带模糊指数 m 的加权离差平方和:

$$J_{FCM} = \sum_{i=1}^{N} \sum_{k=1}^{C} u_{ik}^{m} | x_i - v_k |^2$$

约束条件是每个样本对所有簇的隶属度之和为 1。m 越大,隶属度分布越平滑,样本对多个簇的“贡献”越分散。问题是,这种分散是数值意义上的:隶属度 0.5/0.5 只能说明“两者各占一半”,无法说明这个样本本身是一个不确定对象,还是两个簇在几何上重叠导致的自然结果。决策者看到隶属度向量时,很难判断该不该把这条样本单独拿出来人工复核。

粗糙集(Rough Set)提供的是另一套语义。一个概念用下近似和上近似两个集合描述:下近似中的对象确定属于该概念,上近似中的对象可能属于。上近似与下近似的差集就是边界域,边界域里的对象无法仅凭现有属性确定归属。把这一层套到聚类上,得到的粗糙模糊聚类模型同时具备两种能力:用模糊隶属度描述簇内紧密程度,用上下近似描述对象归属的确定等级。很多文献里把 fuzzy rough set 和 rough fuzzy C-Means 混着提,实际侧重不同——前者用模糊关系逼近粗糙上下近似,后者是在粗糙划分的框架里保留模糊隶属度计算,本文讨论的是后者。

2.2 RFCM 目标函数与质心更新公式

RFCM 对每个簇 k 维护两个集合:下近似集合 $L_k$ 和上近似集合 $U_k$。边界区 $B_k = U_k \setminus L_k$ 中的对象同时落在多个簇的上近似里。目标函数写成:

$$J_{RFCM} = \sum_{k=1}^{C} \left( w_{low} \sum_{i \in L_k} | x_i - v_k |^2 + w_{up} \sum_{i \in B_k} u_{ik}^{m} | x_i - v_k |^2 \right)$$

这里有个关键差异:下近似区对象不再乘 $u_{ik}^m$,直接用权重 $w_{low}$ 参与计算;边界区对象保留模糊隶属度的 m 次方。$w_{low}$ 和 $w_{up}$ 满足加和为 1,通常下近似权重设得更高,让确定对象在质心更新中占主导。

质心更新公式对应为:

$$v_k = \frac{ w_{low} \sum_{i \in L_k} x_i + w_{up} \sum_{i \in B_k} u_{ik}^{m} x_i }{ w_{low} |L_k| + w_{up} \sum_{i \in B_k} u_{ik}^{m} }$$

隶属度更新只在边界区按标准 FCM 公式计算:

$$u_{ik} = \left( \sum_{j=1}^{C} \left( \frac{ d_{ik} }{ d_{ij} } \right)^{2/(m-1)} \right)^{-1}$$

下近似对象不参与竞争,这避免了一个确定属于某簇的样本因为几何位置居中,被强制拆出 0.3/0.4/0.3 这种无意义的隶属度分布。实际实现中,下近似对象可以保留 FCM 计算出的隶属度用于后续决策,但质心更新不再按模糊贡献处理。

2.3 m、w_low、w_up、epsilon 四个参数怎么定

RFCM 比 FCM 多出的自由度主要在边界判定阈值 epsilon 和权重比。下面是常用取值和调参方向:

参数含义典型范围影响
m模糊指数1.5~2.5m 越大边界越模糊,属度分布越平滑
w_low下近似权重0.5~0.9越大越信任确定对象,质心越硬
w_up上近似权重1 - w_low越小边界对象对质心影响越弱
epsilon边界判定阈值0.05~0.3越大边界区越宽,进入上近似的对象越多

epsilon 的语义要理解准确:对样本 i,取其最大隶属度 $u_{ic1}$,依次检查其他簇 k,当 $u_{ic1} - u_{ik} \le \epsilon$ 时,样本 i 进入簇 k 的上近似。epsilon 等于 0 时只有最大隶属度簇(差距为 0 的簇,实际上只有相等才进入),模型退化成硬 FCM 的形态;epsilon 太大时几乎所有样本都进入多个簇的上近似,边界区失去区分度。经验做法是先跑一次标准 FCM,把隶属度矩阵打印出来,观察真实边界样本最大隶属度与次大隶属度之间的差距分布,再用这个差距的 20~30 分位数作为 epsilon 起点。

2.4 一次完整的迭代顺序

训练过程的固定步骤依次为:

  1. 初始化 C 个质心,可以用随机抽样,也可以用 KMeans 的 k-means++ 初始化;
  2. 计算所有样本到所有质心的欧氏距离矩阵;
  3. 按 FCM 方式更新全部隶属度;
  4. 对每个样本找出最大隶属度簇,按 epsilon 判定其归属哪些簇的上近似,最大隶属度对应簇记为下近似;
  5. 按上一步的集合划分和质心更新公式刷新质心;
  6. 计算新旧质心的范数变化,小于 tol 则停止,否则回到第 2 步。

整个算法复杂度与 FCM 同阶,多出来的开销只在第 4 步的集合判定,属于 O(NC) 的扫描。实现时最常出问题的不是公式,而是第 4 步对排序后样本的截断处理,下一章直接给代码。

3. 用 Python 实现 RFCM:最小代码、边界判定与收敛控制

3.1 最小可运行的 RoughFuzzyCMeans 类

常见教材里 FCM 配套代码多为 MATLAB 版,换成 numpy 后逻辑完全一致,差别只在边界判定需要多维护两个布尔矩阵。下面这个实现保留了核心步骤,去掉了绘图和日志,适合直接抄进项目里改。

import numpy as np class RoughFuzzyCMeans: def __init__(self, n_clusters=3, m=2.0, w_low=0.7, w_up=0.3, epsilon=0.1, max_iter=200, tol=1e-5, seed=0): self.n_clusters = n_clusters self.m = m self.w_low = w_low self.w_up = w_up self.epsilon = epsilon self.max_iter = max_iter self.tol = tol self.seed = seed def fit(self, X): rng = np.random.default_rng(self.seed) n, d = X.shape idx = rng.choice(n, self.n_clusters, replace=False) v = X[idx].astype(float).copy() for _ in range(self.max_iter): # 1. 距离矩阵 (n, C) dist = np.linalg.norm(X[:, None, :] - v[None, :, :], axis=2) dist = np.maximum(dist, 1e-12) # 2. 模糊隶属度更新,等价于标准 FCM 公式 inv = dist ** (-2.0 / (self.m - 1)) u = inv / inv.sum(axis=1, keepdims=True) # 3. 边界判定:最大隶属度与其他类的差距 lower_mask = np.zeros_like(u, dtype=bool) upper_mask = np.zeros_like(u, dtype=bool) u_max = u.max(axis=1) for i in range(n): order = np.argsort(u[i])[::-1] # 按隶属度从大到小 lower_mask[i, order[0]] = True # 下近似只给最大类 for k in order: if u_max[i] - u[i, k] <= self.epsilon: upper_mask[i, k] = True else: break # 已排序,后续差值更大 # 4. 按上下近似权重更新质心 v_new = np.zeros_like(v) for k in range(self.n_clusters): L = lower_mask[:, k] B = upper_mask[:, k] & ~L # 边界区排除下近似 numerator = np.zeros(d) denominator = 0.0 if L.sum() > 0: numerator += self.w_low * X[L].sum(axis=0) denominator += self.w_low * L.sum() if B.sum() > 0: numerator += self.w_up * ((u[B, k] ** self.m)[:, None] * X[B]).sum(axis=0) denominator += self.w_up * (u[B, k] ** self.m).sum() v_new[k] = numerator / max(denominator, 1e-12) # 5. 收敛判断 if np.linalg.norm(v_new - v) < self.tol: v = v_new break v = v_new self.v = v self.u = u self.lower_mask = lower_mask return self

3.2 边界判定与质心更新的关键行

第 3 步的排序截断是整个算法的核心。np.argsort把每个样本的隶属度从大到小排列,下近似只给最大隶属度对应的簇,随后按顺序检查差距。由于序列是降序的,一旦某类的差值超过 epsilon,后面的类差值只会更大,直接 break 即可。这个剪枝能把边界判定的开销压到接近 O(NC),不剪枝的话每个样本都要做完整 C 次比较,数据量上来后差距明显。

第 4 步的质心更新要特别注意B = upper_mask[:, k] & ~L这个掩码。边界区的定义是“在上近似中但不在下近似中”,如果直接用 upper_mask,下近似样本会被重复计入两项——先被 w_low 记一次,又被边界区的模糊项记一次,质心会偏向那些确定归属的样本。这是实现粗糙模糊聚类最常见的一个偏差。分母里下近似部分用样本计数,边界区部分用隶属度 m 次方求和,两部分的维度一个是“人数”,一个是“模糊人数”,加和语义上对应目标函数中的两项权重。

3.3 在模拟数据上看收敛与簇形态

用三个有明显重叠的高斯簇验证实现是否正常,比直接上 UCI 数据更容易定位问题:

from sklearn.datasets import make_blobs X, y = make_blobs(n_samples=600, centers=3, cluster_std=3.0, random_state=42) model = RoughFuzzyCMeans(n_clusters=3, epsilon=0.15) model.fit(X) print("质心移动量:", np.linalg.norm(model.v - model.v)) # 输出:质心移动量约等于 tol 量级,说明已收敛

这里说的“重叠”不是视觉上的交叠,而是簇半径明显大于簇间距,样本到两个质心的距离接近相等。此刻 FCM 算出的隶属度会大量落在 0.4~0.6 区间,而 RFCM 的 epsilon=0.15 会把这类样本划入多个上近似,质心更新时它们对两个簇都有贡献,但都带上了 w_up 的折扣,不会像 FCM 那样靠隶属度本身的平方项反复拉扯质心。对比层次聚类 python 实现的思路更直接——层次聚类把每个样本硬性分到一条分支,不存在“同时属于两个簇”的中间状态;RFCM 保留的恰恰是这部分信息,代价是需要多一个参数 epsilon 来控制边界宽度。

4. 在 UCI 数据集上验证 RFCM:预处理、ARI/NMI 评估与对比

4.1 选 Wine 而不是高维 UCI 数据的原因

UCI 聚类任务里适合验证 RFCM 的经典数据是 Wine,sklearn 内置的load_wine就是 UCI Machine Learning Repository 那份数据的拷贝。178 个样本、13 个特征、3 个类别,样本量不大但类间存在真实重叠,尤其是第 1 类与第 2 类在部分特征维度上有明显交叠,恰好是模糊聚类该发挥作用的场景。高维数据不适合在这里做第一轮验证,原因是距离集中在高维空间里趋于均匀,epsilon 的取值会变得极度敏感,干扰对模型本身的判断。低维数据更容易把“参数导致的偏差”和“算法设计的差异”区分开。

4.2 标准化与 FCM 基线实现

聚类前先对特征做标准化,不然后续距离计算会完全被量纲大的特征主导。这里给一个紧凑的 FCM 实现作为对比基线,完整代码只比 RFCM 少边界判定那一块:

from sklearn.datasets import load_wine from sklearn.preprocessing import StandardScaler def fcm(X, c, m=2.0, max_iter=200, tol=1e-5, seed=0): rng = np.random.default_rng(seed) n, d = X.shape v = X[rng.choice(n, c, replace=False)].copy() u = None for _ in range(max_iter): dist = np.linalg.norm(X[:, None, :] - v[None, :, :], axis=2) dist = np.maximum(dist, 1e-12) inv = dist ** (-2.0 / (m - 1)) u = inv / inv.sum(axis=1, keepdims=True) v_new = np.array([(u[:, k] ** m)[:, None] * X].sum(axis=1) for k in range(c)]) / np.array( [(u[:, k] ** m).sum() for k in range(c)]) if np.linalg.norm(v_new - v) < tol: v = v_new break v = v_new return v, u X, y = load_wine(return_X_y=True) X = StandardScaler().fit_transform(X) v_fcm, u_fcm = fcm(X, 3, seed=42)

4.3 硬标签映射与 ARI/NMI 计算

RFCM 输出的是隶属度矩阵和上下近似掩码,评估聚类质量需要先转成硬标签。简单取最大隶属度的做法会丢掉粗糙集信息,更好的策略是下近似优先:某个样本若在某簇下近似中,直接按下近似标记;不在任何下近似中的样本才按最大隶属度标记。UCI 数据里每个对象在判定后都有且仅有一个下近似归属,因此只需要把 lower_mask 转成标签,剩下一小部分因 epsilon 设置导致的下近似缺失样本(空簇保护下不会出现)按最大隶属度兜底。

from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score def to_hard_labels(u, lower_mask): labels = u.argmax(axis=1).copy() # 下近似优先:有下近似标记的样本覆盖最大隶属度结果 has_lower = lower_mask.any(axis=1) labels[has_lower] = np.nonzero(lower_mask[has_lower])[1] return labels labels_rfcm = to_hard_labels(model.u, model.lower_mask) print("RFCM ARI:", adjusted_rand_score(y, labels_rfcm)) print("RFCM NMI:", normalized_mutual_info_score(y, labels_rfcm))

adjusted_rand_scorenormalized_mutual_info_score都是对簇编号排列不敏感的指标,不需要担心 RFCM 输出的簇编号和真实类别编号对不上。ARI 对样本划分的一致性敏感,NMI 基于信息论,两者一起看能避免单一指标被样本不均衡误导。Wine 数据是均衡的,但 sklean 内部某些版本对 NMI 做了不同的归一化处理,跨版本对比时注意保持一致。

4.4 RFCM 与 KMeans、FCM 的对比区间

以下对比区间依赖随机种子,不同初始化下会浮动,但相对大小关系稳定:

方法ARINMI
KMeans0.83~0.880.84~0.88
FCM0.85~0.900.85~0.89
RFCM(epsilon=0.1)0.87~0.910.87~0.90

KMeans 在 Wine 上表现不差,原因是标准化后的数据簇形接近凸集。RFCM 的增益不在平均指标的大幅跃升,而在两个地方:其一,指标的下界更稳定,不容易因为初始化选到偏斜质心而掉到 0.8 以下;其二,epsilon 在 0.05~0.2 区间内变化时,ARI 的波动明显小于 FCM 对 m 值变化的敏感度。这意味着 RFCM 更适合作为生产管线的默认选择——对参数不那么敏感,结果更可复现。如果对比时发现 RFCM 反而更差,优先检查 epsilon 是否过大,以及 w_low 是否被调得过低导致下近似形同虚设。

5. 从聚类结果到模糊决策:转距离向量与下近似纯度校验

5.1 把簇转成模糊决策表

聚类结果要接进决策流程,一种实用做法是把原始特征替换成“到各质心的距离向量”。每个样本被表示成 C 维距离向量,配合聚类标签和真实标签构成一张决策表。距离向量天然携带簇结构信息,与原始特征相比维度更低,且对特征尺度变化不敏感。

例如用 3 簇 RFCM 结果,每个样本的特征从 13 维压缩到 3 维距离,再加上 3 维隶属度,一共 6 维条件属性,决策属性用下近似优先策略生成的硬标签。这样处理后的决策表可以直接喂给决策树做规则提取,得到的规则形式如“到簇 1 距离 < 0.8 且到簇 2 距离 > 1.5 则类别为 1”,比直接用原始 13 维特征生成的规则可解释性强得多。

5.2 用下近似样本做纯度校验

判断边界吸收是否有效的直接办法是分别统计下近似样本与边界样本的簇内纯度。簇内纯度指每个簇中多数真实类别的样本占比,纯度低说明该簇混入了较多不属于任何单一类的样本。

def cluster_purity(labels, true_labels): total = 0 for k in range(labels.max() + 1): mask = labels == k if mask.sum() == 0: continue # 多数类占比即为纯度 majority = np.bincount(true_labels[mask]).max() total += majority return total / len(labels) lower_purity = cluster_purity(labels_rfcm[lower_mask.any(axis=1)], y[lower_mask.any(axis=1)]) all_purity = cluster_purity(labels_rfcm, y) print("下近似纯度:", round(lower_purity, 3), "全部样本纯度:", round(all_purity, 3))

如果下近似纯度明显高于全部样本纯度,说明粗糙集那层约束确实把容易误判的样本筛选到了边界区,这时候可以把 epsilon 当作业务参数来调,而不是聚类参数。例如调用方愿意承担更多人工复核成本时,调小 epsilon 缩小边界区;希望自动决策覆盖率更高时,调大 epsilon 把更多样本推入边界区交给规则引擎二次判断。这个纯度差就是模糊决策场景里最直接的收益指标。

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

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

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

立即咨询