一元线性回归的预测模型为:
y ^ i = w x i + b \hat{y}_{i}=w x_{i}+b y^i=wxi+b
式中 y ^ i \hat{y}_{i} y^i是预测的结果,希望通过预测值 y ^ i \hat{y}_{i} y^i 来拟合真实值 y i y_{i} yi,所以我们定义了误差函数:
f ( w , b ) = 1 n ∑ i = 1 n ( y ^ i − y i ) 2 f(w,b)=\frac{1}{n} \sum_{i=1}^{n}\left(\hat{y}_{i}-y_{i}\right)^{2} f(w,b)=n1i=1∑n(y^i−yi)2
我们希望预测值 y ^ i \hat{y}_{i} y^i和真实值 y i y_{i} yi 误差最小。接下来我们要考虑的是 w w w、 b b b取何值时,能够使得误差函数的值最小。高中学过当函数导数为零时,能过取极值(极大值或极小值),我们把 f ( w , b ) f(w,b) f(w,b)分别对 w 、b 求导,求导的结果将为梯度下降时更新时用。
∂ f ( w , b ) ∂ w = 2 n ∑ i = 1 n x i ( w x i + b − y i ) \frac{\partial f(w, b)}{\partial w}=\frac{2}{n} \sum_{i=1}^{n} x_{i}\left(w x_{i}+b-y_{i}\right) ∂w∂f(w,b)=n2i=1∑nxi(wxi+b−yi)
∂ f ( w , b ) ∂ b = 2 n ∑ i = 1 n x i ( w x i + b − y i ) \frac{\partial f(w, b)}{\partial b}=\frac{2}{n} \ \sum_{i=1}^{n} x_{i}\left(w x_{i}+b-y_{i}\right) ∂b∂f(w,b)=n2 i=1∑nxi(wxi+b−yi)
我们令求导结果为零,就能求解出当前 w 和 b 的值:
∂ f ( w , b ) ∂ w = ∑ i = 1 n y i ( x i − x ˉ ) ∑ i = 1 n x i 2 − 1 n ( ∑ i = 1 n x i ) 2 ∂ f ( w , b ) ∂ b = 1 n ∑ i = 1 n ( y i − w x i ) \begin{aligned} \frac{\partial f(w, b)}{\partial w} &=\frac{\sum_{i=1}^{n} y_{i}\left(x_{i}-\bar{x}\right)}{\sum_{i=1}^{n} x_{i}^{2}-\frac{1}{n}\left(\sum_{i=1}^{n} x_{i}\right)^{2}} \\ \frac{\partial f(w, b)}{\partial b} &=\frac{1}{n} \sum_{i=1}^{n}\left(y_{i}-w x_{i}\right) \end{aligned} ∂w∂f(w,b)∂b∂f(w,b)=∑i=1nxi2−n1(∑i=1nxi)2∑i=1nyi(xi−xˉ)=n1i=1∑n(yi−wxi)
我们再来说梯度下降,我们给 w w w、 b b b随机初始化一个值,然后通过迭代来更新 w w w、 b b b,更新公式如下,更新后的结果是 w:和 b:
w : = w − η ∂ f ( w , b ) ∂ w b : = b − η ∂ f ( w , b ) ∂ b \begin{aligned} w &:=w-\eta \frac{\partial f(w, b)}{\partial w} \\ b &:=b-\eta \frac{\partial f(w, b)}{\partial b} \end{aligned} wb:=w−η∂w∂f(w,b):=b−η∂b∂f(w,b)
我们结合下图来说明原理。下图横坐标是 w w w,纵坐标是损失函数 f ( w , b ) f(w, b) f(w,b),我们的目的是求损失函数
f ( w , b ) f(w, b) f(w,b)的最小值,在a点时,损失函数并没有到最小值, f ( w , b ) f(w, b) f(w,b)对 w w w的斜率为负,带到 w : = w − η ∂ f ( w , b ) ∂ w w:=w-\eta \frac{\partial f(w, b)}{\partial w} w:=w−η∂w∂f(w,b), w值增大,w增大损失函数的就会减小。当在 b 点时,斜率为正,带到 w : = w − η ∂ f ( w , b ) ∂ w w:=w-\eta \frac{\partial f(w, b)}{\partial w} w:=w−η∂w∂f(w,b)中,w 的值减小,同时损失函数的值也在减小。通过不断的调整 w 和 b 的值就能找到损失函数的最小值。
我们用jupytor来运行上面的整个过程,大家也像我这样分块运行
import torch
import numpy as np
from torch.autograd import Variable
x_train = np.array([[3], [4], [5.5], [6], [6], [4],
[9.779], [6.182], [7.59], [2.167], [7.042],
[10.791], [5.3], [7.7], [3.1]], dtype=np.float32)
y_train = np.array([[1.7], [2.7], [2.09], [3.19], [1.64], [1.53],
[3.36], [2.5], [2.53], [1.221], [2.27],
[3.465], [1], [2.904], [1.3]], dtype=np.float32)
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(x_train, y_train, 'bo')
x_train = torch.from_numpy(x_train)
y_train = torch.from_numpy(y_train)
# 定义参数 w 和 b
w = Variable(torch.randn(1), requires_grad=True) # 随机初始化
b = Variable(torch.zeros(1), requires_grad=True) # 使用 0 进行初始化
x_train = Variable(x_train)
y_train = Variable(y_train)
def linear_model(x):
return x * w + b
y_ = linear_model(x_train)
经过上面的步骤我们就定义好了模型,在进行参数更新之前,我们可以先看看模型的输出结果长什么样
plt.plot(x_train.data.numpy(), y_train.data.numpy(), 'bo', label='real')
plt.plot(x_train.data.numpy(), y_.data.numpy(), 'ro', label='estimated')
plt.legend()
def get_loss(y_, y):
return torch.mean((y_ - y_train) ** 2)
loss = get_loss(y_, y_train)
# 打印一下看看 loss 的大小
print(loss)
loss.backward()
print(w.grad)
print(b.grad)
# 更新一次参数
w.data = w.data - 1e-2 * w.grad.data
b.data = b.data - 1e-2 * b.grad.data
更新完成参数之后,我们再一次看看模型输出的结果
y_ = linear_model(x_train)
plt.plot(x_train.data.numpy(), y_train.data.numpy(), 'bo', label='real')
plt.plot(x_train.data.numpy(), y_.data.numpy(), 'ro', label='estimated')
plt.legend()
从上面的例子可以看到,更新之后红色的线跑到了蓝色的线下面,没有特别好的拟合蓝色的真实值,所以我们需要在进行几次更新
for e in range(10): # 进行 10 次更新
y_ = linear_model(x_train)
loss = get_loss(y_, y_train)
w.grad.zero_() # 记得归零梯度
b.grad.zero_() # 记得归零梯度
loss.backward()
w.data = w.data - 1e-2 * w.grad.data # 更新 w
b.data = b.data - 1e-2 * b.grad.data # 更新 b
print('epoch: {}, loss: {}'.format(e, loss.item()))
y_ = linear_model(x_train)
plt.plot(x_train.data.numpy(), y_train.data.numpy(), 'bo', label='real')
plt.plot(x_train.data.numpy(), y_.data.numpy(), 'ro', label='estimated')
plt.legend()
经过 10 次更新,我们发现红色的预测结果已经比较好的拟合了蓝色的真实值。