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

人事把销售部的 24 条也加进来。数据从 60 条涨到 84 条,误差却从 0.94 涨到 1.39。 原因是两批人不服从同一套规则:技术部每 10 点多 0.90 个点,销售部只多 0.55 个点。 按部门切开各算各的,总误差回落到 0.87。分割的目的不是把数据弄小,是让每段误差变小。

📝 本章和前面几章的关系

上一篇把误差压成了一个数。这一篇讲怎么让它变小:不是换算法,是换分组。 销售部 24 条是新加的数据,种子写死在脚本里,跑出来人人一样。

一、客户又给了 24 条

人事这次把销售部也纳进来了:24 条,分数 42 到 92。 销售部的加薪区间是 2.5% 到 6.2%,平均 4.37%;技术部是 3.4% 到 10.6%,平均 6.76%。 两个部门的加薪水平根本不在一条线上。这不是数据脏,是制度不同。

销售的钱一部分走提成,年度调薪那部分本来就压得低;技术走职级,绩效直接挂钩。 数据合并之前,先问一句:这两批人是同一套规则生成的产物吗?

二、合并之后,误差反而变大了

用哪些数据 RMSE(百分点)
只用技术部 60 条 0.94
只用销售部 24 条 0.67
合并 84 条 1.39

数据从 60 条涨到 84 条,误差从 0.94 涨到 1.39,涨了 48%。 数据更多不等于模型更好。多出来的这批人,根本不服从同一个方程。 这是「样本量越大越好」这句话最典型的反例。样本量只在同分布的前提下才有意义。

三、为什么会变差

合并后的方程:加薪 = 0.56 + 0.0818 × 分数。 技术部单独是 0.70 + 0.0898 × 分数,销售部单独是 0.66 + 0.0554 × 分数。 合并的斜率 0.0818 落在两者之间,是个折中——而折中在这种场合对谁都不合适。

给技术部的人算,普遍偏低;给销售部的人算,普遍偏高。两头都错。 误差不是来自噪声,是来自结构。噪声可以多测几次压下去,结构错只能靠分组解决。

四、分割:按部门切开,各算各的

把 84 条分成两组,各自拟合一条线,再把两组残差合起来算一个总 RMSE。 结果 0.87,比合并的 1.39 低了不少,也比技术部单独的 0.94 低——因为它两边都照顾到了。 两个斜率也说得清了:技术部每 10 点多 0.90 个点,销售部每 10 点多 0.55 个点。

这才是分割的真正目的:不是把数据切小,是让每一段的误差变小。 切完如果误差没降,说明切错了变量——那个变量本来就不该分。

五、解释和预测,这次同时在

预测上:RMSE 从 1.39 降到 0.87。 解释上:现在能说「技术部靠绩效,销售部靠业绩提成」——这句话合并模型给不出来。 优秀的回归分析两件事都要做:既能给出更准的数,又能说清为什么。

只准不说,客户不敢用;只说不准,客户没法做预算。 下一篇把这件事固定成一个模型:分区模型。

六、动手 Demo:分割是为了管理误差

技术部 60 条加销售部 24 条,先合并拟合,再分开拟合,比两个 RMSE。 顺便看看合并方程给两个部门各自的预测是偏高还是偏低。

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

def near(d, q):
    sub = [y for x, y in d if abs(x - q) <= 2]
    return sum(sub) / float(len(sub)), len(sub)

kt, bt = fit(TECH)
ks, bs = fit(SALE)
print("技术部 m=%.4f b=%.2f RMSE=%.2f" % (kt, bt, rmse(TECH, kt, bt)))
print("销售部 m=%.4f b=%.2f RMSE=%.2f" % (ks, bs, rmse(SALE, ks, bs)))

ALL = TECH + SALE
ka, ba = fit(ALL)
print("合并   m=%.4f b=%.2f RMSE=%.2f" % (ka, ba, rmse(ALL, ka, ba)))

res = [y - (bt + kt * x) for x, y in TECH] + [y - (bs + ks * x) for x, y in SALE]
print("分开后总 RMSE=%.2f" % ((sum(d * d for d in res) / len(res)) ** 0.5))

print()
mt, nt = near(TECH, 70)
ms, ns = near(SALE, 70)
print("合并方程给 70 分的人:%.2f%%" % (ba + ka * 70))
print("技术部 68~72 分 %d 人,实际平均 %.2f%%" % (nt, mt))
print("销售部 68~72 分 %d 人,实际平均 %.2f%%" % (ns, ms))
print("两边实际平均相差 %.2f 个点" % (mt - ms))

实跑输出:

--------------------------------------------------------------------
分割是为了管理误差
--------------------------------------------------------------------

  ① 客户又给了 24 条
  --------------------------------------------------------------------
  人事这次把销售部也纳进来了:24 条,分数 42 到 92。
  销售部的加薪区间是 2.5% 到 6.2%,平均 4.37%。技术部是 3.4% 到 10.6%,平均 6.76%。
  两个部门的加薪水平根本不在一条线上。

  ② 合并之后,误差反而变大了
  --------------------------------------------------------------------

| 用哪些数据 | RMSE(百分点) |
|---|---:|
| 只用技术部 60 条 | 0.94 |
| 只用销售部 24 条 | 0.67 |
| 合并 84 条 | 1.39 |

  数据从 60 条涨到 84 条,误差从 0.94 涨到 1.39,涨了 48%。
  数据更多不等于模型更好。多出来的这批人,根本不服从同一套规则。
  ! 这是「样本量越大越好」这句话最典型的反例。

  ③ 为什么会变差
  --------------------------------------------------------------------
  合并后的方程是:加薪 = 0.56 + 0.082 × 分数。
  技术部单独是 0.70 + 0.0898 × 分数,销售部单独是 0.66 + 0.0554 × 分数。
  合并方程的斜率 0.0818 落在两者之间,对谁都不合适。
  给技术部的人算,普遍偏低;给销售部的人算,普遍偏高。两头都错。
  拿 70 分验一下:合并方程给 6.29%。
  技术部 68~72 分 5 人实际平均 6.76%,销售部 3 人实际平均 4.73%。
  同一个分数,两个部门实际差了 2.03 个点。合并方程只能给一个数,它给的是这两个的折中。
  这叫把一个方程硬套在两套制度上。误差不是来自噪声,是来自结构。

  ④ 分割:按部门切开,各算各的
  --------------------------------------------------------------------
  切开之后,84 个点的总 RMSE 是 0.87,比合并的 1.39 低了不少。
  两个部门各自的斜率也说得清了:技术部每 10 点多 0.90 个点,销售部每 10 点多 0.55 个点。
  这才是分割的真正目的:不是把数据弄小,是让每一段的误差变小。

  ⑤ 解释和预测,这次同时在
  --------------------------------------------------------------------
  预测上:RMSE 从 1.39 降到 0.87。
  解释上:现在能说「技术部靠绩效,销售部靠业绩提成」——这句话合并模型给不出来。
  优秀的回归分析两件事都要做:既能给出更准的数,又能说清为什么。
  只准不说,客户不敢用;只说个大概,客户没法做预算。

合并的 RMSE 1.39 比两个部门各自的都大——这不是「平均」,这是「都不合适」。 「拿 70 分验一下」那三行最能说服客户:同一个分数,两个部门实际差 2.03 个点, 合并方程却只能给一个数。

30 秒实验

  1. 如果按分数段切(40~65、65~95)而不是按部门切,总 RMSE 会降吗?为什么?
  2. 切完误差没降,说明什么?该怎么处理?
  3. 想一想:如果销售部只有 5 条记录,还该不该单独建一条线?

常见误区

  1. 数据越多越好:不同分布的数据掺在一起,只会把误差做大。
  2. 为了降误差而乱切:切到什么程度都能降一点,切到没有业务含义就是自欺。
  3. 切完不看样本量:一段只剩五六条,斜率完全靠不住。
  4. 只报更低那个数:切分后的 RMSE 是全样本合起来算的,不是各段里挑一个。
自测三问(点开对答案) 1. 数据从 60 条涨到 84 条,误差为什么反而变大? —— 因为销售部不服从技术部那套规则。合并方程是个折中,对两边都偏,残差自然更大。 2. 分割的真正目的是什么? —— 让每一段的误差变小,同时让每一段的参数能各自解释。不是单纯把数据切小。 3. 切完之后误差没下降,说明什么? —— 说明切错了变量。那个变量本来就不携带结构差异,这种切法只会浪费样本。

把「先分组、每组一条线」固定下来,就叫分区模型。下一篇看它的威力和它的陷阱。

相关笔记