📈 第11章 误差
📄 一句话概括

分区模型没有新算法:先按一个变量分组,每组拟合一条自己的回归线。 技术部斜率 0.0898、销售部 0.0554,两条线各自的段内误差是 0.94 和 0.67,合起来 0.87。 但切太细会翻车:把技术部切成六段,六个斜率从 0.031 跳到 0.253,差 8 倍——那是噪声在装结构。

📝 本章和前面几章的关系

上一篇已经把两段分开算了。这一篇把这件事叫做一个模型,并说清它的边界。 书里最后还是回到「客户满意」这个落点:交付物变了,客户的问法也就变了。

一、分区模型就是几条线

分区模型没有新算法,就是把上面的做法固定下来:先分组,每组一条回归线。 分组变量可以是类别(部门、职级、地区),也可以是把连续量切成段(分数段、工龄段)。 这里用的是类别:技术部一条,销售部一条。两条线的参数完全不同,这是允许的。

关键在于分组要有业务含义。纯粹为了降误差而分组,等于把噪声也当成结构。

二、两条线长什么样

部门 人数 斜率 截距 段内 RMSE
技术部 60 0.0898 0.70 0.94
销售部 24 0.0554 0.66 0.67

两条线的斜率差了 0.0344:技术部每多 10 点涨 0.90 个点,销售部只涨 0.55 个点。 截距几乎一样(0.70 和 0.66),说明两条线差不多交汇在低分那一段——但越往高分越分开。 这个差本身就是结论:两个部门的加薪机制不是同一个。

三、切得太细会怎样

既然切两段好,切六段是不是更好?拿技术部 60 条按分数切成六段试一下。 六个斜率分别是 0.253、0.150、0.221、0.195、0.031、0.243。最大是最小的 8.3 倍。 可这六段全是技术部的同一套制度,斜率不该这么乱。这些差别是噪声装出来的,不是制度变了。

段内 RMSE 确实降到了总 0.86,但那是拿样本换的:每段只剩 6 到 11 个人。 段越细,样本越少,拟合出的斜率越像噪声。这叫过拟合,代价是解释力和可靠性。

四、怎么定切几段

第一条:段内的人得真的像同一类人。理由是业务上的,不是统计上的。 第二条:每段要有足够样本,否则斜率不可信。经验上少于二十条就要小心。 第三条:段与段之间要有实质差别。两条线斜率差不多,就没必要分开。

三条都是判断,不是计算。没有一条能靠跑代码得出。 所以分区模型是最依赖业务理解的一种模型:算法只占很小一部分。

五、客户回头了

人事拿走了两段模型:技术部一条、销售部一条,各自带着自己的 RMSE。 他们不再问「这个人该加多少」,改成问「这个人在哪一段、那一段的误差多大」。 问题变了,因为交出去的东西变了:从一个数,变成了一个数加一个适用范围。

108 分那位也有了去处:他不在任何一段的范围内,走特批。 这就是第 11 章的全部内容:模型给的不是答案,是带边界的答案。

六、动手 Demo:分区模型:每条线各管一段

两个部门各自一条线,把参数和段内误差摆出来。 再把技术部按分数切成六段,看斜率乱成什么样。

def mk(seed, n, lo, hi, a, b0, noise):
    S = [seed]

    def rnd():
        S[0] = (S[0] * 1103515245 + 12345) % 2147483648
        return S[0] / 2147483648.0

    out = []
    for i in range(n):
        x = lo + int(round(i * (hi - lo) / float(n - 1)))
        y = a + b0 * x + (rnd() - 0.5) * noise
        out.append((x, round(y, 1)))
    return out

TECH = mk(20261102, 60, 40, 95, 1.2, 0.085, 3.2)
SALE = mk(88117, 24, 42, 92, 1.0, 0.055, 2.6)

def fit(d):
    xs = [p[0] for p in d]
    ys = [p[1] for p in d]
    n = len(xs)
    mx = sum(xs) / float(n)
    my = sum(ys) / float(n)
    k = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / sum((x - mx) ** 2 for x in xs)
    return k, my - k * mx

def rmse(d, k, b):
    return (sum((y - (b + k * x)) ** 2 for x, y in d) / float(len(d))) ** 0.5

for nm, d in (("TECH", TECH), ("SALE", SALE)):
    k, b = fit(d)
    print("%s  n=%2d  m=%.4f  b=%.2f  RMSE=%.2f" % (nm, len(d), k, b, rmse(d, k, b)))

print()
print("技术部切成六段:")
for lo, hi in ((40, 50), (50, 60), (60, 70), (70, 80), (80, 90), (90, 96)):
    sub = [p for p in TECH if lo <= p[0] < hi]
    k6, b6 = fit(sub)
    print("  %2d~%2d  n=%2d  斜率 %+.3f  段内 RMSE %.2f" % (lo, hi, len(sub), k6, rmse(sub, k6, b6)))

实跑输出:

--------------------------------------------------------------------
分区模型:每条线各管一段
--------------------------------------------------------------------

  ① 分区模型就是几条线
  --------------------------------------------------------------------
  分区模型没有新算法,就是把上面的做法固定下来:先分组,每组一条回归线。
  分组变量要么是类别(部门、职级),要么是把连续量切成段。
  这里用的是类别:技术部一条,销售部一条。两条线的参数完全不同,这是允许的。

  ② 两条线长什么样
  --------------------------------------------------------------------

| 部门 | 人数 | 斜率 | 截距 | 段内 RMSE |
|---|---:|---:|---:|---:|
| 技术部 | 60 | 0.0898 | 0.70 | 0.94 |
| 销售部 | 24 | 0.0554 | 0.66 | 0.67 |

  两条线的斜率差了 0.0344——技术部每多 10 点涨 0.90 个点,销售部只涨 0.55 个点。
  这个差本身就是结论:两个部门的加薪机制不是同一个。

  ③ 切得太细会怎样
  --------------------------------------------------------------------
  既然切两段好,切六段是不是更好?拿技术部 60 条试一下,按分数切成六段。

| 分数段 | 人数 | 斜率 | 段内 RMSE |
|:---:|---:|---:|---:|
| 40~50 | 11 | +0.253 | 0.93 |
| 50~60 | 10 | +0.150 | 0.74 |
| 60~70 | 11 | +0.221 | 0.81 |
| 70~80 | 11 | +0.195 | 0.84 |
| 80~90 | 11 | +0.031 | 0.87 |
| 90~96 | 6 | +0.243 | 0.97 |

  六个斜率从 0.031 到 0.253,最大是最小的 8.3 倍。
  六个段都是技术部的同一套制度,斜率不该这么乱——这些差别是噪声装出来的,不是制度变了。
  段内 RMSE 确实更小了(总 RMSE 0.86),但那是拿样本换来的:每段只剩十几个人。
  ! 段越细,样本越少,拟合出来的斜率越像噪声。这叫过拟合,代价是解释力和可靠性。

  ④ 怎么定切几段
  --------------------------------------------------------------------
  第一条:切开之后,段内的人得真的像同一类人。理由是业务上的,不是统计上的。
  第二条:每段要有足够的样本,否则斜率不可信。经验上少于二十条就要小心。
  第三条:段与段之间要有实质差别。两条线斜率差不多,就没必要分开。
  这三条没有一条是算法给的,全靠对业务的理解。

  ⑤ 客户回头了
  --------------------------------------------------------------------
  人事拿走了两段模型:技术部一条、销售部一条,各自带 RMSE。
  他们不再问「这个人该加多少」,改成问「这个人在哪一段、那一段的平均误差多大」。
  问题变了,因为交出去的东西变了:从一个数,变成了一个数加一个适用范围。
  这就是第 11 章的全部内容:模型给的不是答案,是带边界的答案。

两段模型的斜率差 0.0344,是制度差异;六段模型的斜率差 0.222,是噪声。 同样是「差」,来源完全不同。 判断方法只有一个:问这个差在业务上解不解释得通。解释得通就是结构,解释不通就是过拟合。

30 秒实验

  1. 如果把销售部按「有无大客户」再分两段,会降误差还是会过拟合?判断依据是什么?
  2. 两条线的截距几乎一样(0.70 与 0.66),这说明了什么?
  3. 想一想:为什么分区模型比单一模型更容易说服客户,也更容易被客户质疑?

常见误区

  1. 切到误差最小为止:一直切下去误差总会降,但那是在拟合噪声。
  2. 分组没有业务含义:按「姓名字母」分组也能切,切出来的斜率毫无意义。
  3. 每段样本太少还硬拟合:六条数据拟合一条线,斜率可以是任何值。
  4. 只报段内 RMSE:单段误差小是必然的,要比就得拿全样本合起来比。
自测三问(点开对答案) 1. 分区模型和普通回归的本质区别是什么? —— 没有区别。算法完全一样,只是先把数据按一个变量分组,每组各跑一次回归。 2. 为什么切成六段反而不好? —— 因为每段样本太少,拟合出来的斜率主要由噪声决定。段内误差看着降了,但那是在拟合噪声。 3. 判断一个分组值不值得,看哪三件事? —— 段内是不是同一类人、样本够不够、段与段之间有没有实质差别。三条都要靠业务判断。

第 11 章到此收口。第 12 章换赛道:数据从一个表变成多张表,讲关系数据库。

相关笔记