简介:面向计算机、电子信息工程、数学等专业的大学生,这份基于Matlab实现二维高斯分布抽样的资源,可作为课程设计、期末大作业或毕业设计的参考资料,帮助读者理解二维高斯分布的原理与抽样实现方法。压缩包内共9个文件,包含4个Matlab源码脚本(.m)用于抽样与测试,4张运行结果示意图(.png)展示抽样分布效果,以及1份Markdown说明文档讲解实现思路与使用要点,整体大小仅154KB,结构清晰,便于按需查阅。目前已有289人学习下载,口碑与实际参考价值可见一斑。资源不仅提供可直接运行的抽样代码,还配有说明文档和结果图,方便读者对照验证、理解算法流程,并在此基础上自行修改与扩展功能,适合具备一定Matlab基础、希望快速上手或深入掌握二维高斯分布抽样的学习者。
1. 二维高斯抽样:把mvnrnd从“黑盒”变成“工具箱”
二维高斯分布抽样在Matlab里常被简化成两步:调mvnrnd出样本,再scatter画散点。课堂上演示没问题,但一到课程设计或期末大作业,协方差矩阵怎么设定、样本点为何呈椭圆排列、抽样结果怎样验证,这些才是拉开分数的地方。资源包里的test01.m到test04.m四个脚本加上说明文档,覆盖了从基础抽样、手动矩阵分解到统计校验的完整链路。这篇按调试思路展开,先讲透二维高斯分布的理论与抽样原理,再逐段拆解源码,最后落到仿真应用和排错技巧。如果你正在做概率论相关课程设计,或者需要在粒子滤波、蒙特卡洛模拟里生成相关随机向量,下面的代码和参数表可以直接拿去做底子。
2. 二维高斯分布理论:密度函数、协方差矩阵与抽样原理
2.1 概率密度函数与马氏距离的几何含义
二维高斯分布的概率密度函数,在Matlab里可以写成一个完整函数:
function p = gauss2d_pdf(x, mu, Sigma) % x: 2x1 列向量(一个样本点) % mu: 2x1 均值向量 % Sigma: 2x2 协方差矩阵 d = length(mu); Z = (2*pi)^(d/2) * sqrt(det(Sigma)); mahal = (x - mu)' / Sigma * (x - mu); % 马氏距离平方 p = exp(-0.5 * mahal) / Z; end这里mahal就是我们常说的马氏距离平方,它是在协方差矩阵度量下的“去相关距离”。概率密度函数里真正决定样本分布形状的就是这个二次型项:等概率密度线在二维平面上构成椭圆,椭圆方向由Sigma的特征向量决定,长短轴比例由特征值决定。
具体看一个例子。对Sigma = [1 0.5; 0.5 1]做特征值分解:
[V, D] = eig([1 0.5; 0.5 1]); % V 的列向量约在 45 度和 135 度方向 % D = diag([1.5 0.5])特征向量[0.7071; 0.7071]对应特征值1.5,是椭圆长轴方向;另一个正交方向对应特征值0.5,是短轴方向。这意味着样本点在45度方向上更分散,在135度方向被压缩,整体呈现“右上-左下”拉伸的椭圆轮廓。如果Sigma是对角阵且对角线相等,比如单位矩阵,椭圆退化为圆,两个维度完全独立,此时二维抽样等价于两个独立的一维高斯抽样叠加。
这个理解在调试时非常关键:当你看到散点图的椭圆方向和自己预期不符,第一反应不应该是怀疑随机数,而是看特征向量方向有没有算对。
2.2 mvnrnd 与 Cholesky 分解:两条抽样路线的选型
Matlab统计工具箱里的mvnrnd,内部核心算法就是Cholesky分解。理解它的原理有双重价值:一是没有工具箱时能手动复现,二是当抽样结果出现NaN或协方差方向错乱时,你知道该往哪个环节排查。
手动Cholesky抽样代码:
N = 5000; mu = [2 -1]; Sigma = [2 0.8; 0.8 1.5]; L = chol(Sigma, 'lower'); % Sigma = L * L' Z = randn(2, N); % 2xN 标准正态样本 X = (L * Z + mu')'; % 映射到目标分布并转成 Nx2chol(Sigma, 'lower')返回下三角矩阵L,满足Sigma = L*L'。randn(2,N)生成两个独立标准正态维度,L*Z完成线性变换,使样本协方差从单位阵变换为L*L'=Sigma,最后加mu'平移椭圆中心。
两种抽样方式的对比如下:
| 对比维度 | mvnrnd | 手动Cholesky |
|---|---|---|
| 依赖Statistics工具箱 | 需要 | 不需要 |
| 代码可读性 | 高,一行调用 | 中,需理解矩阵分解 |
| 灵活性 | 接口固定 | 可插入旋转或缩放变换 |
| 重复高频抽样 | 每次调用重新分解 | 分解一次,后续仅矩阵乘法 |
| 报错信息 | 较完整 | 需自己检查chol是否失败 |
实际工程里,如果只抽一次样,mvnrnd是最省事的。但在粒子滤波每步要对数千个粒子做采样时,我会预先算好L,循环体里只执行randn和矩阵乘法,这一步能省掉每次重复分解的开销。
chol有个容易搞反的细节:默认返回上三角R,满足Sigma = R'*R,此时正确写法是X = (R' * Z + mu')'。混用上下三角是手动抽样最常见的错误,表现就是样本协方差与目标Sigma不匹配,椭圆方向完全错乱。
2.3 协方差矩阵合法性检查
mvnrnd和chol对Sigma有严格要求:对称且半正定。写进代码只需几行:
if ~isequal(Sigma, Sigma') error('Sigma 必须对称'); end if min(eig(Sigma)) < 0 error('Sigma 必须半正定'); end这一段检查能在早期拦掉大部分低级错误。实际报错集中在两类:一类是手写矩阵时忘了对称,比如把[1 0.3; 0.5 1]当成合法输入;另一类是数值计算中产生轻微不对称,需要先做Sigma = (Sigma + Sigma') / 2再传入抽样函数。
半正定检查还有一个隐藏坑:浮点误差可能让本应对称正定的矩阵产生微小的负特征值,比如-1e-16级别。这种数值噪声需要用后续的对角加载或特征值截断来处理,这是第4章的内容。
3. Matlab源码逐段拆解:从test01.m到test04.m
3.1 test01.m:mvnrnd基础抽样与1-sigma椭圆叠加
test01.m是入门脚本,用mvnrnd生成样本,再叠加一个马氏距离等于1的椭圆等值线:
% test01.m - 二维高斯基础抽样与可视化 clear; clc; close all; mu = [0 0]; Sigma = [1 0.5; 0.5 1]; N = 2000; X = mvnrnd(mu, Sigma, N); figure('Color', 'w', 'Position', [100 100 620 500]); scatter(X(:,1), X(:,2), 8, 'filled', 'MarkerFaceAlpha', 0.35); hold on; % 1-sigma椭圆:马氏距离等于1的等值线 [V, D] = eig(Sigma); theta = linspace(0, 2*pi, 200); ellipse = V * sqrt(D) * [cos(theta); sin(theta)]; plot(mu(1) + ellipse(1,:), mu(2) + ellipse(2,:), ... 'r-', 'LineWidth', 2); axis equal; grid on; xlabel('x_1'); ylabel('x_2'); title(sprintf('二维高斯抽样 N=%d', N));scatter的第三参数8是点大小,MarkerFaceAlpha控制填充透明度,防止重叠点完全遮住密度信息。eig分解后,V*sqrt(D)的作用是把单位圆上的点映射成目标协方差对应的椭圆;theta在0到2π之间取200个点,保证曲线平滑。
这里有一个答辩时经常被问到的点:1-sigma椭圆内部到底包含多少样本?二维高斯中,马氏距离平方服从自由度为2的卡方分布,所以:
P(D^2 <= 1) = 1 - exp(-0.5) ≈ 0.393也就是说红色椭圆内部大约只有39.3%的样本,不是一个“装满”的椭圆。若想包含95%的样本,需要把半径系数换成sqrt(chi2inv(0.95, 2))约等于2.448。能答出39.3%这个数字,说明你是真的理解二维高斯的概率几何,而不是只会调函数。
3.2 test02.m:Cholesky分解手动抽样与统计输出
test02.m在资源包里的角色是验证手动抽样与mvnrnd等价。完整脚本如下:
% test02.m - Cholesky分解手动抽样 clear; clc; close all; mu = [2 -1]; Sigma = [2 0.8; 0.8 1.5]; N = 5000; L = chol(Sigma, 'lower'); Z = randn(2, N); X = (L * Z + mu')'; figure('Color', 'w', 'Position', [100 100 620 500]); scatter(X(:,1), X(:,2), 6, 'filled', 'MarkerFaceAlpha', 0.35); hold on; [V, D] = eig(Sigma); theta = linspace(0, 2*pi, 200); ellipse = V * sqrt(D) * [cos(theta); sin(theta)]; plot(mu(1) + ellipse(1,:), mu(2) + ellipse(2,:), ... 'r-', 'LineWidth', 2); axis equal; grid on; xlabel('x_1'); ylabel('x_2'); title('Cholesky手动抽样 test02.m'); % 统计校验 fprintf('样本均值: [%.4f, %.4f]\n', mean(X, 1)); fprintf('样本协方差:\n'); disp(cov(X));mean(X,1)对列求均值,返回1x2向量;cov(X)计算样本协方差矩阵,默认除以N-1作无偏估计。N=5000时,样本协方差与真实Sigma的偏差大约在0.01量级,这是Monte Carlo抽样本身的随机波动,不是代码错误。
对比test01.m和test02.m的运行结果,散点形状和椭圆位置应当一致。如果方向对不上,优先检查chol是下三角还是上三角。
3.3 test03.m与test04.m:样本量对比与边界场景处理
test03.m处理的是样本量变化带来的可视化差异。我的实现逻辑是按数量级递增展示分布稳定性:
% test03.m - 样本量对分布呈现的影响 clear; clc; close all; mu = [0 0]; Sigma = [1 -0.6; -0.6 1]; figure('Color', 'w', 'Position', [50 50 1200 300]); Nset = [100 1000 8000 30000]; for i = 1:4 X = mvnrnd(mu, Sigma, Nset(i)); subplot(1, 4, i); scatter(X(:,1), X(:,2), 2, 'filled', 'MarkerFaceAlpha', 0.25); axis equal; grid on; xlim([-4 4]); ylim([-4 4]); title(sprintf('N=%d', Nset(i))); end从N=100到N=30000可以看到两个明显变化:N=100时椭圆长轴方向难以稳定辨识,样本协方差与真实Sigma的偏差可能超过20%;N=8000以上轮廓才趋于稳定。这个脚本在调试时很实用——当你修改抽样代码后要确认分布没有被破坏,直接跑一轮样本量对比即可。
test04.m对应边界场景,指接近退化的协方差矩阵:
Sigma_edge = [1 0.999; 0.999 1];当两个维度几乎完全相关时,最小特征值趋近于零,chol(Sigma_edge)在浮点误差下可能报错“Matrix must be positive definite”。实际处理时需要包一层try-catch:
try L = chol(Sigma_edge, 'lower'); fprintf('Cholesky分解成功\n'); catch ME fprintf('Cholesky分解失败: %s\n', ME.message); end| 脚本文件 | 核心功能 | 关键函数 |
|---|---|---|
| test01.m | mvnrnd基础抽样与1-sigma椭圆可视化 | mvnrnd, eig, scatter |
| test02.m | Cholesky手动抽样与统计校验 | chol, randn, cov |
| test03.m | 样本量对比实验 | mvnrnd, subplot |
| test04.m | 边界协方差矩阵异常处理 | chol, try-catch |
这个文件对照表可以帮助快速定位每个脚本在整条学习链路中的作用,也方便在说明文档里补充对应的运行结果图。
4. 抽样结果的统计验证与参数调优
4.1 蒙特卡洛校验:均值、协方差与卡方分位数对比
写抽样流程后的第一件事,是验证样本分布与理论目标一致。最基础的是对比样本均值和协方差:
mu_true = [0 0]; Sigma_true = [1 0.5; 0.5 1]; N = 10000; X = mvnrnd(mu_true, Sigma_true, N); mu_est = mean(X, 1); Sigma_est = cov(X); % 计算每个样本到中心的马氏距离平方 L = chol(Sigma_est, 'lower'); Y = (X - mu_est) / L'; % 白化后的样本 D2 = sum(Y.^2, 2);这里的关键在最后两步:(X - mu_est) / L'等价于每个样本向量左乘inv(L'),也就是inv(L)的转置效果,白化后每个样本在标准空间里应当是标准正态。D2理论服从自由度2的卡方分布。
进阶校验是用分位数对比:
theo_q = chi2inv([0.5 0.9 0.95], 2); emp_q = quantile(D2, [0.5 0.9 0.95]); disp([emp_q; theo_q]);如果抽样实现正确,emp_q应当接近[1.3863 4.6052 5.9915]。偏差超过10%,先怀疑Cholesky方向,再看Sigma是否被中途修改。
这个马氏距离校验比单纯看散点图可靠得多,因为它同时验证了均值和协方差两个层面的匹配程度,而且是二维联合验证,不是看两个边缘分布那么简单。
4.2 相关系数与协方差矩阵的参数换算
实际项目里经常先拿到相关系数,而不是协方差。比如已知相关系数ρ=0.6,两个维度标准差分别是2和1:
sigma1 = 2; sigma2 = 1; rho = 0.6; Sigma = [sigma1^2, rho*sigma1*sigma2; rho*sigma1*sigma2, sigma2^2]; % 结果: % Sigma = [4.0000 1.2000 % 1.2000 1.0000]反方向从样本估计相关系数:
R = corrcoef(X); % R(1,2) 即样本相关系数估计| 输入参数 | 含义 | 取值范围 |
|---|---|---|
| mu(1), mu(2) | 两维度均值 | 全体实数 |
| sigma1, sigma2 | 两维度标准差 | 大于0 |
| rho | 相关系数 | (-1, 1) |
注意ρ必须严格在(-1,1)之间。ρ等于±1意味着两个维度线性相关,协方差矩阵奇异,二维高斯分布退化到一条直线上,此时chol会失败。课程设计里如果遇到这种情况,本质上已经不是二维问题了。
4.3 接近退化协方差矩阵的数值稳定化
当ρ接近1,比如0.999,浮点误差很容易让最小特征值变成负数,最终导致chol报错。三个常用处理手段:
% 方法1: 对角加载(推荐优先尝试) Sigma_reg = Sigma + 1e-6 * eye(2); % 方法2: 特征值截断 [V, D] = eig(Sigma); D = max(D, 1e-6); Sigma_reg = V * D * V'; % 方法3: 人工限制相关系数 rho = min(rho, 0.99);提示:对角加载量级从1e-6起步。加载值过大,比如1e-2,会把样本协方差明显“撑肥”,导致方差偏大。
方法2能保留原始特征向量方向,只修正特征值,对分布形状影响最小。方法3最粗暴,适合对相关系数精度要求不高的场景。
5. 二维高斯抽样在仿真中的落地:粒子滤波、蒙特卡洛与验收技巧
5.1 粒子滤波中的批量提议分布采样
二维高斯抽样在粒子滤波里的典型用法是给粒子状态加相关噪声扰动。每步预测就是对全量粒子做一次批量高斯抽样:
Np = 2000; % 粒子数 Q = [0.02 0.005; 0.005 0.015]; Lq = chol(Q, 'lower'); % 每个循环步对全部粒子做一步预测 particles = particles + (Lq * randn(2, Np))';Lq预先只算一次,循环体里只有一次矩阵乘法和加法。2000个粒子的预测步在我的机器上大概零点几毫秒,这个开销在实时仿真里可以接受。
优化点时变噪声协方差Q。如果Q全程固定,滤波后期粒子多样性会持续下降,导致样本枯竭。常见做法是在重采样之后对Q的对角元素做指数衰减,或者按有效粒子数动态调整缩放系数。二维高斯抽样本身是工具,真正决定滤波精度的是Q如何随状态变化。
5.2 用QQ图快速验收抽样质量
最后分享一个收尾验收技巧:用qqplot快速判断边缘分布是否正常。对每个维度单独画:
figure('Color', 'w', 'Position', [100 100 900 400]); for d = 1:2 subplot(1, 2, d); qqplot(X(:, d)); title(sprintf('维度 %d 的QQ图', d)); grid on; end如果样本确实来自高斯分布,散点应近似贴合图中的参考直线。观察到S形弯曲,说明分布偏斜或尾重,大概率不是高斯。直线斜率偏离1,说明方差缩放有误,回去检查Sigma对角线是否被意外改动。联合层面的验证再回到第4章的卡方分位数对比,两个维度独立验一次、联合验一次,这套组合基本能把抽样实现里的常见错误全部覆盖。
本文还有配套的精品资源,点击获取