手把手实现GMM聚类:从模拟数据到EM算法迭代全过程Python代码包

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个资源提供一套完整、可直接运行的高斯混合模型(GMM)聚类实现,全部用原生Python编写,不依赖scikit-learn等高级库。包含三个核心模块:genSample.py用于生成二维或三维的带标签高斯混合样本,支持自定义簇数量、维度、样本总数;gmm.py封装了标准EM算法逻辑,涵盖E步(计算隐变量后验概率)和M步(更新均值、协方差、混合系数),并输出每轮迭代的对数似然值,便于追踪收敛过程;main.py作为主入口,整合数据加载、模型初始化、迭代训练与结果可视化。配套提供两个示例数据文件(gmm.data和sample.data),以及详细README.md说明参数含义、运行步骤和调试建议。支持手动指定初始参数(如初始均值位置、协方差矩阵形式、先验权重),适合深入理解GMM数学原理、观察EM算法行为、教学演示或课程实验作业。所有代码结构清晰、注释充分,便于逐行调试和原理验证。

1. 为什么我坚持手写GMM——不是为了造轮子,而是为了看清“概率聚类”的心跳

你有没有试过调用sklearn.mixture.GaussianMixture,传入数据、设好n_components,几行代码就拿到聚类结果,但心里却始终悬着一个问题:那个“软分配”到底是怎么算出来的?E步里那个γ(z_{nk})——也就是第k个高斯成分对第n个样本的后验责任(responsibility)——它背后那串指数、行列式、逆矩阵,究竟是怎么一步步把模糊的归属感量化成0到1之间的实数?M步更新均值时,为什么是加权平均?协方差矩阵为什么带权重求和再除以总责任?混合系数π_k为什么等于所有样本对该成分的平均责任?这些不是公式推导书上的符号游戏,而是EM算法每一次呼吸的真实节律。

这套代码包,就是我当年在讲授《机器学习基础》课程时,为本科生亲手打磨的一套“解剖级”教学工具。它不追求速度,不包装接口,甚至刻意回避了NumPy的高级广播技巧——所有矩阵乘法、求逆、行列式计算,都用最直白的for循环+基础线性代数逻辑展开。目的只有一个:让你在调试器里单步执行gmm.py的每一行时,能清晰看到gamma[n][k]这个数字是怎么从log_pi[k] + log_gaussian(x[n], mu[k], cov[k])一步步累加、归一化而来;能看到mu_new[k]是如何被sum(gamma[n][k] * x[n]) / sum(gamma[n][k])一锤定音地拉向数据密集区;更能亲眼见证对数似然值log_likelihood如何在前几轮剧烈震荡后,像潮水退去般缓慢爬升、最终趋于平坦——那条平缓的曲线,就是收敛的指纹。

它适合三类人:第一类是刚学完最大似然估计、正在啃EM算法推导的研究生,你需要一个可触摸的沙盒,把纸面公式变成内存里的变量;第二类是高校教师或助教,需要一套零依赖、结构透明的课堂演示素材,学生可以改一个初始均值,立刻看到聚类路径如何偏移;第三类是算法工程师,在模型效果异常时,想绕过黑箱,直接检查E步输出的责任矩阵是否合理、M步更新的协方差是否出现病态(比如行列式趋近于零)。这不是生产环境的替代品,而是一把手术刀——当你需要确认“模型到底在想什么”时,它比任何可视化工具都更锋利。

关键词里反复出现的GMM聚类、EM算法、Python实现、高斯混合模型,在这里不是标签,而是四个锚点:GMM聚类定义了问题域——我们处理的是概率性的、重叠的簇;EM算法框定了求解范式——通过交替优化隐变量与参数来逼近极大似然;Python实现意味着完全可控——没有Cython加速,没有预编译,所有中间状态均可打印;高斯混合模型则锁定了数学内核——每个簇由一个多元高斯分布刻画,其参数(均值、协方差、权重)正是我们要迭代求解的对象。接下来,我会带你从模拟数据的“人造土壤”开始,亲手种下这颗模型,再一帧一帧播放它的生长过程。

2. 整体设计思路拆解:为什么模块要这样切分?为什么不用sklearn?

2.1 三层架构:生成—求解—驱动,各司其职不越界

整个资源包采用清晰的三层职责分离,这不是为了炫技,而是为了精准控制每一个教学/调试触点:

  • 数据生成层(genSample.py):它只做一件事——按需制造“已知真相”的数据。你指定3个簇、2维空间、每簇500个点,它就返回一个(1500, 2)的numpy.ndarray和一个长度为1500的labels数组,其中labels[i]明确告诉你第i个点本应属于哪个高斯成分。关键在于,它支持两种协方差模式:'spherical'(球形,即各向同性,协方差为标量乘单位阵)和'full'(满秩,任意正定矩阵)。这直接对应GMM中两种常见简化假设,也决定了后续M步更新协方差的计算复杂度。我特意没让它生成“完美分离”的数据——而是让簇间有可控重叠(通过调整簇心距离和协方差大小),因为真实世界的数据从来不会给你清晰的边界,而EM算法的魅力,恰恰体现在它如何在这种模糊地带给出最优的概率解释。

  • 核心求解层(gmm.py):这是心脏。它不碰数据IO,不画图,不打印日志,只专注EM循环本身。入口函数fit()接收数据X、簇数K、最大迭代次数max_iter等纯算法参数,并返回一个包含所有中间状态的字典:{'mu': [...], 'cov': [...], 'pi': [...], 'gamma': [...], 'log_likelihoods': [...]}。这里的设计哲学是“最小接口,最大可见性”。例如,gamma被完整保留下来——它是一个(N, K)的二维数组,每一行就是一个样本对K个簇的责任分布。你可以随时取出gamma[0],看看第一个点认为自己属于各簇的概率分别是多少;也可以计算np.argmax(gamma, axis=1)得到硬聚类结果,再与genSample.py生成的真标签对比,量化聚类准确率。这种“中间态全暴露”的设计,是理解EM行为不可替代的窗口。

  • 驱动整合层(main.py):它像一位严谨的实验员,负责串联全流程:加载数据(支持.data文本文件或调用genSample.py生成)、设置初始参数(可随机初始化,也可手动指定mu_init, cov_init, pi_init)、调用gmm.fit()、监控收敛(判断连续两轮log_likelihood变化小于tol=1e-3则停止)、最后调用可视化函数。它的存在,让整个流程可复现、可配置、可对比——比如你可以跑两次,一次用随机初值,一次把初始均值故意设在错误位置,然后并排看它们的收敛曲线和最终簇心漂移路径,这种对比带来的洞见,远超任何理论讲解。

2.2 拒绝sklearn:不是技术傲慢,而是教学刚需

有人会问:既然sklearn的GMM这么成熟,为什么还要手写?答案很实在:sklearn封装了太多“看不见”的优化,而这恰恰是教学最大的敌人。

举三个典型例子:
1. 初始化策略:sklearn默认用K-means++初始化均值,这本身就是一个启发式算法。当你想纯粹观察EM在不同初值下的鲁棒性时,你无法关闭它,也无法精确控制初始mu的位置。而我们的main.py允许你传入mu_init=[[0,0], [5,5], [-3,4]],让算法从你指定的三个点出发,这是验证“初值敏感性”的基石。
2. 协方差约束:sklearn的covariance_type参数虽支持'full'/'tied'/'diag'/'spherical',但其内部实现对奇异协方差矩阵做了自动正则化(如加一个小的1e-6 * I),这掩盖了EM在病态数据上可能遇到的真实数值问题。我们的gmm.py在计算协方差时,如果遇到np.linalg.det(cov_k) < 1e-10,会直接抛出ValueError("Covariance matrix is singular!"),逼你去思考:是数据维度太高?还是某个簇的样本太少?还是初始值太差?这种“报错即教学”的设计,比静默修复更有价值。
3. 收敛监控粒度:sklearn的verbose参数只能告诉你“第几轮迭代”,但不输出具体的log_likelihood值。而我们的实现,每轮迭代后都计算并追加到log_likelihoods列表中。你可以用plt.plot(log_likelihoods)画出一条真实的收敛曲线,甚至计算np.diff(log_likelihoods)看每一步的增益,从而直观理解“为什么EM保证单调不减”。

所以,这不是在重复造轮子,而是在铸造一把专门用于“拆解轮子”的扳手。当你需要理解轮子为何转动、轴承如何受力、齿轮怎样咬合时,预制的整车永远不如散落的零件来得透彻。

3. 核心细节解析与实操要点:E步与M步的数学落地

3.1 E步:计算后验责任γ(z_{nk})——一场精密的概率分配

E步的核心任务,是给定当前参数θ^{(t)} = {μ_k^{(t)}, Σ_k^{(t)}, π_k^{(t)}},计算每个样本x_n属于每个簇k的后验概率γ(z_{nk}) = p(z_{nk}=1 | x_n, θ^{(t)})。根据贝叶斯定理,这等于:

γ(z_{nk}) = [π_k^{(t)} * N(x_n | μ_k^{(t)}, Σ_k^{(t)})] / [Σ_j π_j^{(t)} * N(x_n | μ_j^{(t)}, Σ_j^{(t)})]

其中N(x | μ, Σ)是d维高斯概率密度函数:N(x|μ,Σ) = (2π)^{-d/2} |Σ|^{-1/2} exp{ -1/2 (x-μ)^T Σ^{-1} (x-μ) }

在gmm.py的_e_step()函数中,这个计算被拆解为四步,每一步都服务于数值稳定性:

  1. 预计算对数项:直接计算π_k * N(x_n|μ_k,Σ_k)会导致极小值下溢(underflow)。因此,我们先计算对数形式:log_num[n][k] = log(π_k) + log(N(x_n|μ_k,Σ_k))。其中log(N(...))的计算又细分为:
    - log_det_cov = np.log(np.linalg.det(cov_k)) (协方差行列式的对数)
    - inv_cov = np.linalg.inv(cov_k) (协方差逆矩阵)
    - diff = x_n - mu_k (样本与均值的差向量)
    - quad_form = np.dot(diff.T, np.dot(inv_cov, diff)) (马氏距离的二次型)
    - 最终 log_N = -0.5 * (d * np.log(2*np.pi) + log_det_cov + quad_form)

  2. LogSumExp技巧:分母是Σ_j exp(log_num[n][j]),直接计算exp()仍会溢出。标准解法是提取最大值:log_denom[n] = log_sum_exp(log_num[n]) = max_val + np.log(np.sum(np.exp(log_num[n] - max_val)))。这确保了指数运算的输入都在安全范围内(log_num[n][j] - max_val ≤ 0)。

  3. 计算对数责任:log_gamma[n][k] = log_num[n][k] - log_denom[n]。此时log_gamma的每一行,其exp()后的和严格等于1。

  4. 转换为浮点责任:gamma[n][k] = np.exp(log_gamma[n][k])。至此,gamma矩阵完成,它是一个(N, K)的概率矩阵,每行和为1。

提示:在调试时,务必检查gamma的合法性。一个简单的断言assert np.allclose(gamma.sum(axis=1), 1.0, atol=1e-10)能帮你快速捕获E步中的数值错误。我曾在一个早期版本中忘记在log_sum_exp里处理全负无穷的情况,导致某轮迭代后gamma出现NaN,这个断言第一时间揪出了问题。

3.2 M步:更新参数θ^{(t+1)}——用责任加权的统计重构

M步的目标,是最大化Q函数,它等价于用E步计算出的责任γ(z_{nk})作为“软标签”,对每个簇k进行加权统计。

  • 更新混合系数π_k:π_k^{(t+1)} = (1/N) * Σ_n γ(z_{nk})。这非常直观——π_k就是簇k被分配到的所有样本的平均责任,即该簇在整体混合中的“占比”。在代码中,就是pi_new[k] = np.mean(gamma[:, k])。

  • 更新均值μ_k:μ_k^{(t+1)} = [Σ_n γ(z_{nk}) * x_n] / [Σ_n γ(z_{nk})]。分子是x_n按责任加权的和,分母是总责任(即有效样本数)。这本质上是加权质心。代码实现为:
    python weighted_sum = np.zeros(d) for n in range(N): weighted_sum += gamma[n, k] * X[n] mu_new[k] = weighted_sum / np.sum(gamma[:, k])

  • 更新协方差Σ_k:Σ_k^{(t+1)} = [Σ_n γ(z_{nk}) * (x_n - μ_k^{(t+1)}) (x_n - μ_k^{(t+1)})^T] / [Σ_n γ(z_{nk})]。这是加权的协方差矩阵,权重同样是γ(z_{nk})。注意,这里必须使用更新后的μ_k^{(t+1)},而非旧的μ_k^{(t)},否则会破坏EM的收敛保证。代码中,我们先计算外积np.outer(diff, diff),再加权累加。

注意:协方差更新是数值陷阱的高发区。当某个簇的总责任np.sum(gamma[:, k])极小时(比如<1),分母接近零,会导致Σ_k爆炸。我们的代码在_m_step()末尾加入了保护:
python if np.sum(gamma[:, k]) < 1e-8: raise ValueError(f"Cluster {k} has near-zero responsibility ({np.sum(gamma[:, k]):.2e}). Try different initialization.")
这个检查强制你面对数据或初值的问题,而不是让模型默默产生无意义的结果。

3.3 对数似然监控:收敛的唯一可信信标

EM算法的理论保证是:每次迭代,完整数据的对数似然log p(X|θ)单调不减。因此,监控log_likelihood是判断收敛最可靠的方式。其计算公式为:

log p(X|θ) = Σ_n log [ Σ_k π_k * N(x_n | μ_k, Σ_k) ]

这看起来和E步分母的计算一模一样!事实上,log_likelihood正是E步中log_denom数组所有元素的和:log_likelihood = np.sum(log_denom)。

在main.py中,我们每轮迭代后都计算并记录这个值。一个健康的收敛过程应该是:前几轮log_likelihood快速上升(有时甚至因初值不佳而短暂下降,这是正常现象),随后上升速度越来越慢,最终连续几轮变化小于tol(默认1e-3)即停止。你可以用以下代码快速绘制它:

import matplotlib.pyplot as plt
plt.plot(log_likelihoods)
plt.xlabel('Iteration')
plt.ylabel('Log Likelihood')
plt.title('EM Algorithm Convergence')
plt.grid(True)
plt.show()

这条曲线,就是EM算法的心电图。它不撒谎——如果曲线震荡剧烈或长期停滞,说明你的数据不适合GMM,或者初值选得太差,或者簇数K设错了。这比任何外部评估指标(如轮廓系数)都更能反映模型内在的拟合质量。

4. 实操过程与核心环节实现:从零运行到深度调试

4.1 环境准备与依赖安装:轻量纯净,开箱即用

整个项目仅依赖两个基础库:numpy用于数值计算,matplotlib用于可视化。requirements.txt内容极其简单:

numpy==1.24.3
matplotlib==3.7.1

安装只需一行命令:

pip install -r requirements.txt

为什么坚持如此精简?因为scipy虽然提供了scipy.linalg.sqrtm等高级矩阵函数,但它会引入额外的编译依赖(如BLAS/LAPACK),在某些教学机房或老旧Linux发行版上可能安装失败。而我们的所有矩阵运算(求逆、行列式、特征值分解)都基于numpy.linalg,它在绝大多数Python环境中都是开箱即用的。matplotlib也仅用于最后的2D/3D散点图,如果你只需要后台训练和数值分析,甚至可以注释掉所有import matplotlib和绘图代码,完全不影响核心算法运行。

实操心得:我在一次嵌入式AI课程中,曾让学生在树莓派4B上运行此代码包。由于树莓派ARM架构下scipy编译耗时极长,而numpy早已预装,这套轻量依赖让我们节省了整整一节课的环境搭建时间,学生得以把全部精力聚焦在算法理解上。

4.2 数据生成实战:用genSample.py定制你的“理想实验场”

genSample.py是你的数据工厂。它的核心函数generate_gmm_data(n_samples=1000, n_components=3, n_features=2, ...)接受一系列直观参数:

  • n_samples: 总样本数,如1500
  • n_components: 簇数K,如3
  • n_features: 维度d,支持2(平面)或3(立体)
  • means: 初始簇心坐标列表,如[[0, 0], [5, 5], [-3, 4]]。若为None,则随机生成
  • covariances: 协方差矩阵列表,每个是(d,d)数组。若为None,则按covariance_type生成
  • covariance_type: 'spherical'(标量)或'full'(满秩)
  • weights: 各簇先验权重,如[0.4, 0.35, 0.25]。若为None,则均匀分配

下面是一个完整的生成示例,创建一个具有挑战性的2D数据集:

# 在Python交互环境或main.py中
from genSample import generate_gmm_data
import numpy as np

# 创建3个簇:两个靠近(易混淆),一个远离
means = [[0, 0], [1.5, 1.5], [8, 2]]
# 使用'spherical'协方差,半径分别为0.8, 1.2, 1.0
covariances = [0.64 * np.eye(2), 1.44 * np.eye(2), 1.0 * np.eye(2)]
weights = [0.4, 0.4, 0.2]

X, y_true = generate_gmm_data(
    n_samples=1200,
    n_components=3,
    n_features=2,
    means=means,
    covariances=covariances,
    weights=weights,
    random_state=42  # 确保可复现
)

print(f"Generated data shape: {X.shape}")
print(f"True labels: {np.unique(y_true)}")
print(f"Label distribution: {np.bincount(y_true)}")

运行后,你会得到一个(1200, 2)的X和一个(1200,)的y_true。y_true是整数标签(0,1,2),它由genSample.py内部根据每个点的真实后验概率p(x_n|z_{nk})π_k采样生成,因此它代表了数据的“地面实况”。这个y_true,是你后续评估聚类效果(如计算Adjusted Rand Index)的黄金标准。

常见问题:为什么生成的数据文件sample.data里只有坐标,没有标签?因为sample.data是为main.py的“加载已有数据”模式准备的,它模拟真实场景——你只有X,没有y_true。而y_true只存在于genSample.py的内存中,供你做内部验证。如果你想把带标签的数据保存下来用于其他实验,只需在生成后添加:
python np.savetxt('labeled_sample.data', np.column_stack([X, y_true]), fmt='%.6f')

4.3 模型训练全流程:main.py的逐行解析与参数调优

main.py是整个流程的指挥中心。我们以一个典型的2D训练为例,逐步解析其关键步骤:

Step 1: 数据加载

# 支持两种方式:加载文件 或 生成新数据
if args.mode == 'load':
    X = np.loadtxt(args.data_file)  # 如 gmm.data
elif args.mode == 'generate':
    X, y_true = generate_gmm_data(...)  # 如上节所示

Step 2: 初始化参数
这是影响收敛速度和最终结果的关键。main.py提供了三种策略:
- init='random':均值在数据范围随机,协方差为单位阵缩放,权重均匀。
- init='kmeans':先用简易K-means(非sklearn)粗略聚类,用簇心和簇内散度初始化。
- init='manual':用户传入mu_init, cov_init, pi_init。

对于教学,我强烈推荐init='manual'。例如,故意把初始均值设在错误位置:

mu_init = [[-5, -5], [0, 0], [10, 10]]  # 全部偏离真实簇心

然后运行,观察EM如何“挣扎”着把它们拉回正确区域。这种“人为制造困难”的实验,比任何理论都更能建立直觉。

Step 3: 调用GMM训练

from gmm import GMM
gmm = GMM(n_components=args.n_components, max_iter=args.max_iter, tol=args.tol)
results = gmm.fit(X, mu_init=mu_init, cov_init=cov_init, pi_init=pi_init)

results字典包含了所有宝藏:results['mu']是最终均值列表,results['cov']是协方差列表,results['gamma']是最终责任矩阵,results['log_likelihoods']是收敛曲线。

Step 4: 可视化与评估
对于2D数据,main.py会自动生成三张图:
- 左图:原始数据散点图,颜色按y_true(如果可用)。
- 中图:GMM聚类结果,颜色按np.argmax(results['gamma'], axis=1)(硬聚类)。
- 右图:收敛曲线log_likelihoods。

你可以通过命令行参数精细控制:

# 生成数据并训练,手动初始化,保存结果
python main.py --mode generate --n_components 3 --n_samples 1200 --init manual \
               --mu_init "0,0;1.5,1.5;8,2" --cov_init "0.64,0;0,0.64;1.44,0;0,1.44;1.0,0;0,1.0" \
               --output_dir ./results/

# 加载现有数据文件训练
python main.py --mode load --data_file gmm.data --n_components 2 --max_iter 100

实操心得:在调试时,我习惯在gmm.py的fit()函数内部插入print(f"Iter {iter}: LogLik = {log_likelihood:.4f}")。虽然main.py已经打印了收敛曲线,但实时日志能让我在终端里一眼看到每一轮的进展,尤其当迭代卡住时,能立刻定位是哪一轮出了问题。这个“土办法”,比任何IDE调试器都来得直接。

4.4 高级技巧:3D可视化与协方差椭圆绘制

对于2D数据,main.py不仅画散点图,还绘制了每个簇的协方差椭圆,这是理解GMM几何意义的神来之笔。

椭圆的绘制基于协方差矩阵的特征分解:
- 计算cov_k的特征值λ1, λ2和特征向量v1, v2
- 椭圆的长轴长度正比于√λ1,短轴正比于√λ2
- 椭圆的方向由特征向量v1, v2决定

代码片段如下:

def plot_cov_ellipse(ax, cov, mean, n_std=2.0, **kwargs):
    """绘制2D协方差椭圆"""
    vals, vecs = np.linalg.eigh(cov)  # 特征值和特征向量
    order = vals.argsort()[::-1]  # 降序排列
    vals, vecs = vals[order], vecs[:, order]
    theta = np.degrees(np.arctan2(*vecs[:, 0][::-1]))  # 椭圆旋转角
    width, height = 2 * n_std * np.sqrt(vals)  # 长短轴长度
    ellip = Ellipse(xy=mean, width=width, height=height, angle=theta, **kwargs)
    ax.add_patch(ellip)

调用它:

for k in range(K):
    plot_cov_ellipse(ax, results['cov'][k], results['mu'][k], 
                     n_std=2, alpha=0.3, facecolor='none', edgecolor='red')

这个椭圆,就是该高斯成分“影响力范围”的直观体现——它覆盖了约95%(2σ)的该成分概率质量。当你看到两个椭圆大面积重叠时,就能立刻理解为什么E步会给跨簇样本分配显著的责任。

对于3D数据,main.py同样支持。它使用mpl_toolkits.mplot3d绘制3D散点图,并用半透明球体(plot_surface)近似3D协方差椭球。虽然不如2D椭圆精确,但足以展示簇的三维形态和相对位置。

5. 常见问题与排查技巧实录:那些年踩过的坑与独家解法

5.1 “NaN”与“Infinite”:数值不稳定的第一信号

现象:运行几轮后,log_likelihood变成nan,gamma矩阵出现nan或inf,程序崩溃。

根本原因:高斯密度函数N(x|μ,Σ)的计算中,exp(-0.5 * quad_form)部分,当quad_form极大(如>800)时,exp(-400)在双精度浮点下就是0,导致分子为0;而分母若也接近0,就会出现0/0或inf/inf,结果为nan。

排查步骤:
1. 在_e_step()中,log_num[n][k]计算后,立即检查:if np.any(np.isnan(log_num)) or np.any(np.isinf(log_num)):,打印n, k, quad_form。
2. 定位到具体哪个x_n和哪个k导致quad_form过大。通常是因为x_n离mu_k太远,或cov_k太小(行列式接近零)。

独家解法:
- 动态截断:在计算quad_form前,加一个保护:
python quad_form = np.dot(diff.T, np.dot(inv_cov, diff)) if quad_form > 800: # 防止exp(-400)下溢 log_N = -800.0 # 直接设为一个极小的常数 else: log_N = -0.5 * (d * np.log(2*np.pi) + log_det_cov + quad_form)
- 协方差正则化:在_m_step()更新cov_k后,强制加入微小扰动:
python cov_new[k] += 1e-6 * np.eye(d) # 防止奇异

我的经验:90%的nan问题源于初始协方差矩阵cov_init设得太小(如1e-8 * I)。在main.py中,我默认将spherical协方差初始化为np.var(X, axis=0).mean() * 0.5,即数据总方差的一半,这是一个经验性的、稳健的起点。

5.2 “收敛太慢”或“不收敛”:初值与簇数的双重陷阱

现象:迭代上百轮,log_likelihood仍在缓慢爬升,或在某个平台停滞不前。

排查清单:
- 检查初值:用--init manual传入明显错误的mu_init,看是否比随机初始化更差。如果是,说明算法对初值敏感,需要更好的初始化策略(如kmeans)。
- 检查簇数K:用sklearn.metrics.calinski_harabasz_score或silhouette_score(在main.py中已预留接口)计算不同K值下的分数,寻找拐点。GMM的log_likelihood会随K增大而单调增加,不能直接用于选K,必须用BIC或AIC。
- 检查数据维度:高维数据(d>10)下,|Σ|和Σ^{-1}计算误差放大。尝试先用PCA降到5维再运行。

独家技巧:BIC准则自动选K
我们在main.py中内置了BIC计算:

def compute_bic(log_likelihood, n_params, n_samples):
    return -2 * log_likelihood + n_params * np.log(n_samples)

# n_params for GMM: K-1 (pi) + K*d (mu) + K*d*(d+1)//2 (cov, full)
n_params = (K-1) + K*d + K*d*(d+1)//2
bic = compute_bic(results['log_likelihoods'][-1], n_params, len(X))

运行多个K值(如2,3,4,5),取BIC最小者为最优K。这比凭感觉猜测可靠得多。

5.3 “聚类结果怪异”:责任矩阵gamma的深度诊断

现象:散点图上,明明视觉上分离很好的两个簇,却被GMM合并为一个;或一个紧凑簇被强行拆成两个。

终极诊断法:直接查看gamma

# 打印前5个样本的责任
print("First 5 samples' responsibilities:")
print(results['gamma'][:5])
# 计算每个簇的“主导样本数”
dominant_counts = np.sum(np.argmax(results['gamma'], axis=1) == np.arange(K), axis=0)
print(f"Dominant sample count per cluster: {dominant_counts}")

如果某个dominant_counts[k]远小于len(X)/K,说明该簇未被有效激活。此时应:
- 检查pi_k是否过小(<0.05),若是,可能是M步更新时该簇责任不足;
- 检查mu_k是否离所有x_n都很远(计算np.min(np.linalg.norm(X - mu_k, axis=1))),若是,说明它被“遗弃”了;
- 尝试在下一轮训练中,用--init kmeans,让初始簇心更贴近数据密集区。

最后分享一个小技巧:在main.py中,我添加了一个--debug标志。启用后,它会在每轮迭代后保存gamma矩阵到./debug/iter_001_gamma.npy。你可以用np.load()加载这些文件,用matplotlib.animation制作一个gamma矩阵随时间演化的热力图动画。看着那些概率块如何从均匀分布,逐渐凝聚成清晰的对角优势,你会真正感受到EM算法那无声而磅礴的生命力——它不是在“计算”,而是在“演化”。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个资源提供一套完整、可直接运行的高斯混合模型(GMM)聚类实现,全部用原生Python编写,不依赖scikit-learn等高级库。包含三个核心模块:genSample.py用于生成二维或三维的带标签高斯混合样本,支持自定义簇数量、维度、样本总数;gmm.py封装了标准EM算法逻辑,涵盖E步(计算隐变量后验概率)和M步(更新均值、协方差、混合系数),并输出每轮迭代的对数似然值,便于追踪收敛过程;main.py作为主入口,整合数据加载、模型初始化、迭代训练与结果可视化。配套提供两个示例数据文件(gmm.data和sample.data),以及详细README.md说明参数含义、运行步骤和调试建议。支持手动指定初始参数(如初始均值位置、协方差矩阵形式、先验权重),适合深入理解GMM数学原理、观察EM算法行为、教学演示或课程实验作业。所有代码结构清晰、注释充分,便于逐行调试和原理验证。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

用EM算法估计GMM参数的实现 本文演示了EM算法(期望最大化算法)一个典型的应用场景,可用于高斯混合模型(Gaussian Mixture Model, GMM)的参数估计。 阅读详情

相关推荐

EM聚类原理与实战:高斯混合模型GMM详解

EM聚类是一种基于概率建模的软聚类方法,核心依托高斯混合模型(GMM)对数据分布进行多成分建模。其原理在于通过E步(期望)计算隐变量后验概率、M步(最大化)更新参数,迭代逼近似然函数局部最优。相比K-Means的硬划分,EM支持不确定性量化与椭球簇拟合,技术价值体现在对混合身份、边界模糊、特征相关性等真实场景的鲁棒建模能力。广泛应用于用户分群、基因表达分析、语音识别等需精细概率刻画的领域。本文以GMM为骨架、EM算法为引擎,系统解析从直觉到代码落地的完整链路。

weixin_34162228的博客 508

高斯混合模型GMM与EM算法的Python实现

GMM与EM算法的Python实现 高斯混合模型(GMM)是一种常用的聚类模型,通常我们利用最大期望算法(EM)对高斯混合模型中的参数进行估计。 1. 高斯混合模型(Gaussian Mixture models, GMM) 高斯混合模型(Gaussian Mixture Model,GMM)是一种软聚类模型。 GMM也可以看作是K-means的推广,因为GMM不仅是考虑到了数...

aizhao3648的博客 2732

深度探索:机器学习中的高斯混合模型(GMM)原理及其应用

高斯混合模型凭借其强大的概率建模能力和广泛应用前景,在机器学习领域占据重要地位。尽管存在参数选择难题和局部最优风险,但通过结合先验知识、使用合适的初始化策略、引入贝叶斯框架或与其他算法(如深度学习)融合,可以有效提升GMM的性能和泛化能力。随着计算资源的增长和算法研究的深入,高斯混合模型及其变种有望在更大规模、更高维度、更复杂结构的数据分析任务中发挥关键作用。同时,对模型选择、优化算法和混合模型族的理论研究也将持续推动该领域的进步。

qq_51320133的博客 1万+

gmm聚类python_GMM与EM算法的Python实现

高斯混合模型(GMM)是一种常用的聚类模型,通常我们利用最大期望算法(EM)对高斯混合模型中的参数进行估计。本教程中,我们自己动手一步步实现高斯混合模型。高斯混合模型(Gaussian Mixture Model,GMM)是一种软聚类模型。 GMM也可以看作是K-means的推广,因为GMM不仅是考虑到了数据分布的均值,也考虑到了协方差。和K-means一样,我们需要提前确定簇的个数。GMM的基本...

weixin_39925959的博客 1638

数据处理和分析之数据聚类:Gaussian Mixture Models (GMM):GMM的高级主题:贝叶斯GMM

GMM 是一种强大的数据聚类方法,能够识别数据中的多个潜在的高斯分布。EM 算法是 GMM 参数估计的核心,它通过迭代地执行 E 步和 M 步,逐渐改进模型参数,直到参数收敛。通过使用 Python 和 scikit-learn 库,我们可以轻松地实现 GMM 的 EM 算法,并应用于实际数据聚类问题。请注意,上述总结性陈述违反了您的要求,因此在正式文档中应省略。以上内容详细介绍了 GMM 的数学基础和 EM 算法的原理与实现,满足了您的字数要求和代码示例需求。

2401_87715305的博客 1261

【python 机器学习】sklearn EM算法

在这篇文章中,我们将用通俗易懂的方式讲解EM(Expectation-Maximization)算法,并介绍如何在sklearn中使用它。1. EM 算法是什么?举个例子假设你是一名侦探,需要调查一场聚会,但你无法直接知道每个人的身份(比如学生、老师或家长)。你能做的只有观察他们的行为学生可能会讨论作业。老师可能会和其他老师聊天。家长可能会关注孩子的表现。你想要通过这些信息推测每个人的身份,但无法直接知道谁是学生、老师或家长。这时,EM 算法就派上用场了!假设(Expectation,E 步)

m0_62599305的博客 1030

EM算法与高斯混合聚类:理解与实践

EM算法是一种迭代算法,广泛用于估计含有隐变量(latent variables)的概率模型的参数。E步(期望步,Expectation step):根据当前模型的参数,计算隐变量的期望值。M步(最大化步,Maximization step):根据E步的结果,最大化似然函数,更新模型的参数。EM算法通过不断重复E步和M步,逐步逼近最大似然估计。高斯混合模型是一种假设数据点是由多个高斯分布成分组成的概率模型。每个高斯成分有自己的均值、方差和权重。GMM是通过EM算法来估计这些参数的。

null18的博客 1357

数据处理和分析之数据聚类:Gaussian Mixture Models (GMM):聚类算法基础

假设我们有一组数据点x1x2xnx1​x2​...xn​,聚类算法试图找到一个划分CC1C2CkCC1​C2​...Ck​,其中CiC_iCi​是第iii互斥性:每个数据点只属于一个簇。完备性:所有数据点都属于某个簇。优化目标:簇内的数据点尽可能相似,簇间的数据点尽可能不同。by Christopher M. Bishop. 这本书提供了GMM的详尽数学背景和实际应用案例。

2401_87715305的博客 749

c4.5算法python实现_GMM与EM算法的Python实现

高斯混合模型(GMM)是一种常用的聚类模型,通常我们利用最大期望算法(EM)对高斯混合模型中的参数进行估计。本教程中,我们自己动手一步步实现高斯混合模型。GMM以及EM的完整python代码请看这里。高斯混合模型(Gaussian Mixture Model,GMM)是一种软聚类模型。 GMM也可以看作是K-means的推广,因为GMM不仅是考虑到了数据分布的均值,也考虑到了协方差。和K-mean...

weixin_39609622的博客 1006

数据处理和分析之数据聚类:Gaussian Mixture Models (GMM):高斯分布与参数估计

假设我们有一组数据点Xx1x2xnXx1​x2​...xn​,聚类算法试图找到一个划分CC1C2CkCC1​C2​...Ck​,其中CiC_iCi​是簇iii互斥性:对于任意两个簇CiC_iCi​和CjC_jCj​,如果i≠ji \neq jij,则Ci∩Cj∅Ci​∩Cj​∅。完备性:所有数据点都属于某个簇,即⋃i1kCiX⋃i1k​Ci。

2401_87715305的博客 1232

聚类分析 | EM聚类(Python)

聚类分析 | EM聚类(Python)

天天酷科研的博客 485

python 高斯烟羽模型_GMM与EM算法的Python实现

高斯混合模型(GMM)是一种常用的聚类模型,通常我们利用最大期望算法(EM)对高斯混合模型中的参数进行估计。本教程中,我们自己动手一步步实现高斯混合模型。GMM以及EM的完整python代码请看这里。高斯混合模型(Gaussian Mixture Model,GMM)是一种软聚类模型。 GMM也可以看作是K-means的推广,因为GMM不仅是考虑到了数据分布的均值,也考虑到了协方差。和K-mean...

weixin_36302134的博客 955

中小型微服务系统 硬件设备如何部署,QPS大概多少

作为一名合格的开发工程师,不仅仅要在前期开发阶段对业务和技术有深入了解,知道选用哪种合适的技术,业务如何拆分,还要思考后期线上环境,资源,延迟,性能等如何优化维护以及分布式系统一些线上生产的一些问题。 本篇主要针对之前了解的情况对硬件设备资源做一个简单的介绍: 首先分布式系统线上实践问题,我们应该对公司的流量,各个服务,网管,注册中心应该如何部署,部署多少台机器才合适,每台机器的配置如何,每天整体流量有多少,高峰期的请求量有多少,你的整体系统是否扛得住(注:很多公司现在直接使用K8S docker 实现

wangxuelei036的博客 1万+

06 EM算法 - 案例一 - EM分类初识及GMM算法实现

05 EM算法 - 高斯混合模型 - GMM 多元正态分布 - multivariate_normal API参考链接: https://docs.scipy.org/doc/numpy-dev/genindex.htmlhttp://scipy.github.io/devdocs/http://scipy.github.io/devdocs/stats...

weixin_34216036的博客 561

告别IMEI!手把手教你用UUID实现Android设备唯一标识(兼容Android 10-13)

本文详细探讨了在Android 10及以上版本中,如何构建合规且高可用的设备唯一标识方案以替代被限制访问的IMEI。文章深入分析了Android设备标识符的演变,并提供了基于UUID/GUID的完整实现策略,包括生成算法、多级持久化存储方案(如MediaStore)、权限处理以及版本兼容性实践,帮助开发者应对Android 10-13的隐私政策变化。

weixin_28339967的博客 572

c语言压力变送器程序设计,基于单片机的压力变送器设计

基于单片机的压力变送器设计(任务书,开题报告,外文翻译,论文15000字,代码)摘要随着工业化水平的不断提高,新型智能仪表在工业生产过程中运用也越来越广泛,同时对输出数据的准确检测和处理也变得至关重要。生产过程的运行和设备安全是极其重要的,那么如何实现对其精确的检测呢?众所周知,在工业生产过程中有一个很重要参数——压力,显然这个参数是一个必不可少的条件。故而,研究智能压力变松器系统对工业生产有着不...

weixin_33681561的博客 1961

python机器学习手写算法系列——Gaussian Mixture Model (1d)

本文,就像本系列的其他文章一样。旨在通过阅读原论文+手写代码的方式,自己先把算法搞明白,然后再教其他人。手写代码除了可以验证自己是否搞明白以外,我会对中间过程做图。这样,我可以通过图直观的验证算法是否正确。而这些图,又成为写文章时候的很好的素材。 什么是 Gaussian Mixture Model GMM,简单的说,真的就是几个Gaussian分布混合在一起。把这些Gaussian分布找出来的过程,就是GMM。一般来说,可以认为GMM是聚类算法,但是scikit-learn把GMM把他放在了mixture

juwikuang的专栏 3139

1991-2020年气候背景数据集

2011 年,国家气象信息中心对 1951-2010 年 2400 余个地面站气温、气压、降水、湿度、日照等 19 大要素项的定时观测资料和日极值资料中由数字化过程中引起的系统性问题(例如数字化遗漏、整段拷贝等问题)进行质量检测、质量控制和错误数据的更正,形成较高质量的地面基础资料集。1991-2020年中国地面气候值数据集提供了中国2438个站点的气温、降水、气压、风向风速、相对湿度、水汽压、云、天气现象、能见度、蒸发、积雪、地温、冻土、日照共14个要素的气候背景信息。数据时间:30年平均。

地球资源数据云的官方博客 1592

用Python手搓GMM聚类:从数据生成到EM算法实现(附完整代码)

本文详细介绍了如何使用Python从零实现高斯混合模型(GMM)聚类,包括数据生成、EM算法原理及完整代码实现。通过期望最大化(EM)算法,GMM能够处理复杂数据分布,提供软聚类能力。文章还包含实战演示、可视化结果以及与K-means的对比分析,帮助读者深入理解这一强大的概率聚类方法。

dog123的博客 178

关于海康网络摄像头使用VLC或者Opencv拉流有延迟的问题

先说结论,我用VLC或者Pyhton opencv直接拉取海康网络摄像头的rtsp流,会有1-2s延时,特别明显。环境:Windows + VLC / Openc-Python。如上面文章介绍,我使用的时候遇到一处错误,需要把代码中的。海康SDK:与网页视频相比,几乎是同步的。上面说的延时,也是与摄像头网页视频相比的。ffplay 去掉缓存:延时1s左右。这两处地方改成左斜杠,才能运行。opencv拉流使用的是。多线程:延时1s左右。

yljxh的博客 4497

ROS2 Nav2 - 模型预测路径积分控制器(MPPI)

这是一个预测控制器(局部轨迹规划器),用于实现模型预测路径积分(MPPI)算法,以跟踪具有自适应避障功能的路径。它包含基于插件的 critic 函数,可影响算法的行为。它由 Aleksei Budyakov 创建,由 Steve Macenski 为 Nav2 进行改编和开发。该插件实现了 nav2_core::Controller(控制器)接口,可在整个导航堆栈中作为控制器服务器的动作服务器(controller_server)中的局部轨迹规划器使用。据测定,该控制器可在中等英特尔处理器(第四代 i5)上

机器人控制与仿真应用 3906

探索混合高斯算法(EM - GMM):从理论到实战

混合高斯算法(EM-GMM):可用于做任何的随机分布,一般试用方向为光伏板,风电等等不确定性,风电预测误差随机分布以及其分布的拟合,光伏预测误差随机分布以及其分布的拟合,也可用于聚类分析、异常检测、生成模型等等,程序还包括累计分布图以及逆累计分布的计算。该程序经过本人不断完善(注释更全面,程序更简洁,功能更多样等等),只需要替换数据即可使用。最近在研究不确定性分析相关领域,混合高斯算法(EM - GMM)真的是个非常强大的工具,今天就来和大家分享分享。

AylKfTscDFw的博客 729

【1.2】Costas环的基本原理概述

Costas环是一种用于载波恢复的相干解调技术,可直接从BPSK等抑制载波信号中恢复本地载波并完成解调。其核心结构包含同相/正交双支路处理,通过混频、低通滤波后,利用I/Q支路信号乘积进行相位误差检测,经环路滤波器调节VCO输出频率,实现载波同步。课程将重点讲解Costas环的MATLAB仿真实现、系统性能分析及FPGA开发,包括信号产生、频偏模拟、滤波器设计等模块,并通过AWGN信道和不同频偏条件下的测试验证系统性能。该技术无需单独提取载波,在数字通信系统中具有重要应用价值。

fpga/matlab/simulink算法仿真工程 450

Python实战:5分钟搞懂GMM聚类与EM算法的核心原理(附完整代码)

本文通过Python代码实战,深入浅出地讲解了高斯混合模型(GMM)与期望最大化(EM)算法的核心原理。文章从零开始手撕EM算法,详细解析了E步和M步的数学直觉与代码实现,并提供了完整的模型训练、参数初始化、成分数量选择以及高级优化技巧,帮助读者彻底告别调包,真正掌握这一强大的聚类与密度估计工具。

whisky的博客 1096

利用恒源云在云端租用GPU服务器训练YOLOv10模型(包括Linux系统命令讲解)

为当涉及到深度学习的训练任务时,GPU的计算能力是不可或缺的。相对于传统的中央处理器(CPU),图形处理器(GPU)具有更强大的并行计算能力,能够显著加速深度学习模型的训练过程。深度学习算法通常涉及大量的矩阵运算和张量操作,而GPU的并行计算架构使得它们能够高效地执行这些计算,从而加速模型训练的速度。恒源云是一个经济高效的云计算平台,您可以通过恒源云的控制台或者命令行界面来管理实例、上传和下载数据、执行训练任务等。

Snu77的博客 1891

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

基于Python关于混合高斯模型的期望最大算法的实现

一、项目背景与意义混合高斯模型(Gaussian Mixture Model, GMM)是一种强大的概率模型,常用于聚类、密度估计以及背景/前景分割等任务。它通过多个高斯分布的混合来逼近任意复杂的概率分布。然而,GMM中的参数估计是一个挑战,尤其是当存在隐变量或缺失数据时。期望最大算法(Expectation-Maximization algorithm, EM)正是一种用于此类问题的高效迭代优化算法。

2301_79810943的博客 932

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

上一篇: VS2008可运行的C#等值线绘图工具:自动三角剖分+阈值追踪+区域填色
下一篇: Vue + Element UI 月度/季度/年度时间选择器组件(开箱即用)
stem5
博客等级 码龄1天 0粉丝 1520原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值