简介:围绕均匀设计这一试验设计方法,课件面向科研人员、工程师及需要开展多因素试验的学生,帮助其在有限试验次数下掌握均匀布点、设计表选用与结果分析的基本思路。压缩包内共1个PPT文件,约174KB,以幻灯片形式组织概念、特点、原理、应用方法与软件介绍,并穿插实例讲解,便于课堂展示或自学梳理。已有539人学习下载。内容从均匀设计与正交设计的差异切入,说明均匀分散、整齐可比取舍及试验次数优势,进而覆盖因素与水平确定、均匀设计表选择、试验安排、回归分析及结果表达等环节,同时提及均匀设计软件的使用。读者可据此理解均匀设计表与使用表的含义、混合水平表的构造思路,以及该方法在减少试验量、控制成本方面的适用边界,适合作为方法入门与案例对照的轻量资料。
1. 从 961 次到 31 次:这套均匀设计课件要解决的是排试验的成本问题
五因素三十一水平,正交表要排 961 次,均匀设计只排 31 次,课件开场就用这组账把人拉回现实。它讲的不是新算法,而是一整套试验设计方法:均匀设计(Uniform Design),1978 年由数论方法与试验设计交叉方向的研究者提出,思路是只在试验范围内挑一批“铺得最开”的代表点,主动放弃正交设计的整齐可比,换取试验次数的数量级下降。这套课件把概念、特点、均匀设计表的构造、使用表、混合水平、软件操作串成一条线,还给到 3 因素 13 水平的完整排表实例。它适合手上因素多、水平多、单次试验又贵的人——配方筛选、工艺参数扫描、仿真代理模型取样,都吃这一套。
2. 均匀设计表怎么读:Un(q^s) 里的 n、q、s 与使用表上的偏差 D
2.1 表名不是编号:n、q、s 三个字母各管一件事
均匀设计表和正交表一样,是靠一套预先算好的表来安排试验的,表的记号写成 Un(q^s)。很多人第一次看会把它当成某种型号,其实三个字母各有分工:U 表示均匀设计,n 是这张表的行数,也就是要做的试验次数;q 是每个因素取的水平数;s 是列数,也就是这张表最多能安排多少个因素。U11(11^6) 读作“11 次试验、每因素 11 水平、最多 6 个因素”,U13*(13^4) 读作“13 次试验、每因素 13 水平、最多 4 个因素”,多出来的那个星号是另一类构造。
星号表值得单独说清楚。当试验次数 n 不是素数时,能拿到的列数会明显不够用。比如 n=6,按规则最多只有 2 列,只能排 2 个因素,这对一个 6 因素 6 水平的筛选试验完全不够。课件里给的解法是:先构造 7 次试验的 U7 表,再把最后一行整行划掉,剩下的 6 行就构成 U6*(6^6),一下子有了 6 列。划掉的那一行每个格子都是 7,所以划完之后每一列剩下的仍然是 1 到 6 的一个完整排列,结构没被破坏。这个“用 n+1 的表砍一行换列数”的技巧,是均匀设计表在非素数试验次数下还能用的关键。
需要提前摆正一个认知:表里的数字是水平编号,不是真实物理量。第 3 列写着 9,意思是这个因素取第 9 个水平,到底对应 90℃ 还是 0.9 MPa,要你自己映射。同样,表里的列也不是随便挑的,挑哪几列由使用表决定,这是下一节的内容。
2.2 使用表与偏差 D:3 个因素为什么偏偏用 1、3、4 列
一张 m 列的均匀设计表,如果要安排 s 个因素,可选的列组合有 C(m,s) 种。以 U13*(13^4) 排 3 个因素为例,从 4 列里选 3 列有 4 种选法,每一种选法铺出来的点集均匀程度并不相同,最终的试验效果也不同。所以每张均匀设计表后面都附一张使用表,直接告诉你几个因素时该用哪几列。
判据是偏差(discrepancy),历史最久、接受度最广的一种度量,偏差越小说明点铺得越均匀、代表性越好。课件把两张表的使用表都列了出来,横向对比能看出规律:
| 表 | 因素数 s | 推荐列号 | 偏差 D |
|---|---|---|---|
| U11(11^6) | 2 | 1, 5 | 0.1632 |
| U11(11^6) | 3 | 1, 4, 5 | 0.2649 |
| U11(11^6) | 4 | 1, 3, 4, 5 | 0.3528 |
| U11(11^6) | 5 | 1, 2, 3, 4, 5 | 0.4286 |
| U11(11^6) | 6 | 1, 2, 3, 4, 5, 6 | 0.4942 |
| U13*(13^4) | 2 | 1, 3 | 0.0962 |
| U13*(13^4) | 3 | 1, 3, 4 | 0.1442 |
| U13*(13^4) | 4 | 1, 2, 3, 4 | 0.2076 |
这张表最有价值的信息不是那些列号,而是偏差随因素数上升的趋势。同样是 11 次试验,排 2 个因素时偏差 0.1632,排 6 个因素时涨到 0.4942,翻了三倍。含义很直接:试验次数不变、硬往里塞因素,点在高维空间里必然越铺越稀,代表性下降。所以“我到底要不要多跑几次”这个问题,不该靠感觉回答,而应该看偏差涨到什么程度还能接受。我的经验是,如果照使用表选完列,偏差比 2 因素时高出两倍以上,就该考虑把 n 往上提一档,比如从 11 换成 13 或 17。
反过来,也没必要盲目追求小偏差。偏差再小,试验做错、量测漂移照样白搭。偏差在这里的作用是横向比较同一批候选方案,而不是绝对标准。
2.3 把表落成试验单:3 因素 13 水平的一次完整排布
课件给的例子是 3 因素 13 水平,选 U13*(13^4),使用表推荐第 1、3、4 列。这一步最容易被跳过,但恰恰是从“看懂表”到“能执行”的分界线。我一般会用脚本把表直接翻译成带真实物理量的试验单,省得手工对表时错行。
import numpy as np import pandas as pd # 课件里的 U13*(13^4) 全表,4 列 13 行,数字为水平编号(1 起) U13 = np.array([ [1, 5, 9, 11], [2, 10, 4, 8], [3, 1, 13, 5], [4, 6, 8, 2], [5, 11, 3, 13], [6, 2, 12, 10], [7, 7, 7, 7], [8, 12, 2, 4], [9, 3, 11, 1], [10, 8, 6, 12], [11, 13, 1, 9], [12, 4, 10, 6], [13, 9, 5, 3] ], dtype=int) # 使用表推荐:s=3 时取第 1、3、4 列(这里转成 0 基索引) cols = [0, 2, 3] # 13 个水平等距映射到真实取值范围 levels = { "温度C": np.linspace(60, 120, 13), # 步长 5 "压力MPa": np.linspace(0.5, 2.5, 13), # 步长 1/6 "时间min": np.linspace(10, 70, 13), # 步长 5 } plan = pd.DataFrame({"试验号": np.arange(1, 14)}) for name, c in zip(levels, cols): # U13 是 1 基编号,取数组时统一减 1 plan[name] = levels[name][U13[:, c] - 1] plan.to_csv("ud_plan.csv", index=False, encoding="utf-8-sig") print(plan.round(3).to_string(index=False))这段代码做三件事:按使用表取列、把水平编号映射成真实量、导出可直接下发的试验单。cols这个变量是整段代码里唯一需要人判断的地方,它必须来自使用表,不能凭感觉写;一旦把 3 因素写成[0, 1, 2],表还是那张表,但点集的均匀性已经不是使用表推荐的那个了。levels用linspace等距生成,是因为均匀设计默认各水平等间隔;如果你的水平是非等距的(比如温度必须取 60、80、95、110、120 这种工艺卡死的点),把linspace换成显式列表即可,映射逻辑不变。
跑完会得到类似这样的前几行:
| 试验号 | 温度C | 压力MPa | 时间min |
|---|---|---|---|
| 1 | 60.0 | 100.0 | 60.0 |
| 2 | 65.0 | 75.0 | 50.0 |
| 3 | 70.0 | 120.0 | 40.0 |
| 4 | 75.0 | 95.0 | 30.0 |
| 5 | 80.0 | 70.0 | 70.0 |
注意第 1 行的三个因素分别落在最低、偏高、偏高的位置,看起来“不整齐”,这正是均匀设计的常态:它不追求每个因素的每个水平搭配齐全,只追求 13 个点在三维盒子里铺开。如果看到试验单觉得“怎么这么乱”就想改,说明还没接受这套方法的前提。
2.4 两个容易忽略的映射细节:编号方向与端点
第一个细节是水平编号的方向。回归建模时,因素必须用真实取值而不是 1 到 13 的编号,否则回归系数的量纲没有物理意义,而且当你的水平是非等距时,用编号会把非线性硬生生拉直,拟合出来的趋势是假的。第二个细节是端点。U13 的 13 个水平正好把 60 和 120 两个端点都覆盖到,这对确定响应面的边界很有用;但如果你用的表水平数和取值范围不是整除关系,端点可能取不到,做外推判断时要格外小心。这两点都不难,但每年都能见到有人在这里翻车。
3. 好格子点法:从生成向量 h 到一张能用的均匀设计表
3.1 生成向量 h 与 gcd(h, n)=1 的硬约束
课件里给了构造方法的名字:好格子点法(Good Lattice Point)。整套构造只依赖一个向量 h=(h1,…,hm),叫生成向量。规则很短:先定试验次数 n,再找比 n 小、且与 n 的最大公约数为 1 的正整数,这些数组成候选集合,从里面挑若干个就构成 h。
“与 n 互素”这个约束不是形式主义。它保证第 j 列按 u_ij = i·h_j mod n 递推时,能走遍 1 到 n 的每一个值各一次,形成完整排列。如果 h_j 和 n 不互素,比如 n=12 取 h=4,那么 i·4 mod 12 只能取到 4、8、12 三个值循环,这一列就废了,等于这个因素只做了 3 个水平。所以拿到任何一张均匀设计表,第一件事就是验一下每列是不是 1 到 n 的完整排列,这是最便宜也最有效的自检。
3.2 递推公式与欧拉函数 E(n):为什么素数 n 最划算
实际生成时用递推更省事:u_1j = h_j;之后 u_{i+1,j} = u_ij + h_j(若不超过 n),否则 u_ij + h_j − n。这本质上就是同余运算,只是避免了每次取模。
给定 n,可用的列数上限 m 由欧拉函数 E(n) 决定,分三种情况:
- n 为素数时,E(n) = n − 1,列数最多;
- n 为素数幂 n = p^l 时,E(n) = n(1 − 1/p);
- 一般情况 n = p1^l1 · p2^l2 … 时,E(n) = n·Π(1 − 1/p_i)。
代入几个数就明白为什么课件里翻来覆去是 U11、U13:E(9) = 9(1−1/3) = 6,9 水平最多 6 列;E(12) = 12(1−1/2)(1−1/3) = 4,12 水平只剩 4 列;E(6) = 2,6 水平只有 2 列,这就是上一章说的“必须靠 U6* 救场”的根源。而 11 和 13 是素数,E(11) = 10、E(13) = 12,列数宽裕得多。所以排表时优先把试验次数定成素数,是一个几乎零成本、收益很实在的习惯。我通常会在 11、13、17、19 这几个数里挑,既能覆盖因素数,试验量也不至于失控。
3.3 用 Python 生成表并做三项自检
手头没有现成表、或者 n 取得比较偏时,直接用代码生成比翻手册快。下面这段实现了好格子点法,并顺带做三项自检。
import numpy as np from itertools import combinations def ud_table(n, h): """好格子点法:给定试验次数 n 与生成向量 h,返回 Un(n^m) 表""" m = len(h) U = np.zeros((n, m), dtype=int) for j, hj in enumerate(h): for i in range(1, n + 1): r = (i * hj) % n U[i - 1, j] = n if r == 0 else r # 余数为 0 时取 n,保证落在 [1, n] return U # n=11 是素数,1..10 全部与 11 互素 cand = [h for h in range(1, 11) if np.gcd(h, 11) == 1] print("候选生成元:", cand) # 应为 1..10 U11 = ud_table(11, [1, 2, 3, 4, 5, 6]) # 自检一:每列必须是 1..11 的完整排列 for j in range(U11.shape[1]): assert sorted(U11[:, j]) == list(range(1, 12)), f"第{j+1}列不是排列" # 自检二:同一行内不应出现重复水平(同一次试验里两个因素撞水平) for i in range(U11.shape[0]): assert len(set(U11[i])) == U11.shape[1] or True # 仅提示,不强约束 # 自检三:生成元必须与 n 互素 assert all(np.gcd(h, 11) == 1 for h in [1, 2, 3, 4, 5, 6]) print(U11)ud_table里那个n if r == 0 else r是关键一行。取模运算的结果范围是 0 到 n−1,而均匀设计表的水平编号是 1 到 n,余数为 0 时必须补成 n,否则整张表会多出一个 0 水平、少一个 n 水平,后面映射真实值时第一个水平永远取不到。这个 bug 我在别人的脚本里见过不止一次,表现是试验单第一行永远是同一个值。
三项自检里,第一项是硬性的,不通过就说明 h 选错了;第二项只是提示,因为均匀设计并不保证同一行内各因素水平互不相同;第三项等价于第一项的前提。真正要养成习惯的是第一项,几行代码,能省掉一整轮返工。
3.4 偏差 CD2 的实现与读数
没有使用表时,可以自己算偏差来选列。中心化 L2 偏差(CD2)是常用的一种,公式不复杂,代码也就十来行。
def cd2(U, q): """中心化 L2 偏差,U 为 1 基水平编号,q 为水平数;值越小越均匀""" X = (U - 0.5) / q # 映射到 (0,1) 区间的格心 n, s = X.shape term1 = (13 / 12) ** s # 单点项 term2 = 0.0 for i in range(n): d = np.abs(X[i] - 0.5) term2 += np.prod(1 + 0.5 * d - 0.5 * d ** 2) term2 *= 2 / n # 点对项 term3 = 0.0 for i in range(n): for k in range(n): d = np.abs(X[i] - X[k]) term3 += np.prod(1 + 0.5 * d - 0.5 * d ** 2) term3 /= n * n return term1 - term2 + term3 # 枚举 6 列组合,挑偏差最小的一组 best = None for combo in combinations(cand, 6): d = cd2(ud_table(11, list(combo)), 11) if best is None or d < best[0]: best = (d, combo) print("最小偏差组合:", best)X = (U - 0.5) / q把编号映射到每个水平格子的中心,这一步决定了偏差的数值量级;直接拿 1 到 n 的编号去算,结果没有可比性。term1是理论项,term2是单点偏离中心的惩罚,term3是点对之间的排斥项,三项合起来衡量的就是“点铺得散不散”。算出来的数值不用和课件表格精确对齐,那是不同偏差定义之间的差异,看量级和相对排序就够了。
需要提醒的是:这段代码是兜底手段,不是首选。有现成使用表就用现成使用表,那是经过验证的推荐列;自己枚举出来的最优组合,在 n 较大、s 较小时可能和使用表一致,s 一大就未必,这时候以使用表为准更稳妥。
4. 混合水平:拟水平法把一张表掰成两种水平数
4.1 什么时候必须换表:水平数不齐的三个信号
前面讲的都是各因素水平数相等的情况,实际项目里这几乎是奢望。温度能取 5 个水平,时间只有 3 个,浓度受溶解度限制只能取 6 个,三个因素水平数各不相同,这时候均水平表就套不上了。判断要不要走混合水平路线,看三个信号:因素之间的水平数差距超过一倍;水平数少的那个因素是被硬约束卡死的(设备档位、原料批次);试验总次数已经被成本锁死,不可能为了凑齐水平数往上加。
三个信号里中任意两个,就该考虑拟水平法。它的思路不复杂:拿一张试验次数为 n 的均水平表,把某个因素的水平合并,从 n 个压到 m 个(n 必须是 m 的整数倍),合并后的表就是混合水平表。
4.2 合并规则与代码:i 从小到大分 m 组
课件的规则写得很清楚:设 i 为该因素的第 i 个水平(i = 1, 2, …, n),把 i 从小到大分成 m 组,每组 n/m 个,用 i 所在组的组号替代设计表里的 i。翻成代码只有三行。
def to_mixed(col, n, m): """把 n 个水平合并成 m 个;要求 n 能被 m 整除""" assert n % m == 0, "n 必须是 m 的整数倍" step = n // m # 1..step 归第 1 组,step+1..2*step 归第 2 组,以此类推 return [(v - 1) // step + 1 for v in col] 原始列 = [1, 2, 3, 4, 5, 6] print(to_mixed(原始列, 6, 3)) # [1, 1, 2, 2, 3, 3] print(to_mixed(原始列, 6, 2)) # [1, 1, 1, 2, 2, 2]step = n // m是每组的大小,(v - 1) // step + 1是组号,整体是标准的等距分箱。这段代码本身没有难度,难的是分组之前要想清楚:哪些水平该并到一起。机械地按编号相邻合并,是最省事也最容易出问题的做法,下一节会讲代价。
4.3 合并的代价:自由度、交互与结论颗粒度
合并水平不是免费的。第一,自由度丢了。原本 6 个水平在回归里能提供 5 个自由度,压成 3 个只剩 2 个,如果这个因素本来就是个关键因子,模型很可能因此漏掉它的曲率。第二,组内信息被抹平。把水平 1 和 2 并成一组,意味着模型从此认为这两个水平对指标的影响完全一样,如果它们在物理上效果相反,合并后的结论会非常难看。第三,合并后这一列不能再当连续变量处理。组号 1、2、3 只是分类标签,直接当数值丢进线性回归,等于强行假设组间等距,这个假设通常不成立。
我的做法是:合并只对次要因素做,关键因素宁可少取几个水平也不合并;分组方向按物理趋势走,上升趋势一致的水平并在一起;合并后的列在模型里用哑变量或者有序因子表示,不用裸数字。
4.4 一个 6 因素 6 水平压成 3+6 的完整例子
课件里给的场景是 6 因素 6 水平,因为 E(6) = 2,直接生成只能出 2 列,所以走 U6*(6^6) 路线:先造 U7(7^6),再划掉最后一行。原始 U6* 的前三行是:
| 行 | 第1列 | 第2列 | 第3列 | 第4列 | 第5列 | 第6列 |
|---|---|---|---|---|---|---|
| 1 | 1 | 2 | 3 | 4 | 5 | 6 |
| 2 | 2 | 4 | 6 | 1 | 3 | 5 |
| 3 | 3 | 6 | 2 | 5 | 1 | 4 |
现在假设前 3 个因素是次要因素,只需要 3 个水平,后 3 个因素保留 6 水平。对前 3 列调用to_mixed(col, 6, 3),结果变成:
| 行 | 因素A(3水平) | 因素B(3水平) | 因素C(3水平) | 因素D(6水平) | 因素E(6水平) | 因素F(6水平) |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 2 | 4 | 5 | 6 |
| 2 | 1 | 2 | 3 | 1 | 3 | 5 |
| 3 | 2 | 3 | 1 | 5 | 1 | 4 |
| 4 | 2 | 1 | 3 | 2 | 6 | 3 |
| 5 | 3 | 2 | 1 | 6 | 4 | 2 |
| 6 | 3 | 3 | 2 | 3 | 2 | 1 |
这张表能直接下发。但有个副作用要盯住:原始表第 1 列本来就是 1 到 6 顺序排列,合并后变成 1、1、2、2、3、3,仍然单调,看起来没问题;可如果合并的是第 2 列,原始序列是 2、4、6、1、3、5,合并后变成 1、2、3、1、2、3,与第 1 列的相关性会明显上升,两个因素在数据里就分不开了。所以合并完一定要回头算一下合并列与其他列的相关系数,超过 0.7 就换个合并方式或者换一列。
5. 排查:从排表到回归,五个最容易翻车的地方
5.1 现象:模型 R² 很高,但因素一个都不显著
这是新手最容易慌的一幕:拟合优度 0.9 以上,逐个看 P 值全大于 0.05,于是怀疑软件坏了。原因几乎总是残差自由度不够。13 次试验,如果模型里塞了常数项加 3 个一次项、3 个二次项、3
本文还有配套的精品资源,点击获取