损失函数从何而来:是似然,不是惯例
同一组20次测量,三条目测直线,三种评分规则选出三个赢家。平方误差是一种关于噪声的假设。
本页内容
切割零件的刀片会磨损。一个十小时班次下来,刀刃钝到足以让传送带上下来的零件比刚开始宽出几分之一毫米;一旦超过 23.5 毫米,质检就会拒收。厂里没人知道这个时刻何时发生。他们手里只有一把卡尺、一本笔记,以及上周二的二十条读数:换刀后经过的小时数,以及那一刻测到的零件宽度。
有人在这些点中间画了一条直线。另一个人画了稍有不同的一条。第三个人又画了第三条。三条线在纸上看起来都合理,却对何时换刀给出了相差好几个小时的结论——在这家工厂,这就是平静一周和报废一批货之间的差别。
哪条线更好?
按现在的说法,这个问题没有答案。不是答案很难——而是根本没有答案。所谓“更好”不是一条直线像斜率那样自带的属性;它是一条直线连同一条给直线打分的规则所具有的属性。在有人把规则写下来之前,没有任何东西可算。本章会认真对待这句话,并最终发现:机器学习中最常见的规则不是惯例,而是某个关于世界的断言所带来的结果——这个断言可以被检验,而且有时是错的。
在第一行代码之前先坦白一句。这二十条读数并不来自真实工厂:它们是我从自己选的一条直线 生成的,再加上大约十分之一毫米尺度的随机噪声。这一点很重要,因为下面的一切都在讨论一个方法能否恢复真相,而检验这一点的唯一办法,是预先知道真相。所以:每小时 0.30 毫米,就是书后答案。你不能用它,只能拿它来核对。
Three rules, three winners
链接到此部分:Three rules, three winners下面是读数和三条直线,并用三种方式打分:平方误差,也就是所有人都会先拿起来用的规则;绝对误差,统计学家可能会选它;以及最大误差,机加工师傅会选它,因为质检员不关心你的平均表现——他拒收的是那一个超差的零件。
NumPy 在这里登场,比纯 Python 感知机晚了一章,原因只有一个:本章结尾我们要把四十万条候选直线逐一拿去和二十条读数比较,而 Python 循环不是做这件事的工具。它也是下面引用的所有资料所使用的记号。
import numpy as np
# Hours since the blade was changed, and the width of the part measured then.
SHIFT = np.array([
(0.5, 20.17), (1.0, 20.28), (1.5, 20.53), (2.0, 20.61), (2.5, 20.69),
(3.0, 20.94), (3.5, 21.21), (4.0, 21.31), (4.5, 21.27), (5.0, 21.35),
(5.5, 21.58), (6.0, 21.80), (6.5, 21.67), (7.0, 22.07), (7.5, 22.10),
(8.0, 22.31), (8.5, 22.48), (9.0, 22.66), (9.5, 22.90), (10.0, 23.13),
])
h, y = SHIFT[:, 0], SHIFT[:, 1]
LINES = {"A": (20.10, 0.26), "B": (20.20, 0.28), "C": (20.30, 0.26)}
for name, (a, b) in LINES.items():
r = y - (a + b * h)
print(f"{name} mean square {np.mean(r**2):.5f}"
f" mean absolute {np.mean(np.abs(r)):.5f}"
f" worst {np.max(np.abs(r)):.3f}") 高亮行里的量叫作 residual:直线说的数减去卡尺说的数,每条读数一个数。本章里的每条评分规则,以及之后二十八章里的每个 loss function,都是把一串 residual 压缩成一个数的某种办法。它们的区别只在于怎么压缩。
A mean square 0.02699 mean absolute 0.12600 worst 0.430
B mean square 0.02524 mean absolute 0.13700 worst 0.350
C mean square 0.03179 mean absolute 0.15000 worst 0.320读列,不要读行。平方误差说 B 最好,绝对误差说 A 最好,最大误差说 C 最好:同样二十个点,三条规则,三个赢家。
这三条线是我特意选来让它们产生分歧的,我应该明说。重点在于这有多容易——在看起来合理的截距和斜率里搜几分钟,就能找到成百上千组三元组。排序是你所选规则的属性,不是这些直线的事实,所以规则不是实现细节:它就是问题的定义。这也引出本章要回答的问题:你凭什么选择它?
One parameter, and a valley
链接到此部分:One parameter, and a valley先处理一个更小的问题,因为直线不是三条,而是无限多条。暂时采用平方误差,因为人人都会这么做,并用 Chapter 1 里让感知机少跑一万一千个 epoch 的技巧,把问题缩成一个数:从两列中各自减去均值。一旦点云被居中到原点,在平方误差下的最佳直线就会正好穿过原点——所以截距定了,只剩斜率要选。
u, v = h - h.mean(), y - y.mean() # 5.25 hours, 21.553 mm
def mse(theta):
return np.mean((v - theta * u) ** 2)
grid = np.arange(0.0, 0.6001, 0.001)
curve = np.array([mse(t) for t in grid])
print(grid.size, "candidates ->", f"theta={grid[curve.argmin()]:.3f}", f"mse={curve.min():.6f}")601 candidates -> theta=0.293 mse=0.010115六百零一个候选斜率,一个赢家:每小时 0.293 毫米,而真实值是 0.300。二十条带噪读数和一个 for 循环,就把误差压到每小时百分之一毫米以内——2.33%。
有意思的不是赢家,而是搜索的形状。把整条曲线打印出来,并旋转一下,让 loss 从左到右延展:
ts = np.arange(0.0, 0.6001, 0.04)
ls = np.array([mse(t) for t in ts])
for t, l in zip(ts, ls):
col = round(l / ls.max() * 50)
print(f"theta={t:.2f} |{' ' * col}*{' ' * (50 - col)}| mse={l:7.4f}")theta=0.00 | * | mse= 0.7244
theta=0.04 | * | mse= 0.5428
theta=0.08 | * | mse= 0.3878
theta=0.12 | * | mse= 0.2593
theta=0.16 | * | mse= 0.1575
theta=0.20 | * | mse= 0.0822
theta=0.24 | * | mse= 0.0336
theta=0.28 | * | mse= 0.0116
theta=0.32 | * | mse= 0.0161
theta=0.36 | * | mse= 0.0473
theta=0.40 | * | mse= 0.1050
theta=0.44 | * | mse= 0.1894
theta=0.48 | * | mse= 0.3004
theta=0.52 | * | mse= 0.4379
theta=0.56 | * | mse= 0.6021
theta=0.60 | *| mse= 0.7928这是一座从侧面看的山谷。它只有一个谷底,两侧的坡壁平滑升高,而且——这是 Chapter 1 的阶梯无法提供的部分——在它的每一个点上,都有一个定义明确的“下坡”方向。记住这个形状。Chapter 3 会完全围绕如何不访问全部六百零一个点就沿着它走下去,以及当一座山谷有不止一个谷底时会发生什么。
So why squared?
链接到此部分:So why squared?我们有山谷,是因为我们做了平方。绝对误差会让谷底出现一个尖角;最大误差会产生平坦区间,移动直线也什么都不变。平方当然方便——多数课程给出的理由大致也就是方便,只是换了四种说法:它让误差为正(绝对值也可以);它更惩罚大误差(为什么应该这样?);它可微(四次方也可微);大家都这么用(确实如此,但这不是论证)。
诚实的立场是这样:平方误差选择了直线 B,绝对误差选择了直线 A。对这家工厂来说,其中一个是对的,另一个是错的,而到目前为止说过的任何东西都无法告诉你是哪一个。要选择规则,你必须知道一些关于读数是如何偏离直线的事情,而这是关于世界的问题,不是关于数学的问题。回答它需要一小件工具。
The likelihood of a line
链接到此部分:The likelihood of a line下面这个断言,会把“哪条线更好”变成一个有答案的问题。
假设一个零件的宽度等于直线加上一项随机误差,并假设这项误差来自一个 Gaussian——钟形曲线——均值为零,标准差为 :
Gaussian 的密度是
现在做一件感知机做不到的事。对一个给定候选斜率 ,每条读数都有一个 residual,而上面的公式会把这个 residual 变成一个数:如果这个斜率就是真相,那么正好这么大的误差有多可信?落在直线上的读数得到一个大数,偏离半毫米的读数得到一个小数。
这些读数彼此独立——卡尺不会记住上一个零件——所以乘法规则说,整本笔记的可信程度就是各个密度的乘积。这个乘积就是 的似然。1 注意方向,因为这正是 Bayes 规则关心的方向:数据是固定且已知的,变化的是参数。这不是“斜率的概率”。它是模型分配给你实际得到的数据的概率,只是把它看作斜率的函数。
SIGMA = 0.12
def gaussian(r, sigma):
return np.exp(-r ** 2 / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))
def likelihood(theta):
return np.prod(gaussian(v - theta * u, SIGMA))
for t in (0.25, 0.293, 0.35):
print(f"theta={t} likelihood = {likelihood(t):.6g}")theta=0.25 likelihood = 521.952
theta=0.293 likelihood = 2.42028e+07
theta=0.35 likelihood = 0.190312斜率 0.293 让这本笔记比 0.25 可信四万六千倍,比 0.35 可信一亿二千七百万倍。最大似然的原则是:选择那个让你实际观察到的东西尽可能不令人意外的参数。它不是定理,而是关于“最好”应该是什么意思的一项提议——而且这项提议有内容,因为在你被允许给任何东西打分之前,它迫使你先陈述关于噪声的假设。
The product breaks
链接到此部分:The product breaks把同样三行代码从一个班次换成一个月的班次来跑,这个方法就倒下了。
rng = np.random.default_rng(7)
u_big = rng.uniform(-5.25, 5.25, 2000) # 2000 readings, not 20
v_big = 0.30 * u_big + 0.12 * rng.standard_normal(2000)
print("2000 readings, sigma = 0.12 mm :", np.prod(gaussian(v_big - 0.30 * u_big, 0.12)))
noisy = 0.30 * u_big + 2.0 * rng.standard_normal(2000)
print("2000 readings, sigma = 2.00 mm :", np.prod(gaussian(noisy - 0.30 * u_big, 2.0)))
print("largest float64 :", np.finfo(np.float64).max)RuntimeWarning: overflow encountered in reduce
2000 readings, sigma = 0.12 mm : inf
2000 readings, sigma = 2.00 mm : 0.0
largest float64 : 1.7976931348623157e+308两千次乘法之后,答案是 inf。改动一个常数——卡尺更粗糙一些,让密度小于 1 而不是大于 1——同样的代码返回 0.0。两个答案都错,而且方向相反;没有一个会抛出你能捕获的异常,第二个甚至不会打印警告。
数学没有错。在那些设置下,似然是一个定义完好的有限数:它的自然对数是 1400.91,所以这个数本身大约是 。问题是你的计算机没有这个数。弄清楚它到底有哪些数,很值得,因为这不是它最后一次决定结果。
Where the square comes from
链接到此部分:Where the square comes from修复爆炸乘积的办法是常规办法:取对数。对数把乘积变成和;它严格递增,所以不会移动最大值的位置;而两千个温和数值的和,是 float64 可以毫无怨言处理的东西。按惯例,我们取负对数似然,这样更好就意味着更小。现在把 Gaussian 密度代进去,看看会发生什么。
-
从乘积开始。 似然是 ,其中 是上面的 Gaussian 密度。
-
取负 log。 乘积变成和,密度里的指数项会直接和对数抵消:
- 丢掉所有不含 的东西。 第一项是常数。求和前面的 是正的常数,而把一个函数乘以正的常数不会移动它的最小值位置。剩下的是
也就是 squared residuals 之和——本章一开始就拿来用的东西,因为这是任何人第一个会想到的东西。
这就是本章存在的结果,值得毫不含糊地说出来:平方误差不是惯例。它是 Gaussian 的负对数似然,去掉了常数。 最小化平方误差,与断言你的误差是 Gaussian、并询问哪个参数让你的数据最不令人意外,是完全同一件事。你一直在做这个断言;只是没人告诉你。
这个等价关系可以检查,所以就检查一下:用完整的负对数似然(常数全保留)和普通平方误差,扫描同样的六百零一个斜率。
N = v.size
def nll(theta):
r = v - theta * u
return N * np.log(SIGMA * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * SIGMA ** 2)
nlls = np.array([nll(t) for t in grid])
mses = np.array([mse(t) for t in grid])
print(f"argmin of the negative log-likelihood : theta={grid[nlls.argmin()]:.3f} nll={nlls.min():.6f}")
print(f"argmin of the mean squared error : theta={grid[mses.argmin()]:.3f} mse={mses.min():.6f}")
print("same index:", nlls.argmin() == mses.argmin())argmin of the negative log-likelihood : theta=0.293 nll=-17.001977
argmin of the mean squared error : theta=0.293 mse=0.010115
same index: True纵轴上的数不同,其中一个还是负的,而平方和永远不会为负:负对数似然可以低于零,因为密度可以超过 1。同一座山谷,同一个谷底,精确到最后一个网格点。
查看完整推导
究竟哪些丢弃是安全的?每一章推导 loss 时都会出现同样的手法,而且它并不总是无害。
只要加性常数不依赖你正在优化的参数,丢掉它就是安全的;丢掉一个正的乘性常数也是安全的,因为对任意 ,都有 。一旦 也要被拟合,这两条都会失效:那时 根本不是常数,它是阻止模型声称 和无限可信度的那一项。这正是下一节。
它们在 Chapter 3 还会以另一种方式失效:乘性常数不会移动最小值,但它会缩放 gradient,而 gradient 会乘以学习率。除以 得到均方误差而不是平方和,对答案不可见,却对训练运行高度可见——使用平方和时,batch size 翻倍,你迈出的每一步也翻倍。
Sigma is not free either
链接到此部分:Sigma is not free either我们武断地把 固定为 0.12,而厂里没人知道卡尺误差的散布。把它当作第二个未知量,也交给最大似然来决定。这里刚才丢掉的常数项又回来了,因为只有它站在模型和“完美精度”这种说法之间。
r = v - 0.293 * u
sigmas = np.arange(0.01, 1.0001, 0.0001)
nll_sigma = N * np.log(sigmas * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * sigmas ** 2)
print("best sigma on the grid :", round(float(sigmas[nll_sigma.argmin()]), 4))
print("sqrt(mean squared residual) :", round(float(np.sqrt(np.mean(r ** 2))), 4))best sigma on the grid : 0.1006
sqrt(mean squared residual) : 0.1006二者一致到四位小数,而且不是巧合:对那个表达式求导并令其为零,会精确得到 。所以均方误差不只是像方差。在这个模型下,它就是噪声方差的最大似然估计——你一直在最小化的那个数,其实是在估计你的传感器有多吵。
有一个小褶皱,说出来很便宜,之后重新发现会很贵:这个估计偏低,因为 residual 是相对于一个本身就被选来让它们变小的拟合来测量的。模拟一下——二十万本笔记,每本二十条读数,都来自真实方差恰好为 1 的分布,并且拟合的一个参数也是从这些读数中估计出来的。用 去除平方和,平均得到 0.9501;用 去除,得到 1.0001;而 正好是 0.95。你拟合的每个参数都会消耗一个自由度,这是一个大得多的问题最小的可见实例:模型在它被拟合的数据上总会显得更好。Chapter 4 会把这变成留出数据的纪律,Chapter 6 会给这个效应命名。
A loss is a claim about the noise
链接到此部分:A loss is a claim about the noise如果平方误差断言噪声是 Gaussian,下一个问题就是当这个断言为假时会怎样。不是略微为假——而是以真实测量会出错的方式为假。
在车间里,大多数卡尺读数能精确到十分之一毫米;但每个班次一两次,会有一片切屑卡在卡爪下面,让读数偏出好几毫米。这样的误差是重尾的:大多数时候很小,偶尔巨大,而且巨大发生的频率远高于钟形曲线允许的程度。Cauchy 分布是这种行为的标准简洁模型,它的密度和 Gaussian 一样简单:
差别在尾部:Gaussian 像 一样衰减,极其猛烈;Cauchy 像 一样衰减,几乎不衰减。结果说起来不如看起来容易:
rng = np.random.default_rng(3)
g = 0.12 * rng.standard_normal(10 ** 6) # Gaussian noise
c = 0.12 * rng.standard_cauchy(10 ** 6) # Cauchy noise, same scale
for k in (10 ** 2, 10 ** 3, 10 ** 4, 10 ** 5, 10 ** 6):
print(f"{k:>9,} samples gaussian var {g[:k].var():.4f} cauchy var {c[:k].var():10.2f}") 100 samples gaussian var 0.0164 cauchy var 0.26
1,000 samples gaussian var 0.0146 cauchy var 59.88
10,000 samples gaussian var 0.0145 cauchy var 358.17
100,000 samples gaussian var 0.0144 cauchy var 3097.98
1,000,000 samples gaussian var 0.0144 cauchy var 32886.10Gaussian 的样本方差稳定在 0.0144,也就是 ,并停在那里。Cauchy 的样本方差会上升,而且只要你继续采样就会继续上升,因为它没有东西可收敛到:Cauchy 分布没有方差,也没有均值。平方误差的全部工作都是最小化平方的平均值,但现在它被要求寻找一个不存在的量。
所以这里有一个卡尺被骗到的班次。同样二十个小时,同一把刀片,同样每小时 0.30 毫米的漂移——只是噪声现在是 Cauchy。拟合两次:一次最小化 squared residuals,一次最小化实际生成数据的噪声的负对数似然。居中技巧在这里帮不上忙——它只会为平方误差固定截距——所以两次拟合都要在截距和斜率的网格上暴力搜索,因为除了访问山谷中的每个点,我们仍然没有办法找到谷底。
SWARF = np.array([
(0.5, 20.08), (1.0, 21.95), (1.5, 20.86), (2.0, 27.51), (2.5, 20.64),
(3.0, 20.75), (3.5, 21.01), (4.0, 21.03), (4.5, 21.37), (5.0, 20.60),
(5.5, 22.03), (6.0, 21.95), (6.5, 21.98), (7.0, 22.01), (7.5, 21.73),
(8.0, 22.97), (8.5, 22.60), (9.0, 22.66), (9.5, 22.44), (10.0, 22.78),
])
hs, ys = SWARF[:, 0], SWARF[:, 1]
A = np.arange(18.0, 22.001, 0.005) # 801 intercepts
B = np.arange(-0.20, 0.8001, 0.002) # 501 slopes
R = ys - (A[:, None, None] + B[None, :, None] * hs) # every line against every point
SCALE = 0.12
square = np.sum(R ** 2, axis=2) # least squares
cauchy = np.sum(np.log(1 + (R / SCALE) ** 2), axis=2) # Cauchy likelihood
for name, surface in (("least squares", square), ("Cauchy likelihood", cauchy)):
i, j = np.unravel_index(surface.argmin(), surface.shape)
print(f"{name:>18}: width = {A[i]:.3f} + {B[j]:.4f} * hours"
f" -> 23.5 mm at hour {(23.5 - A[i]) / B[j]:.2f}")
print(f"{'the truth':>18}: width = 20.000 + 0.3000 * hours"
f" -> 23.5 mm at hour {(23.5 - 20.0) / 0.30:.2f}")
print(f"{A.size * B.size:,} candidate lines evaluated")两条高亮行就是两种拟合的全部差别。对 Cauchy 密度取 log,像之前一样丢掉常数,留下来的就是 。同一个配方,不同的噪声断言。
least squares: width = 21.380 + 0.1080 * hours -> 23.5 mm at hour 19.63
Cauchy likelihood: width = 19.935 + 0.3020 * hours -> 23.5 mm at hour 11.80
the truth: width = 20.000 + 0.3000 * hours -> 23.5 mm at hour 11.67
401,301 candidate lines evaluated最小二乘报告的漂移是每小时 0.108 毫米,约为真实速率的三分之一,并得出刀片可用到第 19.6 小时的结论。真实答案是第 11.7 小时。如果按这个拟合行动,工厂会让冲床多跑八小时,生产超差零件,而依据正是这个领域最标准的 loss function。Cauchy 拟合使用同样二十条读数、同样的网格,以及代码里仅一行的差别,落在了第 11.8 小时。
两个反对意见值得回答,因为它们都是优秀工程师首先会说的话。
离群点很明显——删掉就行。 你可以删,它会有帮助,但还不够。删掉单个最糟读数,会把最小二乘斜率从 0.108 移到 0.239,这仍然把换刀时间放在第 13.1 小时,晚了一个半小时;删掉最糟的、重新拟合、再删掉现在最糟的,会得到 0.286——注意这已经是一套程序,不是一次观察:如果改成删掉原始拟合的两个最大 residual,你会落在 0.223。但你现在已经做出了无法写清楚也无法辩护的判断,而把规则自动化也救不了它:先删最大 residual 再重拟合,在一千个模拟班次上运行,斜率误差中位数是 0.0177,而似然拟合是 0.0100;误差超过 0.05 的班次比例是 14.7%,而似然拟合是 1.3%。删除是在错误假设之上的补丁。似然不需要补丁,因为它从未假设离群点不可能发生。
你选了一个幸运数据集。 这个反对意见完全正确,所以最后一个实验会模拟一千个独立班次,并对每个班次用两种方式重新拟合。
A = np.arange(18.0, 22.001, 0.02) # a coarser grid: a thousand fits to do
B = np.arange(-0.20, 0.8001, 0.005)
lines = A[:, None, None] + B[None, :, None] * hs
rng = np.random.default_rng(2026)
err_sq, err_ca = [], []
for _ in range(1000): # 1000 independent shifts
ys = 20.00 + 0.30 * hs + SCALE * rng.standard_cauchy(hs.size)
R = ys - lines
_, j = np.unravel_index(np.sum(R ** 2, axis=2).argmin(), (A.size, B.size))
_, q = np.unravel_index(np.sum(np.log1p((R / SCALE) ** 2), axis=2).argmin(), (A.size, B.size))
err_sq.append(abs(B[j] - 0.30))
err_ca.append(abs(B[q] - 0.30))
err_sq, err_ca = np.array(err_sq), np.array(err_ca)
for name, e in (("least squares", err_sq), ("Cauchy likelihood", err_ca)):
print(f"{name:>18}: median slope error {np.median(e):.4f} mm/h"
f" off by more than 0.05 in {100 * np.mean(e > 0.05):4.1f}% of shifts"
f" worst {e.max():.3f}")
print(f"the likelihood fit is the closer of the two in {100 * np.mean(err_ca < err_sq):.1f}% of shifts") least squares: median slope error 0.0350 mm/h off by more than 0.05 in 40.4% of shifts worst 0.500
Cauchy likelihood: median slope error 0.0100 mm/h off by more than 0.05 in 1.3% of shifts worst 0.090
the likelihood fit is the closer of the two in 75.6% of shifts报告中位数而不是均值,原因与本节其他所有事情相同:最小二乘误差受 Cauchy 驱动,所以它们的平均值不是一个稳定可报告的东西。最小二乘在五个班次里有两个严重出错;似然拟合在七十七个班次里才有一个严重出错,而且它在一千个班次中的最坏失败,还不到最小二乘最坏失败的五分之一。
这并不让平方误差变坏。它让平方误差变得具体,而算术精确说明了原因。取一个 0.1 mm 的 residual 和一个 7 mm 的 residual。平方之后,坏读数对总和的贡献是好读数的 4,900 倍,所以整条线会被硬生生拖向它;在 Cauchy log-likelihood 下,同样两个 residual 的贡献是 0.527 和 8.133,比值为 15.4。坏读数仍然算数,只是不再有权决定一切。这就是稳健统计的开端:Huber 1964 年的 loss 在两者之间折中,小 residual 时表现为二次,大 residual 时表现为线性,7 而 Tukey 更早已经展示了只需多么少的污染,就足以让样本方差成为比平均绝对偏差更差的工具。8
还有一条历史注记,太好,不能不提。最小二乘最早由 Legendre 在 1805 年发表,作为一种方便的代数工具,除了确实有效之外并没有其他辩护。9 四年后,Gauss 把论证倒过来:他把算术平均数是合并重复测量的正确方式当作已知,问哪种误差分布会让均值成为最可能的值,并证明本质上只有一种分布做到这一点——也就是后来以他命名的那个分布。10 本章的推导就是他的推导,已有两个多世纪历史,却仍然是大多数课程省略的部分。
What you can now say, and what you still cannot do
链接到此部分:What you can now say, and what you still cannot doEarned. loss function 是评分规则,它产生的排序是规则的属性,不是候选项的属性。本课程里的每个 loss 都是某个关于噪声的假设的负对数似然,并丢掉了常数——这里 Gaussian 给出平方误差,Chapter 4 中 Bernoulli 给出交叉熵,而词汇表上的 categorical distribution 给出 Chapter 8 的 next-token loss。配方从不改变:陈述噪声,写出似然,取负 log。而当假设错误时,模型不只是“不精确”,而是会沿着你可以预测的方向出错。
Still missing. 我们通过访问山谷里的每一个点找到了谷底。这对一个参数和六百个候选值有效,也在两个参数、401,301 个候选值时用五分之一秒撑住了。同样分辨率下三个参数就是 201,051,801 个候选值,已经放不进一个数组;Chapter 5 里的一个小网络有数千个参数,而 Chapter 10 会给出价格的模型有数十亿个。这里的暴力法不是慢,而是在算术上不可能,本章也没有提出替代方案。
不过回头看看那座山谷。站在 、loss 为 0.0822 的位置,“下坡”方向并不神秘——你在页面上就能看见,曲线向右下方倾斜。如果你能询问 loss function:在你所站的这个点上,它往哪边倾斜,而不需要在别处求值,那么你就可以朝那个方向迈一步,再问一次,如此重复,直到地面变平。
这个问题有名字。函数在某一点的斜率叫导数;对于一个有许多参数的函数,同时收集所有方向上的斜率,就是 gradient。Chapter 1 不能使用它,因为感知机的误差是一道没有斜率可问的阶梯。本章构造了一个更好的东西:处处平滑的 loss,而且它来自一个明确陈述的假设,而不是偏好。
所以 Chapter 3 的问题不再是斜率是否存在。问题是如何计算它,为什么逆着它移动会下坡而不是上坡——这个符号几乎每门课程都要求你凭信念接受——以及每次再问之前该迈多远,而这一个数字最终会决定一次训练运行是收敛、永远围着答案振荡,还是一路跑向无穷。
Sources and method
链接到此部分:Sources and method也值得与本章一起阅读:Prince, Understanding Deep Learning §5.1–5.2 and Appendix C,它按这里使用的顺序从最大似然构建书中的每个 loss;Goodfellow, Bengio and Courville, Deep Learning §3.1–3.11 and §5.5,其最大似然部分也推导了 Chapter 4 需要的 KL divergence;Murphy, Probabilistic Machine Learning: An Introduction chapter 2 and §4.2,讨论最大似然能保证什么、不能保证什么;Deisenroth, Faisal and Ong, Mathematics for Machine Learning §6.1–6.4,严谨处理求和规则、乘法规则和 Bayes 规则;Tom Mitchell 的 CMU 短文 Estimating Probabilities: MLE and MAP (2016);以及 Dive into Deep Learning 的 §22.7,它用可运行代码得到同样结果。
参考资料
链接到此部分:参考资料-
Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222, pp. 309–368 (1922). 这里把似然作为一种通用方法提出,同时还有“parameter”、“statistic”、充分性和效率。命名本身,以及它与概率的分离,早一年出现于:Fisher, R. A., On the “probable error” of a coefficient of correlation deduced from a small sample, Metron 1, pp. 3–32 (1921), pp. 24–25. ↩
-
IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. 定义 binary32 和 binary16,以及让求和实验得出上述结果的舍入规则。 ↩
-
Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). 该格式的参数,以及用尾数 bit 换指数 bit 的理由。 ↩
-
Micikevicius, P. et al. Mixed Precision Training. ICLR 2018, arXiv:1710.03740. loss scaling,以及使它在 float16 中成为必要的实测 gradient 大小。 ↩
-
Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), pp. 5–48 (1991). 仍然是解释为什么两种求和顺序会不一致的最佳单篇文章。 ↩
-
Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1), p. 40 (1965). 半页讲清补偿求和。 ↩
-
Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1), pp. 73–101 (1964). 一个在零附近为二次、在尾部为线性的 loss,是推导出来的,不是拼补出来的。 ↩
-
Tukey, J. W. A survey of sampling from contaminated distributions, in Contributions to Probability and Statistics (Stanford University Press, 1960), pp. 448–485. ↩
-
Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (Paris, 1805), appendix Sur la méthode des moindres quarrés. 最小二乘的首次发表,当时作为一种计算工具。 ↩
-
Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburg, 1809), Book II, §§175–179. 从算术平均数到正态误差律,再从那里到最小二乘的论证。 ↩