目录
线性回归模型
对于线性函数
f_{w_0, w_1}(x) = w_1x + w_0 = y
确定w_0
和w_1
,使得f_{w_0, w_1}(x)
更好地拟合已有的数据。
损失函数(Loss Function)
损失函数可以采用和方差或均方差的形式,教授在课件中使用的是和方差形式,在代码中用了均方差。
和方差:
Loss(f_w) = \sum\limits_{i = 1}^N (y_i - f_w(x_i))^2
均方差:
Loss(f_w) = \frac{1}{n} \sum\limits_{i = 1}^N (y_i - f_w(x_i))^2
另外通常引入常系数\frac{1}{2}
来抵消求导之后的系数2,方便计算。教授在课件中使用了该常系数但代码中没有,导致代码中带有一项2.0/n。
带有系数\frac{1}{2}
的和方差损失函数:
Loss(f_w) = \frac{1}{2} \sum\limits_{i = 1}^N (y_i - f_w(x_i))^2
带有系数\frac{1}{2}
的均方差损失函数:
Loss(f_w) = \frac{1}{2n} \sum\limits_{i = 1}^N (y_i - f_w(x_i))^2
添加系数或使用平均值对参数的优化没有影响。
损失函数的梯度
梯度(Gradient)
\nabla f_{w_0, w_1}(x) = \begin{pmatrix}\frac{\partial \mathcal{f}}{\partial w_0}\\ \frac{\partial \mathcal{f}}{\partial w_1}\end{pmatrix}
梯度是损失函数关于需优化的参数的偏导数构成的向量。
梯度的方向指向当前位置斜率最大的方向。
Für jede Stelle (x,y) zeigt der Vektor in die Richtung des steilsten Anstiegs von f.
当梯度为零向量时,当前位置是f的局部极小值。
Ist ∇f an einer Stelle (x,y) der Nullvektor, so ist (x,y) ein Optimum von f.
损失函数的梯度计算
以课件中的带系数和方差损失函数为例:
\frac{\partial Loss(f_w)}{\partial w_1} = -\sum_i (y_i - (w_1x_i + w_0)) * x_i
\frac{\partial Loss(f_w)}{\partial w_0} = -\sum_i (y_i - (w_1x_i + w_0))
两种优化方法
闭式解(Closed-form)
直接令\nabla f = 0
,求w_0
和w_1
。
缺点:只适合简单的情况,对于线性函数的求解比较容易,而对于许多非线性方程难以求解,此时使用梯度下降法。
梯度下降(Gradient Descent)
更新公式(Update Form)
\theta_j := \theta_j - \alpha \frac{\partial}{\partial \theta_j} J(\theta_0, \theta_1)
其中:
\alpha
是学习率(learning rate)\theta_0, \theta_1
是需要优化的参数,可以有很多个\frac{\partial}{\partial \theta_j} J(\theta_0, \theta_1)
是损失函数关于当前参数的偏导数
更新公式的意义是,以-\Delta
向梯度为零的位置逐步靠近,步长为\alpha
。
因此,w_0
和w_1
的更新公式可以写成:
w_1 := w_1 + \alpha \sum_i (y_i - f_w(x_i)) * x_i
w_0 := w_0 + \alpha \sum_i (y_i - f_w(x_i))
教授给出的课件中似乎有些问题,在对损失函数计算偏导数之后使用加号是没有问题的,但是计算之前以\frac{\partial f_w(x)}{\partial w}
书写梯度时应该使用减号。
更新公式不是推导出来,而是定义出来的。至于为什么使用减号,我参考了知乎上的一个回答:梯度下降的参数更新公式是如何确定的? – 老董的回答。
递归下降法程序
实际程序中需要注意两个参数的设置:\alpha
和迭代次数(Iteration/Epochen)。
当\alpha
过小时,迭代次数需要很大才能找到局部最优解;
当\alpha
过大时,一次迭代可能就越过了最优解,无法收敛。
import math
import random
import matplotlib.pyplot as plt
num = 50
sizes = random.sample(range(50, 200), num)
prices = [size*(1000+offset) for (size,offset) in zip(sizes, random.sample(range(1000, 3000), num))]
def f(x):
return w1*x + w0
def loss():
return (1.0/n) * sum([math.pow(y-f(x), 2) for (x,y) in zip(sizes, prices)])
n = len(sizes)
w0,w1=0.0,0.0
epochs = 10
alpha = 0.00001
x1,x2 = min(sizes), max(sizes)
l=[]
for i in range(epochs):
plt.plot([x1,x2], [f(x1),f(x2)], color='red') # regression line
l.append((w0,w1,int(loss()/1000000000)))
w0 += alpha*(2.0/n)*sum([ (y-f(x)) for (x,y) in zip(sizes, prices)])
w1 += alpha*(2.0/n)*sum([x*(y-f(x)) for (x,y) in zip(sizes, prices)])
# 这里系数是2/n,说明用的是均方差,且没有添加系数1/2
print("f(x)= %.2f x + %.2f"%(w1,w0))
plt.scatter(sizes, prices)
plt.ylabel("Flat Price in EUR")
plt.xlabel("Flat Size in $m^2$")
plt.show()
plt.plot([x1,x2], [f(x1),f(x2)], color='red') # regression line
plt.scatter(sizes, prices)
plt.ylabel("Flat Price in EUR")
plt.xlabel("Flat Size in $m^2$")
plt.show()
l2=[]
for w0 in range(0,50,10):
for w1 in range(1000,5000,100):
l2.append([w0,w1,int(loss()/1000000000)])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
w0s,w1s,losss = zip(*l)
ax.scatter(w1s, w0s, losss, label="Gradient descent", marker="o")
w0s,w1s,losss = zip(*l2)
ax.scatter(w1s, w0s, losss, marker=".")
ax.set_xlabel('$w_0$')
ax.set_ylabel('$w_1$')
ax.set_zlabel('Loss in $10^9$', rotation=90)
ax.legend()
plt.tight_layout()
plt.show()
参考内容
Lecture 2 – Linear Regression and Gradient Descent | Stanford CS229: Machine Learning (Autumn 2018)