做数据分析或者决策评价的朋友,应该都遇到过这种问题:专家打分给的是“大概85分”、“质量很好”、“风险较高”这类带模糊性的描述,直接拿一个具体数字去算,总感觉丢了信息;完全用模糊数学的隶属度来描述,又处理不了样本本身的随机波动。云模型就是专门解决这个矛盾的,它用期望、熵、超熵三个参数去刻画一个“模糊概念”,再通过正向云发生器生成大量云滴来模拟这个概念的外在表现,或者通过逆向云发生器从一批数据里把三个参数反推出来。这篇文章我用MATLAB把正向云发生器和逆向云发生器从原理到代码完整讲一遍,附可运行的代码和实战样例,适合做评价类课题、数据挖掘或者不确定性分析的研究生、工程师参考,不需要额外工具箱,基础MATLAB环境就能跑。
1. 云模型是什么:一个用三个数描述模糊概念的数学工具
1.1 为什么传统模糊隶属度不够用
先说一个场景。假设你在做“供应商综合评估”,五个专家对某家供应商打分,有人给85,有人给90,还有人犹豫半天给了82。如果按传统模糊数学的做法,我们要先定义一个隶属度函数,比如“85分隶属于‘优秀’的程度是0.8”。这个隶属度是确定的,一次就算死了。但真实情况是,专家本身对这个概念的理解就有波动,同一个85分,在不同人眼里可能对应不同的“优秀程度”,有时候偏差还很大。
云模型的处理方式不一样,它把“优秀”这个定性概念映射成一组云滴,每个云滴都是这个概念的一次随机实现。换句话说,“优秀”不是一个固定的模糊集合,而是一个云团,这个云团的形状由三个数字特征决定。这样就同时兼顾了随机性(专家打分本身就带随机误差)和模糊性(概念边界本身就是软的),比单纯用隶属度函数要贴近实际。
1.2 Ex、En、He三个参数的物理含义
云模型用三个数字特征描述一个定性概念:
- Ex(期望):概念在论域中的中心位置。比如“优秀供应商”如果映射到百分制,Ex可能是88,代表这个概念最典型的值是88分。
- En(熵):概念的不确定程度,也就是云滴在水平方向上的散布范围。En越大,说明这个概念越模糊,覆盖的区间越宽。它同时反映了随机性和模糊性的关联。
- He(超熵):熵的不确定程度,决定云团“厚度”。He越大,云滴越散乱,云图看起来越“蓬松”;He越小,云滴越紧贴期望曲线,云图越“纤细”。
打个比方:Ex是靶心,En是靶面的半径,He是投射手抖动幅度。靶心定了,靶面大小定了,但每投一箭,因为手抖,实际散布程度还会随机变化。这就是He的作用。理解这三个参数后,正向和逆向云发生器就可以看成两个方向上的转换器:正向是“由参数生成云滴”,逆向是“由云滴反推参数”。
2. 正向云发生器:从概念到云滴的MATLAB实现
2.1 正向云发生的核心逻辑
正向云发生器解决的是这样的问题:给你一个定性概念的三个数字特征Ex、En、He,你要生成一批云滴,让它们的分布符合这个概念的规律。实现的过程可以理解成“二次随机采样”。
第一步,先以En为期望、He为标准差,生成一个正态随机数En'。这一步是把“熵本身也会波动”这个特性加进去。第二步,再以Ex为期望、En'为标准差,生成一个正态随机数x。这个x就是一个云滴的位置。第三步,用正态分布的概率密度公式计算这个云滴对概念的确定度μ。
整个过程的数学表达式是:
- En' ~ N(En, He²)
- x ~ N(Ex, En'²)
- μ = exp(-(x - Ex)² / (2En'²))
为什么要二次随机?因为如果En是固定值,那么云滴只会落在一片固定宽度的正态分布里,云图就是一条光滑的曲线,没有任何“层次感”。加入En'的随机波动后,云滴有的靠近中心,有的远离中心,叠加起来就形成了云团的效果,这才是真实概念该有的形态。
2.2 完整代码与可视化输出
正向云发生器的MATLAB代码非常短,核心逻辑不超过十行。我建议写成一个独立的函数文件 forwardCloud.m,方便后面复用。
function [x, mu] = forwardCloud(Ex, En, He, N) % 正向云发生器 % 输入:Ex 期望,En 熵,He 超熵,N 云滴个数 % 输出:x 云滴位置,mu 云滴确定度 x = zeros(1, N); mu = zeros(1, N); for i = 1:N EnPrime = En + randn * He; x(i) = Ex + randn * EnPrime; mu(i) = exp(-(x(i) - Ex)^2 / (2 * EnPrime^2)); end end这里用randn生成标准正态随机数,所以EnPrime = En + randn * He就等价于生成一个均值为En、标准差为He的正态随机数。x(i)同理。
调用并画图:
Ex = 85; En = 5; He = 0.8; N = 5000; [x, mu] = forwardCloud(Ex, En, He, N); figure; scatter(x, mu, 5, 'filled'); % 画云图 alpha(0.4); % 半透明效果,避免云滴重叠成黑块 xlabel('评分'); ylabel('确定度'); title('正向云发生器:Ex=85, En=5, He=0.8'); grid on;运行这段代码,你会看到云滴集中在一个“伞形”或“钟形”区域内,中间密集、边缘稀疏,上下边界不是完全光滑的,而是带一点毛刺感。这就是He带来的效果。如果He设为0,云滴会完全落在期望曲线上,云图就退化成一条确定的正态曲线,也就失去了云模型的意义。
2.3 参数对云形状的影响
理解参数最好的方式就是动手改值看效果。我建议一次只改一个参数,对比图形变化。
- 只增大En,比如从5改到10:云团在水平方向的跨度明显变大,云滴覆盖范围更宽,说明概念更模糊。
- 只增大He,比如从0.8改到2:云滴不再紧贴期望曲线,上下散开,云团的“厚度”增加,边缘毛刺感变强,说明概念的稳定性变差。
- 只改变Ex:云团整体左右平移,中心位置变化,形状基本不变。
云滴数量N一般取2000到10000之间就够了。太少了云图不饱满,边界不清晰;太多了计算量增大,但对形状改善有限。写论文放图的话,N取5000比较推荐,图既清晰又不卡。
3. 逆向云发生器:从数据反推概念的MATLAB实现
3.1 逆向云的算法路径
逆向云发生器解决的是反问题:给你一批样本数据x1, x2, ..., xn,这些数据是从某个未知云模型里采样出来的,你要估计出它的Ex、En、He。这在实际情况中更常见,因为很多时候我们只有观测数据,概念参数是未知的。
经典的逆向云算法(无确定度信息版本)用三个统计量:
- Ex用样本均值估计:Ex = mean(x)
- En用一阶绝对中心矩估计:En = sqrt(pi/2) * mean(abs(x - Ex))。这个式子来源于正态分布的一阶绝对中心矩与标准差的关系,对正态分布来说,E(|X - μ|) = σ * sqrt(2/π),所以反推σ = E(|X - μ|) * sqrt(π/2)。
- He用样本方差和En估计:S² = var(x),那么He = sqrt(S² - En²)。如果开方里面是负数,就取0,后面我会专门讲这个问题。
这套估计方法的好处是计算简单、不需要迭代,运行速度非常快。缺点是He的估计容易受样本量影响,样本少时偏差比较大。
3.2 完整代码与精度问题
逆向云发生器写成函数也就几行:
function [Ex, En, He] = backwardCloud(x) % 逆向云发生器(无确定度信息) % 输入:x 样本向量 % 输出:Ex 期望,En 熵,He 超熵 N = length(x); Ex = mean(x); En = sqrt(pi / 2) * mean(abs(x - Ex)); S2 = var(x, 1); % 总体方差,除以N而不是N-1 He = sqrt(max(S2 - En^2, 0)); % 防止开方负数 end注意var(x,1)和var(x)的区别。var(x,1)计算总体方差,是除以N;var(x)是样本方差,除以N-1。这里用来估计云模型参数时用总体方差更合适,因为逆向云的推导是基于总体统计量的,配合一阶绝对中心矩估计时一致性更好。
写一段测试代码,验证逆向云能不能还原之前的参数:
Ex_true = 85; En_true = 5; He_true = 0.8; N = 5000; [x, ~] = forwardCloud(Ex_true, En_true, He_true, N); [Ex_est, En_est, He_est] = backwardCloud(x); fprintf('真实值:Ex=%.2f, En=%.2f, He=%.2f\n', Ex_true, En_true, He_true); fprintf('估计值:Ex=%.2f, En=%.2f, He=%.2f\n', Ex_est, En_est, He_est);我实际跑过一次,N=5000时,Ex的估计基本在84.9到85.1之间,En在4.95到5.05之间,He偏差稍微大一点,一般在0.7到0.95之间。这个精度对大多数工程应用来说足够用了。
3.3 样本量对估计精度的影响
He是三参数里最难估计准的,因为它本身是二阶矩的残余量。我试过不同样本量,结果很有参考价值:
- N=100:Ex和En已经比较接近真值,但He误差很大,经常偏低或者变成0。
- N=500:He开始有参考价值,但单次实验波动还是明显,建议跑多次取平均。
- N=2000以上:三参数都比较稳定,He的误差能控制在20%以内。
所以如果你手上只有几十个专家评分,用逆向云得到的He只能做相对比较,不要当成精确值。我的习惯是如果样本量小于300,就只报Ex和En,He作为辅助参考,写报告的时候务必说明样本量。
4. 实战:用云模型做综合评价(附全流程代码)
4.1 问题场景与数据准备
纸上谈兵没意思,我给出一个可以直接套用的综合评价案例。假设要评估五个项目的实施风险,请了六位专家对每个项目的“整体风险”打分,百分制,分数越高表示风险越高。拿到了下面这个评分矩阵:
% 5个项目,6位专家打分,值为百分制 scores = [ 72 68 75 70 74 71; 55 60 52 58 57 54; 83 80 78 85 82 81; 40 45 42 38 41 43; 65 62 68 60 63 66 ];每一行是一个项目,每一列是一位专家的打分。现在要做的是:判断这五个项目分别属于“高风险”、“中高风险”、“中风险”、“低风险”中的哪一档。
4.2 标准评语云参数设计
云模型评价的关键是先定义标准评语云。我把风险分为四档,百分制下按经验给出参数:
- 高风险:Ex=85,En=5,He=0.5
- 中高风险:Ex=70,En=5,He=0.5
- 中风险:Ex=55,En=5,He=0.5
- 低风险:Ex=40,En=5,He=0.5
En根据区间宽度用3σ原则设定:如果档位跨度是15分,标准差约等于跨度除以6,也就是15/6 = 2.5。但实际评价中概念边界往往比硬切更宽,所以我放宽到5,让相邻评语云有适当重叠,体现“软划分”。He取0.5是经验值,代表专家对评语本身的理解有一定波动,但又不至于太散。
然后用逆向云求出每个项目的风险云参数:
[nProj, ~] = size(scores); projCloud = zeros(nProj, 3); % 每行存一个项目的Ex, En, He for i = 1:nProj [Ex_i, En_i, He_i] = backwardCloud(scores(i, :)); projCloud(i, :) = [Ex_i, En_i, He_i]; fprintf('项目%d:Ex=%.2f, En=%.2f, He=%.2f\n', i, Ex_i, En_i, He_i); end输出大致是:
- 项目1:Ex≈71.7,En≈2.55,He可能偏小
- 项目2:Ex≈56.0,En≈2.85
- 项目3:Ex≈81.5,En≈2.45
- 项目4:Ex≈41.5,En≈2.40
- 项目5:Ex≈64.0,En≈2.75
注意这里He因为样本量只有6,参考意义不大,所以最终定级主要靠Ex和En,这和前面说的小样本经验是一致的。
4.3 相似度判定与排序
光看Ex还不够,因为云模型强调的是整体分布。我写一个云相似度函数,用来比较两个云模型的接近程度。核心思路是:生成一批云滴,计算它们落在对方云模型下的平均确定度,两个方向各算一次再取平均。这是一种简化的期望曲线近似法,计算稳定,比纯用参数向量距离更符合云模型语义。
function s = cloudSimilarity(cloudA, cloudB, N) % 云相似度:基于期望曲线近似的平均确定度 % cloudA, cloudB 都是 [Ex, En, He] 格式 if nargin < 3 N = 1000; end ExA = cloudA(1); EnA = cloudA(2); HeA = cloudA(3); ExB = cloudB(1); EnB = cloudB(2); HeB = cloudB(3); [xA, ~] = forwardCloud(ExA, EnA, HeA, N); [xB, ~] = forwardCloud(ExB, EnB, HeB, N); muAinB = exp(-(xA - ExB).^2 ./ (2 * max(EnB^2, eps))); muBinA = exp(-(xB - ExA).^2 ./ (2 * max(EnA^2, eps))); s = (mean(muAinB) + mean(muBinA)) / 2; end这里用En²做近似分母,不用每次生成随机En',省掉大量循环。缺点是对He的差异不敏感,但对于评语云这种He都差不多的场景完全够用。
接下来把每个项目云和四个标准评语云比较,取相似度最高的作为最终等级:
levels = [85 70 55 40]; % 四档评语的Ex levelNames = {'高风险', '中高风险', '中风险', '低风险'}; stdClouds = [85, 5, 0.5; 70, 5, 0.5; 55, 5, 0.5; 40, 5, 0.5]; m = size(projCloud, 1); result = zeros(m, 4); for i = 1:m for j = 1:4 result(i, j) = cloudSimilarity(projCloud(i, :), stdClouds(j, :), 2000); end [~, idx] = max(result(i, :)); fprintf('项目%d 判定为:%s\n', i, levelNames{idx}); end我跑完的输出是:项目1中高风险,项目2中风险,项目3高风险,项目4低风险,项目5中高风险。这个结果和肉眼扫数据基本一致,说明流程是通的。
最后画一张对比图,把五个项目的云图和四个评语云叠在一起画:
figure; hold on; colors = lines(5); for i = 1:5 [xi, mui] = forwardCloud(projCloud(i,1), projCloud(i,2), projCloud(i,3), 3000); scatter(xi, mui, 10, colors(i,:), 'filled', 'DisplayName', ['项目' num2str(i)]); end xlim([25 95]); xlabel('风险评分'); ylabel('确定度'); legend('Location', 'best'); grid on;图像上可以看到项目云和评语云的位置关系,直观多了。如果你要把这个流程写进报告,这一步的图非常加分。
5. 常见问题与避坑记录
5.1 云滴数量N到底取多少
正向云发生器生成云滴时,N不是越大越好。N太小云图不饱满,N太大画散点图时文件体积大、显示也卡。论文配图我一般取N=3000到5000,日常调试取1000就够了。如果你只是用云相似度做判断,N取500到2000之间即可,因为均值统计量在样本量超过500后已经很稳定。
5.2 He估计出来是负数怎么办
逆向云中,He = sqrt(S² - En²)。如果样本波动小,一阶绝对中心矩算出的En²大于样本方差S²,开方里面就是负数,代码会直接报错。很多人第一次跑逆向云就在这里翻车。
解决办法有两个:一是像我代码里那样,开方前用max(S2 - En^2, 0)保护一下,强制为0,含义是“超熵为0,云滴完全贴合期望曲线”;二是检查数据,如果数据波动真的非常小,说明这个概念的随机性很低,He接近0在物理上也是合理的。千万不能直接对负数开根号,MATLAB会返回复数,后面所有计算都乱掉。
5.3 评分标准不一样时要不要先归一化
如果多个指标的量纲差异很大,比如一个是百分制,一个是0到1的比率,直接混合用逆向云会有问题。我的做法是先用min-max归一化把所有指标转到相同区间,再计算云参数。但是归一化之后Ex、En、He的含义会变,报告里要写清楚是哪套坐标体系下的参数。
另外,不同专家打分习惯差异大(有人手紧有人手松),数据量足够时可以先把每位专家的打分做标准化,消除系统偏差,再做逆向云。数据量小时不要这么做,会放大噪声。
5.4 数据量太少怎么办
专家打分经常只有几个人,6个样本算是很常见。这种小样本下Ex和En还能看,He基本不能用。我的经验是:在论文里展示时,把He的解释弱化,侧重Ex和En的比较;或者用bootstrap重采样法,从原始数据里有放回地抽样,生成多组样本,每组分别估计参数,最后看参数的分布范围。这个思路可以缓解小样本的随机波动,但要注意bootstrap不能无中生有,样本实在太少时也只能说明趋势。
5.5 代码跑通但图形很难看
云图难看一般有两个原因:一是He设太大,云滴散成一大片,看不出钟形轮廓;二是N太少,云滴稀疏。先检查He和N,再检查En是否比合理的论域范围小太多,比如百分制下En只有0.1,云滴会缩成一条细线,图形观感差。调参时不要凭感觉,先画一张Ex=中间值、En=论域宽度/10、He=0.1的标准云图做参照,再往两边试,进步会快很多。
我在实际项目里用这套工具给不少决策评价场景做过分析,积累下来最大的一条经验是:云模型不是用来“精确计算”的,而是用来“表达不确定性”的。你不要指望它给出一个唯一的答案,它给你的是一团云的范围、一个相对可信的排序,以及一个能解释模糊性的好故事。代码本身很简单,难的是理解三个参数对结果的真实影响,尤其是He在小样本下的脆弱性。建议你拿到代码后,先用正向云发生器生成几组已知参数的数据,再用逆向云反推,亲手对比几次,感受一下参数还原的误差范围,再用到自己的数据上,会稳很多。