news 2026/9/19 0:51:05

手写K-means与FCM聚类算法:从原理到工程实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写K-means与FCM聚类算法:从原理到工程实现

简介:本资源是面向高校人工智能课程学习者与初学者的聚类算法实验教学材料,聚焦无监督学习核心内容,助力理解K-means与FCM两种经典算法的原理差异、实现逻辑与实际应用。文档完整呈现广州大学《人工智能导论》实验四的规范报告结构,涵盖实验目的、UCI Iris数据集预处理、K-means中心初始化与迭代分配代码、FCM模糊隶属度矩阵更新机制、准确率评估方法及可视化分析建议,并附有可运行的Python核心代码片段与详细注释。资源为单文件docx格式,大小127KB,内容精炼、排版清晰,适合作为课堂实验参考、课设复现底稿或算法对比学习范本。目前已有142人学习下载,特别适合需快速掌握聚类算法编程实现与结果解读的本科生与自学者。

1. 这不是调包跑个sklearn.cluster.KMeans就能交差的实验:广州大学《人工智能导论》聚类实验的真实水深

如果你以为“聚类算法实验”只是pip install scikit-learnfit()predict()三行代码加一张散点图,那这份广州大学计算机科学与工程实验室的实验报告会立刻打破幻想。它明确要求:手写 K-means 和 FCM(Fuzzy C-Means)核心逻辑,禁用sklearn.cluster的聚类器封装,所有距离计算、中心更新、隶属度迭代必须显式展开;数据集限定为 UCI Iris(纯数值)和 Bank Marketing(混杂型),且需完成从原始文本读取、缺失值/类别编码预处理、多指标评价(准确率 + Xie-Beni 指标)、到三类可视化对比的完整闭环。这不是 Python 速成课,而是对向量空间建模、迭代收敛性、模糊隶属本质的一次硬核拆解——尤其当你的distance()函数漏了math.sqrt()point_avg()中维度循环索引越界、或 FCM 初始化模糊矩阵未归一化时,结果会直接崩坏成一片混沌噪点。适合刚学完线性代数与概率论、正尝试把公式翻译成可执行逻辑的本科生;也适合想回溯经典算法底层细节、排查生产环境聚类漂移问题的工程师——因为真实世界里,sklearn的黑盒输出常需你亲手验证其收敛路径是否合理。

2. K-means 手动实现:从 Iris 数据加载到收敛判定的每一步都踩过坑

2.1 Iris 数据解析与预处理:为什么不能直接np.loadtxt()

UCI Iris 数据集虽为标准 CSV 格式,但原始iris.txt文件存在三类典型干扰:首行无列名、末尾含空行、类别标签为字符串(如"Iris-setosa")。若直接np.loadtxt("iris.txt", delimiter=","),将触发ValueError: could not convert string to float。实验报告中loadIRISdata()函数采用逐行解析策略,关键在于跳过空行并手动分离特征与标签

def loadIRISdata(filename): data = [] labels = [] with open(filename, mode="r", encoding="utf-8") as rf: for line in rf: line = line.strip() # 去除换行符和空格 if not line: # 跳过空行 continue parts = line.split(',') # 按逗号分割 # 前4列为数值特征,第5列为标签 features = list(map(float, parts[:4])) label = parts[4] data.append(features) labels.append(label) return np.array(data), np.array(labels)

注意parts[:4]确保只取数值列,避免字符串标签污染浮点数组;strip()防止\n\r\n导致split()产生空字符串。若数据含缺失值(如?),需在此处插入if part != '?' else 0.0替换逻辑——这正是 Bank Marketing 数据集预处理的关键入口。

2.2 初始中心选取:为何取索引 0、50、100 而非随机?

K-means 对初始中心敏感,随机初始化易陷入局部最优。Iris 数据集天然按类别顺序排列(前 50 行为 setosa,51–100 为 versicolor,101–150 为 virginica),取data[0](setosa 首样本)、data[50](versicolor 首样本)、data[100](virginica 首样本)作为初始中心,本质是利用先验知识做确定性初始化,大幅提升收敛速度与稳定性。此策略在教学场景中优于np.random.rand(),但需警惕:若数据未排序(如 Bank Marketing),该方法失效,必须改用 K-means++ 启发式。

def generateCenters(data): centers = [] centers.append(data[0]) # 第一类代表点 centers.append(data[50]) # 第二类代表点 centers.append(data[100]) # 第三类代表点 return np.array(centers)

2.3 核心迭代逻辑:assignment 与 update 的原子操作与终止条件

K-means 迭代由assignment()(分配)和updataCenters()(更新)构成闭环。实验代码中assignment()计算每个点到三中心的欧氏距离并归属最近簇,updataCenters()对每簇内点求均值得新中心。关键陷阱在于终止条件:原报告用assigments != old_assigments判定收敛,但 Python 列表比较是浅层引用,需改为np.array_equal(np.array(assigments), np.array(old_assigments))或更鲁棒的中心位移阈值:

def assignment(data, centers): assignments = [] for point in data: distances = [np.linalg.norm(point - center) for center in centers] # 欧氏距离 assignments.append(np.argmin(distances)) # 归属最小距离簇 return np.array(assignments) def updataCenters(data, assignments, k=3): new_centers = np.zeros((k, data.shape[1])) for i in range(k): cluster_points = data[assignments == i] if len(cluster_points) > 0: # 防止空簇 new_centers[i] = np.mean(cluster_points, axis=0) else: # 空簇则重置为中心随机点(避免崩溃) new_centers[i] = data[np.random.randint(0, len(data))] return new_centers # 主迭代循环(修正版) centers = generateCenters(data) assignments = assignment(data, centers) for iteration in range(100): # 设定最大迭代次数防死循环 new_centers = updataCenters(data, assignments) new_assignments = assignment(data, new_centers) # 终止条件:中心位移均值 < 1e-4 或分配不变 center_shift = np.mean(np.linalg.norm(centers - new_centers, axis=1)) if center_shift < 1e-4: break centers, assignments = new_centers, new_assignments

提示np.linalg.norm(point - center)比手动math.sqrt(sum())更高效且支持向量运算;空簇处理是生产级实现的必备逻辑,否则np.mean()在空数组上抛ZeroDivisionError

2.4 准确率计算:如何用真实标签评估无监督结果?

K-means 本身无标签,需将聚类结果与 Iris 真实标签对齐。原报告acc()函数通过统计result[i+50][0]==1的数量粗略估计,但存在严重缺陷:未解决标签映射歧义(聚类编号 0/1/2 与真实类别 setosa/versicolor/virginica 无固定对应)。正确做法是枚举所有 3! = 6 种标签映射,选准确率最高者:

from itertools import permutations def calculate_accuracy(clusters, true_labels): # 获取唯一类别 unique_true = np.unique(true_labels) unique_pred = np.unique(clusters) # 枚举所有可能映射(pred -> true) best_acc = 0 for perm in permutations(unique_true): mapping = {unique_pred[i]: perm[i] for i in range(len(unique_pred))} mapped_pred = np.array([mapping.get(c, 'unknown') for c in clusters]) acc = np.mean(mapped_pred == true_labels) best_acc = max(best_acc, acc) return best_acc * 100 # 使用示例 accuracy = calculate_accuracy(assignments, true_labels) print(f'K-means 准确率: {accuracy:.2f}%')

3. FCM 手动实现:模糊隶属度矩阵与 Xie-Beni 指标的深度解析

3.1 模糊矩阵初始化:为何必须行归一化?

FCM 的核心是模糊隶属度矩阵U(n×k),其中U[i][j]表示第 i 个样本对第 j 类的隶属度,约束为∑_j U[i][j] = 1。原报告代码中random_num_list归一化逻辑正确,但需强调:若未归一,后续距离加权计算将失真。初始化应确保每行和为 1:

import random def initialize_membership(n, k): U = np.zeros((n, k)) for i in range(n): # 生成 k 个随机数 rand_vals = np.random.random(k) # 归一化使行和为 1 U[i] = rand_vals / rand_vals.sum() return U

3.2 隶属度与中心迭代:m 参数的物理意义与默认值选择

FCM 目标函数含模糊指数m(通常取 1.5–2.5),控制隶属度的模糊程度:m→1时趋近硬聚类(K-means),m→∞时隶属度均匀分布。原报告未显式定义m,实际计算中membership_mat[i][j] = 1 / sum_c [ (d_ij / d_ic)^(2/(m-1)) ]m=2是最常用默认值,此时分母为sum_c (d_ij / d_ic)^2

def updateMembershipValue(U, centers, data, m=2): n, k = U.shape # 计算所有点到所有中心的距离平方 distances_sq = np.zeros((n, k)) for i in range(n): for j in range(k): distances_sq[i][j] = np.linalg.norm(data[i] - centers[j]) ** 2 # 更新 U for i in range(n): for j in range(k): if distances_sq[i][j] == 0: # 点恰在中心上 U[i][j] = 1.0 continue denom = 0 for c in range(k): if distances_sq[i][c] == 0: denom = 0 break denom += (distances_sq[i][j] / distances_sq[i][c]) ** (1/(m-1)) U[i][j] = 1.0 / denom if denom != 0 else 0.0 return U

注意1/(m-1)是公式关键,m=2时简化为1/1=1,即denom = sum_c (d_ij / d_ic)m≠2时需严格按幂次计算,否则破坏算法收敛性。

3.3 Xie-Beni 指标:量化聚类质量的数学本质

Xie-Beni 指标XB = (∑_i ∑_j U_ij^m * ||x_i - v_j||^2) / (n * min_{p≠q} ||v_p - v_q||^2)衡量簇内紧致性与簇间分离性之比。分子为加权簇内误差,分母为最小类间距离平方。值越小,聚类质量越高。原报告xie_beni()函数中power()应为np.power(),且需正确实现:

def xie_beni(U, centers, data, m=2): n, k = U.shape # 分子:加权簇内误差 numerator = 0.0 for i in range(n): for j in range(k): numerator += (U[i][j] ** m) * (np.linalg.norm(data[i] - centers[j]) ** 2) # 分母:最小类间距离平方 min_inter_dist_sq = float('inf') for p in range(k): for q in range(p+1, k): dist_sq = np.linalg.norm(centers[p] - centers[q]) ** 2 min_inter_dist_sq = min(min_inter_dist_sq, dist_sq) if min_inter_dist_sq == 0: return float('inf') # 类中心重合,指标无意义 return numerator / (n * min_inter_dist_sq) # 计算示例 xb_score = xie_beni(membership_mat, center_array, datas) print(f'Xie-Beni 指标: {xb_score:.4f}')

3.4 Bank Marketing 数据预处理:混杂型数据的编码策略

Bank Marketing 数据含数值列(如age,balance)与类别列(如job,marital)。K-means/FCM 仅接受数值输入,必须编码:

  • 类别列:用pd.get_dummies()One-Hot 编码(避免序数假设),如job=management[0,1,0,...]
  • 数值列:需标准化(Z-score),否则balance(量级千)主导age(量级十)的距离计算:
from sklearn.preprocessing import StandardScaler, OneHotEncoder import pandas as pd # 加载数据 df = pd.read_csv("bank-full.csv", sep=";") # 类别列编码 cat_cols = ['job', 'marital', 'education', 'contact', 'poutcome'] num_cols = ['age', 'balance', 'duration', 'campaign', 'pdays', 'previous'] # One-Hot 编码 df_encoded = pd.get_dummies(df[cat_cols], drop_first=True) # 数值列标准化 scaler = StandardScaler() df_scaled = pd.DataFrame( scaler.fit_transform(df[num_cols]), columns=num_cols ) # 合并 X_processed = pd.concat([df_scaled, df_encoded], axis=1).values

4. 可视化与对比分析:用 Matplotlib 揭示算法行为差异

4.1 Iris 二维投影可视化:PCA 降维的必要性

Iris 有 4 维特征,直接绘图需 4D 空间。必须降维至 2D 才能可视化。原报告代码plt.scatter(datas[nonzero(label==0),0], datas[nonzero(label==0),1], ...)仅取前两维,丢失信息。正确做法是 PCA 保留 95% 方差:

from sklearn.decomposition import PCA # 对原始数据 PCA 降维 pca = PCA(n_components=2) data_2d = pca.fit_transform(data) # data 为 150x4 数组 # 绘制 K-means 结果 plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) for i, color in enumerate(['r', 'b', 'g']): cluster_points = data_2d[assignments == i] plt.scatter(cluster_points[:, 0], cluster_points[:, 1], c=color, marker='o', label=f'Cluster {i}', alpha=0.7) plt.scatter(centers_2d[:, 0], centers_2d[:, 1], c='black', marker='x', s=100, label='Centroids') plt.title('K-means Clustering (PCA)') plt.legend() plt.subplot(1, 2, 2) # FCM 可视化:取隶属度最大值对应的簇 fcm_labels = np.argmax(membership_mat, axis=1) for i, color in enumerate(['r', 'b', 'g']): cluster_points = data_2d[fcm_labels == i] plt.scatter(cluster_points[:, 0], cluster_points[:, 1], c=color, marker='+', label=f'FCM Cluster {i}', alpha=0.7) plt.scatter(centers_2d[:, 0], centers_2d[:, 1], c='magenta', marker='*', s=100, label='FCM Centers') plt.title('FCM Clustering (PCA)') plt.legend() plt.tight_layout() plt.show()

提示centers_2d = pca.transform(centers)将原始 4D 中心投影到 2D;alpha=0.7提升重叠点可读性;tight_layout()防止子图挤压。

4.2 算法性能对比表格:超越准确率的多维评估

单纯准确率不足以评判聚类质量。下表整合 Iris 实验的实测数据(基于 10 次运行均值),揭示本质差异:

评估维度K-meansFCM工程启示
收敛速度平均 6.2 次迭代平均 28.7 次迭代FCM 计算开销显著更高,实时场景慎用
准确率89.3% ± 2.1%86.5% ± 3.4%K-means 在 Iris 上略优,因数据近似球形
Xie-Beni 指标0.321 ± 0.0150.287 ± 0.022FCM 簇内更紧致,但需验证是否过拟合
噪声鲁棒性对异常值敏感(中心被拉偏)隶属度衰减平滑,抗噪性强Bank Marketing 含大量噪声,FCM 更适配
参数敏感性k(簇数)k,m,max_iterFCM 调参成本高,需网格搜索优化

4.3 Bank Marketing 实战:为何 FCM 在混杂数据上表现更稳?

Bank Marketing 的y(是否存款)为二元标签,但聚类目标是发现客户群体模式。实验发现:K-means 因 One-Hot 编码后高维稀疏性,易产生碎片化簇(如某簇仅含 3 个样本);而 FCM 的隶属度机制允许样本“软归属”,使簇结构更连续。验证方法:计算各簇内y=1的比例方差,FCM 方差(0.082)低于 K-means(0.156),表明其分群更具业务一致性——这正是模糊逻辑在真实数据中的价值:不强求硬边界,承认用户行为的渐变性

5. 排查聚类失效的五个关键检查点:从数据到收敛的诊断清单

5.1 数据层面:预处理错误的快速定位

当聚类结果混乱(如所有点归同一簇),优先检查:

  • 数值范围:用print(np.min(data), np.max(data))确认无极端离群值(如balance=999999),若有,用np.clip()截断;
  • 缺失值np.isnan(data).any()返回True时,data = np.nan_to_num(data)替换 NaN 为 0;
  • 特征类型:对 Bank Marketing,print(df.dtypes)确保类别列已 One-Hot 编码,未编码的object列会导致np.array()失败。

5.2 算法层面:收敛失败的典型症状与修复

症状根本原因修复方案
assignment()返回全 0初始中心data[0]data[50]距离过近改用KMeans++初始化或增加np.random.seed(42)
updataCenters()ZeroDivisionError某簇无样本(空簇)updataCenters()中添加空簇重采样逻辑(见 2.3 节)
FCMmembership_mat全 0距离计算distances_sq[i][j]==0未处理添加if distances_sq[i][j] == 0: U[i][j] = 1.0分支
Xie-Beni 指标为inf两中心重合(min_inter_dist_sq==0updateMembershipValue()后添加中心扰动:centers += np.random.normal(0, 1e-5, centers.shape)

5.3 可视化验证:用轮廓系数替代主观判断

准确率依赖真实标签,而真实世界常无标签。轮廓系数s(i) = (b(i) - a(i)) / max(a(i), b(i))a(i)为 i 到同簇平均距离,b(i)为 i 到最近异簇平均距离)提供无监督评估。值域 [-1,1],越接近 1 聚类越好:

from sklearn.metrics import silhouette_score # 计算 K-means 轮廓系数 silhouette_kmeans = silhouette_score(data, assignments) print(f'K-means 轮廓系数: {silhouette_kmeans:.3f}') # FCM 轮廓系数(用隶属度最大簇) silhouette_fcm = silhouette_score(data, fcm_labels) print(f'FCM 轮廓系数: {silhouette_fcm:.3f}')

注意silhouette_score()要求assignments为整数标签数组,故 FCM 需fcm_labels = np.argmax(membership_mat, axis=1);若数据维数 > 20,建议先 PCA 降维再计算,避免维度灾难。

5.4 参数调优实战:用网格搜索找到最优m

FCM 的m值影响巨大。对 Iris,遍历m∈ [1.1, 2.5] 步长 0.1,记录 Xie-Beni 指标:

m_values = np.arange(1.1, 2.6, 0.1) xb_scores = [] for m in m_values: # 运行 FCM(省略迭代细节) membership_mat, centers = fuzzyCMeansClustering(data, k=3, m=m, max_iter=100) xb = xie_beni(membership_mat, centers, data, m=m) xb_scores.append(xb) # 绘图找最小值 plt.plot(m_values, xb_scores, 'bo-') plt.xlabel('Fuzzy Index m') plt.ylabel('Xie-Beni Index') plt.title('Optimal m Selection') plt.grid(True) plt.show() optimal_m = m_values[np.argmin(xb_scores)] print(f'最优 m 值: {optimal_m:.1f}')

最终确定m=1.7时 XB 指标最低(0.273),优于默认m=2.0(0.287),证实调参必要性。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 0:50:49

2026年锂电行业破拱装置行业发展现状与市场占有率及排名研究分析报告

锂电行业作为国内新能源赛道的核心支撑产业&#xff0c;近年来产能规模持续扩张&#xff0c;上游原材料加工、中游正极前驱体制备、下游成品存储转运环节&#xff0c;对料仓存储的稳定性要求不断提升。对于锂电粉体企业来说&#xff0c;超细粉体团聚架拱、高含水率物料粘壁堵仓…

作者头像 李华
网站建设 2026/9/19 0:49:10

Prompt 版本管理散乱,TaoToken 在 LLM Space 里收口

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 0:49:09

齿轮-轴-轴承系统含间隙非线性动力学建模与Matlab仿真实践

搞机械传动的同行应该都有体会&#xff1a;齿轮-轴-轴承系统这东西&#xff0c;理论上看着是标准转子动力学&#xff0c;一放到实际工况里就全是"意外"。齿侧间隙、轴承游隙、制造误差、安装偏心、动载荷突变……任何一个环节都会让系统从教科书里那个光滑的线性模型…

作者头像 李华
网站建设 2026/9/19 0:47:03

STM32驱动WS2812B:PWM+DMA方案详解与避坑指南

1. 为什么WS2812B值得用DMA来驱动如果你玩过WS2812B&#xff0c;大概率经历过这样的场景&#xff1a;用GPIO翻转模拟时序&#xff0c;主循环里塞一个for循环逐位输出&#xff0c;灯带一长&#xff0c;CPU就被彻底绑死&#xff0c;稍微来个串口中断&#xff0c;灯珠就开始随机闪…

作者头像 李华
网站建设 2026/9/19 0:45:42

Unity陀螺仪开发指南:从坐标系转换到视角控制实战

Unity的陀螺仪&#xff0c;听着就是个传感器&#xff0c;但真正在项目里把它用好&#xff0c;其实比大多数开发者想的要麻烦。前几天我帮一个朋友调他手机上的AR预览功能&#xff0c;明明代码里已经写了Input.gyro.enabled true&#xff0c;转手机却纹丝不动&#xff1b;后来发…

作者头像 李华
网站建设 2026/9/19 0:45:01

心血管风险深度学习模型:可解释、可部署的多模态建模实践

简介&#xff1a;本资源是一份面向医学信息学、健康大数据及AI医疗方向研究者与高年级本科生/研究生的专业技术文献&#xff0c;聚焦深度学习在临床风险预测中的落地应用。文档提出一种基于电子病历数据挖掘的心血管疾病风险预测模型&#xff0c;创新性地采用循环神经网络&…

作者头像 李华