模型是一种对数据的简洁表达,它可以用几个简单的数字和符号概括繁杂的数据,帮助我们看到事物内在的运作规律。但是在我们追求简洁的同时,势必会牺牲一部分对数据的解释能力,造成失真。用更加通俗的话语来表达,就是会引入一些误差。
平衡模型解释能力和解释误差是每个数据分析者要面对的主要课题。
我们一直在讨论误差,但是「误差」到底是什么?很多数据分析者可能认为它是某种上帝生出来,被人碰巧发现的东西。但实际上,它是可以被你按需构造的一个变量。
假设我们对全校学生的身高做了测量,我们想要用一个数字来表示学生的身高,那么哪个数字是最适合的呢?要衡量什么是适合,我们就得定义什么是不适合。
我们可以把「不适合」定义成「它是否和这个简洁数字相等」,如果不相等,我们就认为这个模型是错误的,记作「一次错误」。在这个误差定义下,哪个数字是最好的「代表值」呢?
我们不妨直接把所有可能的数字都试一遍,看看谁是最好的那个数字。
set.seed(114514)
library(ggplot2)
Mode <- function(x) {
t <- table(x)
as.numeric(names(t)[which.max(t)])
}
data <- round(rnorm(1000, mean = 160, sd = 10), 1)
cat(sprintf("均值: %.1f\n", mean(data)))
cat(sprintf("中位数: %.1f\n", median(data)))
cat(sprintf("众数: %.1f\n", Mode(data)[1]))
calc_err <- function(candidate) {
(candidate != data) |> sum()
}
candidates <- seq(120, 200, by = 0.1)
err <- vapply(candidates, calc_err, integer(1))
estimate_df <- data.frame(
candidate = candidates,
err = err
)
best_estimate <- estimate_df$candidate[which.min(estimate_df$err)]
cat(sprintf("最佳估计: %.1f\n", best_estimate))
ggplot(estimate_df, aes(x = candidate)) +
geom_line(
aes(y = err),
color = "green"
)
有趣的是,当我们把误差定义为「估计值与数据是否相等」时,我们得到的最佳估计竟然是众数。
你可能觉得这样的误差定义太严格了,要么全对要么全错,看着很二极管,为什么不用偏差的尺度来衡量误差呢?估计值和原始数据差 1 毫米,就是 1 毫米的误差;原始数据差 1 厘米就是 1 厘米的误差。这也是一个不错的想法。让我们来把它实际写出来看看[1]。
set.seed(114514)
library(ggplot2)
Mode <- function(x) {
t <- table(x)
as.numeric(names(t)[which.max(t)])
}
data <- round(rnorm(1000, mean = 160, sd = 10), 1)
cat(sprintf("均值: %.1f\n", mean(data)))
cat(sprintf("中位数: %.1f\n", median(data)))
cat(sprintf("众数: %.1f\n", Mode(data)[1]))
calc_err <- function(candidate) {
abs(candidate - data) |> sum()
}
candidates <- seq(120, 200, by = 0.1)
err <- vapply(candidates, calc_err, numeric(1))
estimate_df <- data.frame(
candidate = candidates,
err = err
)
best_estimate <- estimate_df$candidate[which.min(estimate_df$err)]
cat(sprintf("最佳估计: %.1f\n", best_estimate))
ggplot(estimate_df, aes(x = candidate)) +
geom_line(
aes(y = err),
color = "blue"
)
神奇的事情又发生了!最佳估计变成了中位数!接下来你可能会问,更大的偏差可能意味着更严重的错误。比如你开了一家超市,你的员工犯了一次错误,你扣一块奖金,没什么问题,但是如果他一个月犯了 10 次错误,你扣十块奖金,力道看着就不太够。你在想能不能让多严重的偏差被视作更严重的问题。我们有一个统计工具,叫做平方,可以回应这个情况。比如,犯了一个错误,扣一块钱,犯了两个错误,扣四块钱,犯三个错误,扣九块钱,犯四个错误就是十六块[2]。
让我们来看看在这种误差设计下,最优的估计结果是怎样的。
set.seed(114514)
library(ggplot2)
Mode <- function(x) {
t <- table(x)
as.numeric(names(t)[which.max(t)])
}
data <- round(rnorm(1000, mean = 160, sd = 10), 1)
cat(sprintf("均值: %.1f\n", mean(data)))
cat(sprintf("中位数: %.1f\n", median(data)))
cat(sprintf("众数: %.1f\n", Mode(data)[1]))
calc_err <- function(candidate) {
((candidate -data) ^ 2) |> sum()
}
candidates <- seq(120, 200, by = 0.1)
err <- vapply(candidates, calc_err, numeric(1))
estimate_df <- data.frame(
candidate = candidates,
err = err
)
best_estimate <- estimate_df$candidate[which.min(estimate_df$err)]
cat(sprintf("最佳估计: %.1f\n", best_estimate))
ggplot(estimate_df, aes(x = candidate)) +
geom_line(
aes(y = err),
color = "green"
)
啊啦,真是神秘,最佳估计变成了均值!
这就是模型估计啦!很多不熟悉模型构建的朋友可能都以为统计建模是万丈高楼平地起,往一片虚空中不停加料就可以得到可用的模型。但实际的统计建模是通过定义误差、缩小误差后得到的最优结果。
你或许没有意识到,但你现在已经完成了最简单的统计建模工具。尽管均值、中位数、众数看起来是一个「描述统计量」,但在你计算它们的过程中,就已经在做建模的工作了。
进一步地,无论是机器学习模型还是日常用的统计建模其本质都是模型,优化的大体思路也都是一样的,唯一不同的是模型的结构和内部的执行细节。
基于此,我们应当关注这样的一个结构:数据 = 模型 + 误差。数据当中包含了很多信息,能通过建模优化被解释的部分就是模型,不能被解释的部分就是误差。一个完整的建模过程必须得同时报告模型本身,以及模型不能解释的部分有多少。并且,均值、中位数、众数是优化的结果,在报告的时候必须同时提供与之对应的误差统计量是什么。
业界常见的荒谬做法是,把模型定义为中位数,然后把方差当误差报告出来。这很明显是一种错配。当我们把误差定义为「是、否」时,模型是众数;当我们把误差定义为偏差绝对值之和时,模型是中位数;当我们把误差定义为偏差平方之和时,模型是均值。这是很重要的对应关系。
在机器学习中,分别把这三种误差称作 L0,L1,L2 误差。
希望本文能够帮助你理解其中的原理和奥妙。

Loading comments...