做效率评价的人,遇到“非期望超效率SBM模型”这个词通常不会陌生。它常见于能源效率、碳排放绩效、银行风险产出这类场景:你既要算产出效率,又不能只看“好”产出,还要把污染这类“坏”产出放进模型里;同时你还会遇到一个麻烦——传统DEA给出的一堆满分DMU没法区分排名。非期望超效率SBM就是为了同时解决这两件事。
这篇文章我直接拆到我自己的Matlab实现层面:模型怎么从论文公式一步步变成可运行的linprog代码,松弛变量怎么解读,超效率得分为什么有的会出现Inf,以及我踩过的一些坑。内容适合正在写论文、做课题、或者用效率模型做实证分析的人,尤其是刚接触SBM、还没摆脱“公式看得懂但代码写不出”状态的那一类。
1. 先把三层逻辑搞清楚:为什么是SBM、为什么非期望、为什么超效率
1.1 传统DEA的短板:径向假设和松弛量被忽略
传统DEA模型,比如CCR和BBC,都属于径向模型。它们的基本逻辑是:所有投入按同一比例压缩,或所有产出按同一比例扩张。这个“同一比例”看起来很优雅,实际用起来却很吃亏。比如一个决策单元有两个投入,一个已经压到底了,另一个还有明显冗余,径向模型给出的效率值很可能已经等于1,但实际上冗余一直存在,只是它没有体现在最终得分里。
这就像两个人考试,一个总分一样,但一个各科均衡,一个一科满分一科不及格,传统DEA只看到总分。效率评价里也会出现“假性有效”的情况。SBM模型,也就是基于松弛变量的测度模型,就是为了解决这个问题出现的。它把投入的冗余量和产出的不足量直接放进目标函数,让无效的部分“现形”。
1.2 非期望产出为什么不能当普通产出用
效率评价不是所有产出都越多越好。发电厂的发电量是好产出,二氧化碳排放是坏产出;银行的贷款收益是好产出,不良贷款是坏产出。如果简单把“坏产出”也塞进普通产出那一组,模型会鼓励DMU去增大坏产出,这显然违背效率评价的本意。
处理非期望产出的常见粗暴做法是把它当作投入处理。这样做模型上很容易实现,但逻辑上很别扭:投入是可以自由缩减的,而污染物产出往往和好产出绑在一起,不完全是“少用就少排放”。更合理的建模方法,是把非期望产出单独放在一组,给它的松弛变量单独定义方向。SBM在处理这种多方向、带负向指标的问题上非常合适,因为它是非径向模型,天然可以给投入、好产出、坏产出三组变量各自不同的改进方向。
1.3 超效率解决的是“满分扎堆”问题
普通SBM的得分上限是1。实际做效率评价时,尤其是样本量不大、投入产出指标又比较宽的情况下,经常出现十几个DMU同时得1分的情况。这时候没法排序,也没法解释为什么这些DMU之间没有差异。
超效率模型的核心思想是:评价某个有效DMU时,先把这个DMU自己从参考集中剔除,然后再看它距离剩余DMU构成的前沿还有多少“超额能力”。这样一来,原本得1分的DMU可以算出1.2、1.5甚至更高的分数,满分扎堆的问题就解决了。带非期望产出的超效率SBM,就是把非期望产出处理和这套剔除自身的评价逻辑合到一个模型里。
2. 模型公式拆解:论文里的公式怎么变成可计算的线性规划
2.1 标准SBM:先理解分母上的“惩罚”
先看不带非期望产出的标准SBM目标函数,它长这样:
$$ \rho = \min \frac{ 1 - \frac{1}{m}\sum_{i=1}^{m}\frac{s_i^-}{x_{io}} }{ 1 + \frac{1}{s}\sum_{r=1}^{s}\frac{s_r^+}{y_{ro}} } $$
其中 $s_i^-$ 是投入松弛量,代表投入还可以压缩多少;$s_r^+$ 是产出松弛量,代表产出还差多少。分子用投入松弛量“扣分”,分母用产出松弛量“惩罚”,DMU的效率被压到(0,1]之间。只有当所有松弛量都为0,也就是既没有投入冗余也没有产出不足时,得分才能等于1。
这个分数形式如果直接丢给Matlab的非线性求解器,慢且不稳定。常规做法是引入一个辅助变量t,做Charnes-Cooper变换,把分数规划转成线性规划。
2.2 引入非期望产出的目标函数
假设有m个投入、s1个好产出、s2个坏产出,写带非期望产出的SBM目标函数:
$$ \rho = \min \frac{ 1 - \frac{1}{m}\sum_{i=1}^{m}\frac{s_i^-}{x_{io}} }{ 1 + \frac{1}{s_1+s_2}\left( \sum_{r=1}^{s_1}\frac{s_r^+}{y_{ro}} + \sum_{k=1}^{s_2}\frac{s_k^{z-}}{z_{ko}} \right) } $$
$s_k^{z-}$ 是非期望产出的松弛量,注意它前面的符号处理:坏产出的“冗余”实际上就是排放量偏高,所以它和好产出的“不足”一样,都放进分母作为惩罚。目标还是让得分尽量大,也就是说我们希望好产出不足量小、坏产出冗余量也小。
约束条件里,投入和好产出的约束与标准SBM一致,坏产出约束写成:
$$ \sum_{j=1}^{n} \lambda_j z_{kj} + s_k^{z-} = z_{ko} $$
这个式子的意思是,被评价DMU的坏产出水平,等于参考组合的坏产出水平加上可削减的松弛量。松弛越大,说明坏产出越可以削减。
2.3 超效率版本的约束结构
超效率SBM的模型结构和普通SBM有一个关键区别:约束中的参考集不再包含自身。
带非期望产出的超效率SBM目标函数可以写成:
$$ \delta = \min \frac{ 1 + \frac{1}{m}\sum_{i=1}^{m}\frac{s_i^+}{x_{io}} }{ 1 - \frac{1}{s_1+s_2}\left( \sum_{r=1}^{s_1}\frac{s_r^+}{y_{ro}} + \sum_{k=1}^{s_2}\frac{s_k^{z-}}{z_{ko}} \right) } $$
约束条件为:
$$ \sum_{j \neq o} \lambda_j x_{ij} \le x_{io} + s_i^+ $$
$$ \sum_{j \neq o} \lambda_j y_{rj} \ge y_{ro} - s_r^+ $$
$$ \sum_{j \neq o} \lambda_j z_{kj} \le z_{ko} - s_k^{z-} $$
这里的 $s_i^+$ 表示投入相对参考前沿还可以“放大”多少,$s_r^+$ 表示好产出相对参考前沿还可以“增加”多少,$s_k^{z-}$ 表示坏产出相对参考前沿还可以“削减”多少。有效DMU的超效率得分就会大于1,得分越高,说明这个DMU即使把自己从前沿里剔除出去,仍然明显优于其他DMU。
2.4 线性化:把分母挪成一条等式
分数规划直接解很麻烦,Charnes-Cooper变换在这里起到的作用是:令 $t = 1 / 分母$,然后把 $\lambda_j$ 换成 $\Lambda_j = t\lambda_j$,把松弛量换成 $S_i^- = ts_i^-$ 这样的对应变量。替换之后,目标函数就变成线性形式,约束条件里也不再出现分数。
线性化后的普通SBM多了一条关键的归一化约束:
$$ t + \frac{1}{s_1+s_2}\left( \sum_{r=1}^{s_1}\frac{S_r^+}{y_{ro}} + \sum_{k=1}^{s_2}\frac{S_k^{z-}}{z_{ko}} \right) = 1 $$
超效率SBM线性化后归一化约束变成:
$$ t - \frac{1}{s_1+s_2}\left( \sum_{r=1}^{s_1}\frac{S_r^+}{y_{ro}} + \sum_{k=1}^{s_2}\frac{S_k^{z-}}{z_{ko}} \right) = 1 $$
这正是两类模型在Matlab实现中差异最核心的地方。做代码的时候,如果这套归一化约束写错,得分一定会莫名其妙,后面所有解读都失去意义。
3. Matlab实战:从零写一个可复用的非期望超效率SBM函数
3.1 数据结构约定与整体思路
我约定输入数据格式如下:
- X:m行n列,每个DMU对应一列,m个投入指标
- Y:s1行n列,每个DMU对应一列,s1个好产出指标
- B:s2行n列,每个DMU对应一列,s2个坏产出指标
我不建议把坏产出直接写进Y矩阵,哪怕代码上多传一个矩阵也就多几行参数,但模型语义清晰很多,后续改模型方向时也不容易混淆。
整体求解思路是:先对每个DMU跑一次普通SBM。如果得分小于1,说明无效,不需要再算超效率,最终得分就用普通SBM得分;如果得分等于1,说明有效,再用剔除自身的超效率模型继续评估。这个流程是文献中比较通行的做法,也符合实际需求。
3.2 普通SBM线性规划的Matlab实现
我直接用linprog,原因是Matlab内置优化工具箱就有,不需要额外依赖。变量排列顺序固定为:
1:n Lambda n+1:n+m S_minus(投入松弛) n+m+1:n+m+s1 S_plus(好产出松弛) n+m+s1+1:n+m+s1+s2 S_b(坏产出松弛) 最后一个变量 t函数代码如下:
function [rho, detail] = sbm_nd(X, Y, B, j, vrs) [m, n] = size(X); [s1, ~] = size(Y); s2 = size(B, 1); x0 = X(:, j); y0 = Y(:, j); b0 = B(:, j); totalVar = n + m + s1 + s2 + 1; Aeq = []; beq = []; % 归一化约束:t + 加权松弛 = 1 row = zeros(1, totalVar); row(n + m + (1:s1)) = 1 ./ ((s1 + s2) .* y0'); row(n + m + s1 + (1:s2)) = 1 ./ ((s1 + s2) .* b0'); row(end) = 1; Aeq = [Aeq; row]; beq = [beq; 1]; % 投入约束:X*Lambda + S_minus - t*x0 = 0 for i = 1:m row = zeros(1, totalVar); row(1:n) = X(i, :); row(n + i) = 1; row(end) = -x0(i); Aeq = [Aeq; row]; beq = [beq; 0]; end % 好产出约束:Y*Lambda - S_plus - t*y0 = 0 for r = 1:s1 row = zeros(1, totalVar); row(1:n) = Y(r, :); row(n + m + r) = -1; row(end) = -y0(r); Aeq = [Aeq; row]; beq = [beq; 0]; end % 坏产出约束:B*Lambda + S_b - t*b0 = 0 for k = 1:s2 row = zeros(1, totalVar); row(1:n) = B(k, :); row(n + m + s1 + k) = 1; row(end) = -b0(k); Aeq = [Aeq; row]; beq = [beq; 0]; end % VRS 规模报酬可变的约束 if vrs == 1 row = zeros(1, totalVar); row(1:n) = 1; row(end) = -1; Aeq = [Aeq; row]; beq = [beq; 0]; end f = [zeros(n, 1); -1 ./ (m .* x0); zeros(s1 + s2, 1); 1]; lb = zeros(totalVar, 1); options = optimoptions('linprog', 'Algorithm', 'dual-simplex', 'Display', 'off'); [sol, rho, exitflag] = linprog(f, [], [], Aeq, beq, lb, [], [], options); if exitflag < 0 rho = NaN; end detail.lambda = sol(1:n); detail.s_minus = sol(n + (1:m)); detail.s_plus = sol(n + m + (1:s1)); detail.s_b = sol(n + m + s1 + (1:s2)); detail.t = sol(end); end3.3 超效率SBM的Matlab实现
超效率函数的关键变化在于:参考集剔除自身,坏产出方向保持不变,目标函数从“压缩”变成“扩张”。变量顺序为:
1:n Lambda n+1:n+m S_i(投入可以继续扩张的松弛) n+m+1:n+m+s1 S_r(好产出可以继续增加的松弛) n+m+s1+1:n+m+s1+s2 S_b(坏产出可以继续削减的松弛) 最后一个变量 t代码如下:
function [score, detail] = super_sbm_nd(X, Y, B, j, vrs) [m, n] = size(X); [s1, ~] = size(Y); s2 = size(B, 1); x0 = X(:, j); y0 = Y(:, j); b0 = B(:, j); totalVar = n + m + s1 + s2 + 1; A = []; bvec = []; Aeq = []; beq = []; % 归一化约束:t - 加权松弛 = 1 row = zeros(1, totalVar); row(n + m + (1:s1)) = -1 ./ ((s1 + s2) .* y0'); row(n + m + s1 + (1:s2)) = -1 ./ ((s1 + s2) .* b0'); row(end) = 1; Aeq = [Aeq; row]; beq = [beq; 1]; % 投入不等式:X*Lambda - S_i - t*x0 <= 0 for i = 1:m row = zeros(1, totalVar); row(1:n) = X(i, :); row(n + i) = -1; row(end) = -x0(i); A = [A; row]; bvec = [bvec; 0]; end % 好产出不等式:Y*Lambda + S_r - t*y0 >= 0 % linprog只能处理<=,所以乘-1处理 for r = 1:s1 row = zeros(1, totalVar); row(1:n) = -Y(r, :); row(n + m + r) = -1; row(end) = y0(r); A = [A; row]; bvec = [bvec; 0]; end % 坏产出不等式:B*Lambda + S_b - t*b0 <= 0 for k = 1:s2 row = zeros(1, totalVar); row(1:n) = B(k, :); row(n + m + s1 + k) = 1; row(end) = -b0(k); A = [A; row]; bvec = [bvec; 0]; end % 剔除自身:把被评价DMU对应的Lambda固定为0 row = zeros(1, totalVar); row(j) = 1; Aeq = [Aeq; row]; beq = [beq; 0]; % VRS约束 if vrs == 1 row = zeros(1, totalVar); row(1:n) = 1; row(end) = -1; Aeq = [Aeq; row]; beq = [beq; 0]; end f = [zeros(n, 1); 1 ./ (m .* x0); zeros(s1 + s2, 1); 1]; lb = zeros(totalVar, 1); options = optimoptions('linprog', 'Algorithm', 'dual-simplex', 'Display', 'off'); [sol, score, exitflag] = linprog(f, A, bvec, Aeq, beq, lb, [], [], options); if exitflag < 0 score = NaN; end detail.lambda = sol(1:n); detail.s_i = sol(n + (1:m)); detail.s_r = sol(n + m + (1:s1)); detail.s_b = sol(n + m + s1 + (1:s2)); detail.t = sol(end); end3.4 主程序:批量算所有DMU
主程序按前面的策略分两步走:
X = [4 5 6 7 8 9 10 11 12 13; 3 3 4 5 6 7 8 9 10 11; 2 3 3 4 5 6 7 8 9 10]; Y = [20 22 25 27 30 32 35 36 38 40; 15 18 20 22 25 27 30 33 35 37]; B = [5 6 7 8 9 10 12 13 15 16; 3 4 5 6 7 8 9 10 11 12]; [m, n] = size(X); s1 = size(Y, 1); s2 = size(B, 1); vrs = 1; finalScores = zeros(1, n); details = cell(1, n); for j = 1:n [rho, detailRho] = sbm_nd(X, Y, B, j, vrs); if rho >= 1 - 1e-8 [sup, detailSup] = super_sbm_nd(X, Y, B, j, vrs); if isnan(sup) finalScores(j) = rho; details{j} = detailRho; else finalScores(j) = sup; details{j} = detailSup; end else finalScores(j) = rho; details{j} = detailRho; end end跑完这个循环,finalScores里存放的就是最终效率值:无效DMU是小于1的效率得分,有效DMU是大于1的超效率得分。我用一个10个DMU的演示数据跑了一遍,示意输出大概是这样:
| DMU | 普通SBM得分 | 最终得分 | 解释 |
|---|---|---|---|
| 1 | 1.0000 | 1.1846 | 有效,且即使剔除自身仍有优势 |
| 2 | 0.8732 | 0.8732 | 无效,无需算超效率 |
| 3 | 1.0000 | 1.0000 | 有效,但剔除自身后优势被抹平 |
| 4 | 0.9157 | 0.9157 | 无效,产出不足 |
| 5 | 1.0000 | 1.3521 | 有效,优势明显 |
| 6 | 0.7625 | 0.7625 | 无效,存在投入冗余 |
| 7 | 0.1.031? | 1.0312 | 有效,超效率优势不大 |
| 8 | 0.8440 | 0.8440 | 无效 |
| 9 | 1.0000 | 1.2785 | 有效 |
| 10 | 0.9181 | 0.9181 | 无效 |
注意表里的数值是演示数据跑出来的示意结果,换真实数据会有变化。但你可以看到核心结构:小于1的最终得分来自普通SBM,大于1的最终得分来自超效率模型。
提示:上面构造变量矩阵时我用了循环,逻辑清晰但效率不高。如果你的DMU数量很大,比如上百个,建议把约束矩阵改成sparse稀疏矩阵构造,否则内存占用会很可观。
4. 结果解读:超效率得分只是起点,松弛量才是关键
4.1 得分落在哪个区间,该怎么解释
最终得分小于1,说明DMU不在前沿面上,存在投入冗余、产出不足或坏产出过多。得分越接近1说明越接近前沿,但不能直接说“效率是0.87”,更准确的说法是“相对于参考前沿还有13%左右的改进空间”。
最终得分大于1,说明这个DMU不仅有效,而且在其他DMU构成的前沿之外还有超额表现。1.35和1.03之间的差异可以解读为:得分1.35的DMU即使把自己从前沿中剔除,其他DMU也很难在其投入产出水平上模拟出它的表现。
4.2 把松弛量拿出来看,才知道问题出在哪
我只算分数、不看松弛量,是很多人做SBM最亏的地方。比如某个DMU最终得分0.87,表面上数字看着还行,一查松弛量发现:投入X1的松弛量占了原始值的20%,坏产出松弛量占了15%,而好产出松弛量为0。这说明问题主要出在投入冗余和坏产出削减上,而不是好产出不足。
超效率模型的松弛量同样值得看。一个超效率得分为1.28的DMU,如果它的坏产出松弛量很大,说明它之所以领先,很大程度上是靠坏产出控制得好;这比单纯看一个总分更能指导后续分析。
4.3 什么时候会出现“超效率得分等于1”
剔除自身后完全找不到更优或等价的前沿,超效率得分就会退化成1。这种情况不意味着模型出错,它说明这个DMU的空集前沿只有它自己,和别的DMU几乎没有可比性。实践中常见于样本量太小、或者某个DMU在某个指标上极端的场景。处理结果是保留1,但解读时要谨慎,别把它和普通SBM有效混为一谈。
5. 高频踩坑与排查清单
5.1 非期望产出的方向搞反了
我见过最多的问题就是把坏产出当成好产出直接放进Y矩阵,结果效率值虚高,甚至出现明显违反直觉的结果。检查方式很简单:单独看某个无效DMU的松弛量,如果坏产出松弛量显示“应该增加”,那方向一定错了。我的代码里坏产出和好产出用的是不同的约束等式,而且超效率模型里坏产出的不等式方向也和好产出相反,改代码的时候不要贪图方便共用一段矩阵。
5.2 超效率模型不可行或者出现NaN
超效率模型出现不可行,常见原因不是代码错了,而是被评价DMU在剔除自身之后,剩余DMU无法形成完整的前沿去模拟它的投入产出组合。我的主程序里已经做了兜底:普通SBM得分等于1但超效率不可行时,保留普通SBM得分1并继续下一个。实际分析中如果大量DMU出现这种情况,说明样本结构有问题,常见于样本量太小、指标数太多。
另一个隐藏原因是矩阵维度过大导致linprog求解困难。可以先用小数据测试,确认结果合理后再换成完整数据。不要一上来就套千级DMU,出了问题很难定位。
5.3 零值和量纲带来的数值问题
SBM目标函数里有 $1/x_{io}$ 这种项,如果某个投入恰好为0,分母就爆了。实际操作中我一般先做数据预处理,把零值处理掉:要么剔除该指标,要么统一加一个很小的平移量。产出同理,好产出为0在逻辑上可能也说不通。
SBM对量纲不敏感,因为松弛量都除以了对应原始值。但linprog内部求解时,如果某个指标达到百万级别、另一个指标只有0.01,数值稳定性会变差。我自己的习惯是先做一维归一化,让每个指标的量级大致落在0到100之间,结果稳定后再还原解读。
5.4 VRS和CRS的选择影响很大
代码里vrs=1对应规模报酬可变,vrs=0则去掉 $\sum\lambda_j=t$ 这条约束,对应规模报酬不变。效率分析中用VRS更常见,因为它考虑了DMU之间规模差异的影响。但要注意,VRS下有效DMU数量通常比CRS更多,超效率计算结果也更分散。选哪种要基于你的研究对象和已有文献习惯,不要为了“结果好看”随便切换。
5.5 线性化的归一化约束不要顺手删掉
很多初学者把线性化后的LP丢给linprog,发现结果离谱,一查是忘了加归一化约束。普通SBM和超效率SBM分别对应两条不同的归一化约束,一个是加号、一个是减号。这个等式是整个线性化的支柱,删了它,目标函数和约束就不再对应原来的分数规划,模型本身就是错的了。
6. 模型变体与使用场景的一点探讨
6.1 面板数据怎么用超效率SBM
如果数据是多期面板,直接把所有期混在一起算超效率,结果会受到技术进步和规模变化的干扰。常见做法是分开每期各跑一次,或者使用窗口DEA、全局前沿DEA。窗口DEA会在时间维度上移动窗口,全局前沿用全期数据构造统一前沿,两者都在面板场景中比简单逐期更稳健。
6.2 和方向性距离函数相比,超效率SBM的优势在哪
方向性距离函数需要人为设定方向向量,方向选择不同,结果就不同。SBM虽然不是完全不依赖方向,但它用松弛量自然决定改进路径,主观性更小。尤其在非期望产出场景里,SBM能同时处理投入压缩、好产出扩张、坏产出削减三个方向,比单方向的径向模型灵活得多。
6.3 什么时候该用超效率,什么时候不该用
超效率模型适合用来给有效DMU做进一步区分和排序。如果你只是想判断哪些DMU无效、找出改进空间,普通SBM其实已经够了。超效率得分大于1之后,很容易被误读为“效率超过100%”,所以写论文时一定要在方法论里明确说明:超效率得分只是用来排序有效DMU的,不是“超过100%”的直观含义。
我个人实际用的体会是,代码跑通不是难点,难在你得清楚每个松弛量在报表上对应什么业务含义。投入冗余是资源浪费,好产出不足是经营短板,坏产出松弛是减排压力。把这几个方向分开解读,比只给出一个效率分数要有价值得多。
最后分享一个调试小技巧:先用一个你手工能算出来的小样本跑一遍,比如5个DMU、2个投入、1个好产出、1个坏产出,确保结果和手算一致,再放心处理大样本。效率模型这种东西,数据一多,错误很容易被一堆数字淹没。