# 梯度下降
> 原文:[https://www.bookbookmark.ds100.org/ch/11/gradient_descence_define.html](https://www.bookbookmark.ds100.org/ch/11/gradient_descence_define.html)
```
# HIDDEN
# Clear previously defined variables
%reset -f
# Set directory for data loading to work properly
import os
os.chdir(os.path.expanduser('~/notebooks/11'))
```
```
# HIDDEN
import warnings
# Ignore numpy dtype warnings. These warnings are caused by an interaction
# between numpy and Cython and can be safely ignored.
# Reference: https://stackoverflow.com/a/40846742
warnings.filterwarnings("ignore", message="numpy.dtype size changed")
warnings.filterwarnings("ignore", message="numpy.ufunc size changed")
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
%matplotlib inline
import ipywidgets as widgets
from ipywidgets import interact, interactive, fixed, interact_manual
import nbinteract as nbi
sns.set()
sns.set_context('talk')
np.set_printoptions(threshold=20, precision=2, suppress=True)
pd.options.display.max_rows = 7
pd.options.display.max_columns = 8
pd.set_option('precision', 2)
# This option stops scientific notation for pandas
# pd.set_option('display.float_format', '{:.2f}'.format)
```
```
# HIDDEN
tips = sns.load_dataset('tips')
tips['pcttip'] = tips['tip'] / tips['total_bill'] * 100
```
```
# HIDDEN
def mse(theta, y_vals):
return np.mean((y_vals - theta) ** 2)
def grad_mse(theta, y_vals):
return -2 * np.mean(y_vals - theta)
def plot_loss(y_vals, xlim, loss_fn):
thetas = np.arange(xlim[0], xlim[1] + 0.01, 0.05)
losses = [loss_fn(theta, y_vals) for theta in thetas]
plt.figure(figsize=(5, 3))
plt.plot(thetas, losses, zorder=1)
plt.xlim(*xlim)
plt.title(loss_fn.__name__)
plt.xlabel(r'$ \theta $')
plt.ylabel('Loss')
def plot_theta_on_loss(y_vals, theta, loss_fn, **kwargs):
loss = loss_fn(theta, y_vals)
default_args = dict(label=r'$ \theta $', zorder=2,
s=200, c=sns.xkcd_rgb['green'])
plt.scatter([theta], [loss], **{**default_args, **kwargs})
def plot_tangent_on_loss(y_vals, theta, loss_fn, eps=1e-6):
slope = ((loss_fn(theta + eps, y_vals) - loss_fn(theta - eps, y_vals))
/ (2 * eps))
xs = np.arange(theta - 1, theta + 1, 0.05)
ys = loss_fn(theta, y_vals) + slope * (xs - theta)
plt.plot(xs, ys, zorder=3, c=sns.xkcd_rgb['green'], linestyle='--')
```
我们有兴趣创建一个函数,它可以最小化损失函数,而不必强制用户预先确定要尝试的值是\theta$。换句话说,虽然`simple_minimize`函数具有以下签名:
```
simple_minimize(loss_fn, dataset, thetas)
```
我们想要一个具有以下签名的函数:
```
minimize(loss_fn, dataset)
```
此函数需要自动查找最小化的$\theta$值,无论其大小。我们将使用一种称为梯度下降的技术来实现这个新的`minimize`函数。
### 直觉
与损失函数一样,我们将首先讨论梯度下降的直觉,然后用数学形式化我们的理解。
由于`minimize`函数没有给定要尝试的$\theta$的值,因此我们从选择需要的任何位置的$\theta$开始。然后,我们可以迭代地改进对$\theta$的估计。为了改进对$\theta$的估计,我们研究了在选择$\theta$时损失函数的斜率。
例如,假设我们对简单数据集$\textbf y=[12.1、12.8、14.9、16.3、17.2]$使用 mse,而当前选择的$\theta$是 12。
```
# HIDDEN
pts = np.array([12.1, 12.8, 14.9, 16.3, 17.2])
plot_loss(pts, (11, 18), mse)
plot_theta_on_loss(pts, 12, mse)
```
![](https://img.kancloud.cn/88/55/8855d293936eeafe358572524b842815_355x246.jpg)
我们想为.\theta$选择一个减少损失的新值。要做到这一点,我们看损失函数在$\theta=12$时的斜率:
```
# HIDDEN
pts = np.array([12.1, 12.8, 14.9, 16.3, 17.2])
plot_loss(pts, (11, 18), mse)
plot_tangent_on_loss(pts, 12, mse)
```
![](https://img.kancloud.cn/73/6e/736e39ce0679f984501767f33e2c1692_355x246.jpg)
坡度为负,这意味着增加$\theta$将减少损失。
另一方面,如果$\theta=16.5 美元,则损失函数的斜率为正:
```
# HIDDEN
pts = np.array([12.1, 12.8, 14.9, 16.3, 17.2])
plot_loss(pts, (11, 18), mse)
plot_tangent_on_loss(pts, 16.5, mse)
```
![](https://img.kancloud.cn/a7/09/a7099d8803b196e403225cac1133e0aa_355x246.jpg)
当坡度为正时,降低$\theta$将减少损失。
切线的斜率告诉我们移动$\theta$的方向,以减少损失。如果坡度为负,我们希望$\theta$朝正方向移动。如果坡度为正,则$\theta$应朝负方向移动。在数学上,我们写道:
$$\theta^(t+1)=\theta^(t)-\frac \部分\部分\theta l(\theta ^(t),\textbf y)$$
其中,$\theta^(t)$是当前估计数,$\theta^(t+1)$是下一个估计数。
对于 MSE,我们有:
$$ \begin{aligned} L(\theta, \textbf{y}) &= \frac{1}{n} \sum_{i = 1}^{n}(y_i - \theta)^2\\ \frac{\partial}{\partial \hat{\theta}} L(\theta, \textbf{y}) &= \frac{1}{n} \sum_{i = 1}^{n} -2(y_i - \theta) \\ &= -\frac{2}{n} \sum_{i = 1}^{n} (y_i - \theta) \\ \end{aligned} $$
当$\theta^(t)=12$时,我们可以计算$-\frac 2 n sum i=1 n(y i-\theta)=-5.32$。因此,$\theta^(t+1)=12-(-5.32)=17.32 美元。
我们将旧的$theta$值绘制为绿色轮廓圆圈,新的值绘制为下面损失曲线上的填充圆圈。
```
# HIDDEN
pts = np.array([12.1, 12.8, 14.9, 16.3, 17.2])
plot_loss(pts, (11, 18), mse)
plot_theta_on_loss(pts, 12, mse, c='none',
edgecolor=sns.xkcd_rgb['green'], linewidth=2)
plot_theta_on_loss(pts, 17.32, mse)
```
![](https://img.kancloud.cn/16/f1/16f11378c5e11b488a4f1284d10dbdb4_355x246.jpg)
虽然$\theta$朝着正确的方向发展,但最终却远远超出了最低值。我们可以通过将斜率乘以一个小常数,然后从$\theta$中减去它来解决这个问题。我们的最终更新公式是:
$$\theta^(t+1)=\theta^(t)-\alpha\cdot\frac \部分\部分\theta l(\theta ^(t),\textbf y)$$
其中,$\alpha$是一个小常量。例如,如果我们设置$\alpha=0.3$,这是新的$\theta^(t+1)$:
```
# HIDDEN
def plot_one_gd_iter(y_vals, theta, loss_fn, grad_loss, alpha=0.3):
new_theta = theta - alpha * grad_loss(theta, y_vals)
plot_loss(pts, (11, 18), loss_fn)
plot_theta_on_loss(pts, theta, loss_fn, c='none',
edgecolor=sns.xkcd_rgb['green'], linewidth=2)
plot_theta_on_loss(pts, new_theta, loss_fn)
print(f'old theta: {theta}')
print(f'new theta: {new_theta}')
```
```
# HIDDEN
plot_one_gd_iter(pts, 12, mse, grad_mse)
```
```
old theta: 12
new theta: 13.596
```
![](https://img.kancloud.cn/45/26/4526883471c3031f5633f84f12690bed_355x246.jpg)
以下是此过程连续迭代的$\theta$值。请注意,$\theta$随着接近最小损失而变化得更慢,因为坡度也更小。
```
# HIDDEN
plot_one_gd_iter(pts, 13.60, mse, grad_mse)
```
```
old theta: 13.6
new theta: 14.236
```
![](https://img.kancloud.cn/7f/72/7f7277692df60f71c835aa0890e47eca_355x246.jpg)
```
# HIDDEN
plot_one_gd_iter(pts, 14.24, mse, grad_mse)
```
```
old theta: 14.24
new theta: 14.492
```
![](https://img.kancloud.cn/57/45/5745727916bc6795daab99b228bd4e91_355x246.jpg)
```
# HIDDEN
plot_one_gd_iter(pts, 14.49, mse, grad_mse)
```
```
old theta: 14.49
new theta: 14.592
```
![](https://img.kancloud.cn/3a/74/3a7452dc16def8ad38c724e5d5891006_355x246.jpg)
### 梯度下降分析
现在我们有了完整的梯度下降算法:
1. 选择一个起始值$\theta$(0 是一个常见的选择)。
2. 计算$\theta-\alpha\cdot\frac \partial \partial\theta l(\theta、\textbf y)$并将其存储为新值$\theta$。
3. 重复直到$\theta$在迭代之间不改变。
您将更常见地看到梯度$\nabla_uta$代替部分导数$\frac \部分\部分\theta$。这两个符号本质上是等效的,但是由于梯度符号更为常见,从现在起我们将在梯度更新公式中使用它:
$$\theta^(t+1)=\theta^(t)-\alpha\cdot\nabla\theta l(\theta^(t),\textbf y)$$
要查看符号:
* $\theta^(t)$是第$t$次迭代时的当前估计值$\theta^*。
* $\theta^(t+1)$是$\theta$的下一个选择。
* $\alpha$称为学习率,通常设置为一个小常量。有时,从一个更大的$\alpha$开始并随着时间的推移减少它是有用的。如果在迭代之间$\alpha$发生变化,我们使用变量$\alpha^t$来标记$\alpha$随时间变化$t$。
* $\nabla_ \theta l(\theta^(t),\textbf y)$是损失函数相对于时间$t$的偏导数/梯度。
现在您可以看到选择一个可微分损失函数的重要性:$\nabla_theta l(\theta、\textbf y)$是梯度下降算法的关键部分。(虽然可以通过计算两个稍有不同的$theta$值的损失差异并除以$theta$值之间的距离来估计梯度,但这通常会显著增加梯度下降的运行时间,因此使用它变得不切实际。)
梯度算法简单而强大,因为我们可以将它用于许多类型的模型和许多类型的损失函数。它是拟合许多重要模型的计算工具,包括大数据集和神经网络上的线性回归。
### 定义`minimize`函数[¶](#Defining-the-minimize-Function)
现在,我们回到原来的任务:定义`minimize`函数。我们将不得不稍微改变我们的函数签名,因为我们现在需要计算损失函数的梯度。
```
def minimize(loss_fn, grad_loss_fn, dataset, alpha=0.2, progress=True):
'''
Uses gradient descent to minimize loss_fn. Returns the minimizing value of
theta_hat once theta_hat changes less than 0.001 between iterations.
'''
theta = 0
while True:
if progress:
print(f'theta: {theta:.2f} | loss: {loss_fn(theta, dataset):.2f}')
gradient = grad_loss_fn(theta, dataset)
new_theta = theta - alpha * gradient
if abs(new_theta - theta) < 0.001:
return new_theta
theta = new_theta
```
然后我们可以定义函数来计算 mse 及其梯度:
```
def mse(theta, y_vals):
return np.mean((y_vals - theta) ** 2)
def grad_mse(theta, y_vals):
return -2 * np.mean(y_vals - theta)
```
最后,我们可以使用`minimize`函数计算$\textbf y=[12.1,12.8,14.9,16.3,17.2]$的最小化值。
```
%%time
theta = minimize(mse, grad_mse, np.array([12.1, 12.8, 14.9, 16.3, 17.2]))
print(f'Minimizing theta: {theta}')
print()
```
```
theta: 0.00 | loss: 218.76
theta: 5.86 | loss: 81.21
theta: 9.38 | loss: 31.70
theta: 11.49 | loss: 13.87
theta: 12.76 | loss: 7.45
theta: 13.52 | loss: 5.14
theta: 13.98 | loss: 4.31
theta: 14.25 | loss: 4.01
theta: 14.41 | loss: 3.90
theta: 14.51 | loss: 3.86
theta: 14.57 | loss: 3.85
theta: 14.61 | loss: 3.85
theta: 14.63 | loss: 3.84
theta: 14.64 | loss: 3.84
theta: 14.65 | loss: 3.84
theta: 14.65 | loss: 3.84
theta: 14.66 | loss: 3.84
theta: 14.66 | loss: 3.84
Minimizing theta: 14.658511131035242
CPU times: user 7.88 ms, sys: 3.58 ms, total: 11.5 ms
Wall time: 8.54 ms
```
我们可以看到,梯度下降很快找到了与解析法相同的解:
```
np.mean([12.1, 12.8, 14.9, 16.3, 17.2])
```
```
14.66
```
### 最小化 Huber 损失
现在,我们可以应用梯度下降来最小化提示百分比数据集上的 Huber 损失。
Huber 损失为:
L 123; 123; 1 2 \ delta)&;\text 否则\结束案例$$
Huber 损失的梯度为:
$$\nabla_\theta l_\delta(\theta,\textbf y)=\frac 1 n \sum i=1 n\ begin cases-(y \theta)&;y i-\theta \le\delta\
```
- \delta \cdot \text{sign} (y_i - \theta) & \text{otherwise}
```
\结束案例$$
(注意,在之前的 Huber 损失定义中,我们使用变量$\alpha$来表示转换点。为了避免与梯度下降中使用的$\alpha$混淆,我们将 Huber 损失的过渡点参数替换为$\delta$。)
```
def huber_loss(theta, dataset, delta = 1):
d = np.abs(theta - dataset)
return np.mean(
np.where(d <= delta,
(theta - dataset)**2 / 2.0,
delta * (d - delta / 2.0))
)
def grad_huber_loss(theta, dataset, delta = 1):
d = np.abs(theta - dataset)
return np.mean(
np.where(d <= delta,
-(dataset - theta),
-delta * np.sign(dataset - theta))
)
```
让我们最小化 Tips 数据集上的 Huber 损失:
```
%%time
theta = minimize(huber_loss, grad_huber_loss, tips['pcttip'], progress=False)
print(f'Minimizing theta: {theta}')
print()
```
```
Minimizing theta: 15.506849531471964
CPU times: user 194 ms, sys: 4.13 ms, total: 198 ms
Wall time: 208 ms
```
### 摘要[¶](#Summary)
梯度下降给了我们一种一般的方法来最小化损失函数,当我们无法通过分析来求解$\theta$的最小值时。随着我们的模型和损失函数的复杂性增加,我们将转向梯度下降作为我们选择适合模型的工具。
- 一、数据科学的生命周期
- 二、数据生成
- 三、处理表格数据
- 四、数据清理
- 五、探索性数据分析
- 六、数据可视化
- Web 技术
- 超文本传输协议
- 处理文本
- python 字符串方法
- 正则表达式
- regex 和 python
- 关系数据库和 SQL
- 关系模型
- SQL
- SQL 连接
- 建模与估计
- 模型
- 损失函数
- 绝对损失和 Huber 损失
- 梯度下降与数值优化
- 使用程序最小化损失
- 梯度下降
- 凸性
- 随机梯度下降法
- 概率与泛化
- 随机变量
- 期望和方差
- 风险
- 线性模型
- 预测小费金额
- 用梯度下降拟合线性模型
- 多元线性回归
- 最小二乘-几何透视
- 线性回归案例研究
- 特征工程
- 沃尔玛数据集
- 预测冰淇淋评级
- 偏方差权衡
- 风险和损失最小化
- 模型偏差和方差
- 交叉验证
- 正规化
- 正则化直觉
- L2 正则化:岭回归
- L1 正则化:LASSO 回归
- 分类
- 概率回归
- Logistic 模型
- Logistic 模型的损失函数
- 使用逻辑回归
- 经验概率分布的近似
- 拟合 Logistic 模型
- 评估 Logistic 模型
- 多类分类
- 统计推断
- 假设检验和置信区间
- 置换检验
- 线性回归的自举(真系数的推断)
- 学生化自举
- P-HACKING
- 向量空间回顾
- 参考表
- Pandas
- Seaborn
- Matplotlib
- Scikit Learn