介绍完梯度下降算法,接下来通过具体的编程实现简单线性回归(对于只有一个样本特征的回归问题,我们称之为简单线性回归)。为了更好的介绍简单简单线性回归案例,将实现简单线性回归分成四个关键步骤:
- 准备数据
- 计算误差
- 计算梯度
- 梯度更新
准备数据
本小节准备了一个名为data.csv
的外部数据文件,文件内容格式如下所示:
...
61.530358025636438,62.562382297945803
47.475639634786098,71.546632233567777
...
以逗号分隔的.csv
文件非常常见,通常使用Numpy.genfromtxt
函数进行读取。
import numpy as np
import matplotlib.pyplot as plt
points = np.genfromtxt("./data.csv", delimiter = ",")
plt.scatter(points[:, 0], points[:, 1])
plt.show()
「通过数据集的分布图可以很清楚的看出数据整体呈现一定的线性关系,」 针对这样的数据集使用线性回归模型非常合适。
计算误差
使用梯度下降算法之前需要确定一个损失函数,这里使用简单的均方误差损失函数。均方误差 MSE(Mean Squared Error)的公式如下所示:
其中 m 为样本个数,y^{(i)} 为第 i 个样本的真实值(即 points[:, 1]
),hat y^{(i)} 为线性模型预测的第 i 个样本的预测值。实现如下:
def mse(b, w, points):
"""
根据当前w, b值计算均方误差损失值
"""
totalError = 0
# 没有使用向量化的方式,因此需要循环迭代所有样本点
for i in range(0, len(points)):
x = points[i, 0] # 第i个样本点的输入x^{(i)}
y = points[i, 1] # 第i个样本点的输出y^{(i)}
# 计算差的平方,并累加
totalError = (y - (w * x b)) ** 2
# 将累加的误差求平均,得到均方误差
mse_loss = totalError / float(len(points))
return mse_loss
需要注意此时计算的均方误差损失值是根据传入的 w, b 计算出来的。
计算梯度
梯度下降算法的计算公式如下所示:
其中 theta 为待优化参数,就本例的线性回归而言,theta 为 w, b。梯度下降算法更新迭代的是待优化参数 w, b 的值,因此我们需要计算每一个样本点上损失函数对待优化参数 w, b 的梯度,即: frac{partial L}{partial w}, frac{partial L}{partial b}。由于没有考虑向量化,因此计算梯度时将均方差损失函数展开:
- 计算 frac{partial L}{partial w} 的梯度:
由于:
因此:
- 计算 frac{partial L}{partial b} 的梯度:
计算 frac{partial L}{partial w}, frac{partial L}{partial b} 的梯度非常简单(上面的式子展开有点过于啰嗦了...)。实现如下:
代码语言:txt复制def step_gradient(b_current, w_current, points, lr):
"""
计算损失函数在所有样本点上的梯度
"""
b_gradient = 0
w_gradient = 0
M = float(len(points)) # 总的样本数
for i in range(0, len(points)):
x = points[i, 0]
y = points[i, 1]
# 损失函数对b的梯度:grad_b = 2(wx b - y)
b_gradient = (2 / M) * ((w_current * x b_current) - y)
# 损失函数对w的梯度:grad_w = 2 * x * (wx b - y)
w_gradient = (2 / M) * x * ((w_current * x b_current) - y)
return [b_gradient, w_gradient]
梯度更新
计算出 w, b 的梯度后,我们可以根据梯度下降算法来更新 w 和 b 的值。梯度下降算法是一个迭代算法,我们把对数据集中所有样本点都训练一次称为一个 Epoch,通常情况下需要迭代很多次才能达到最优的结果。循环迭代的次数也是一个超参数,需要在训练模型的时候人为的进行指定。此时指定循环迭代 num_iterations 次,我们会把数据集中的所有样本点都训练 num_iterations 个 Epoch。实现如下:
代码语言:txt复制def gradient_descent(points, starting_b, starting_w, lr, num_iterations):
"""
梯度下降算法更新w,b的值,执行num_iterations次更新,返回更新最后一次的w,b
"""
b = starting_b # b的初始值
w = starting_w # w的初始值
# 执行num_iterations个Epoch
for step in range(num_iterations):
# 获取w,b的梯度
b_gradient, w_gradient = step_gradient(b, w, np.array(points), lr)
# 梯度下降算法更新w,b
b = b - (lr * b_gradient)
w = w - (lr * w_gradient)
# 计算当前w,b的损失函数,用于监控训练进度
loss = mse(b, w, points)
if step % 50 == 0: # 打印损失和实时的w,b的值
print(f"iteration:{step}, loss:{loss}, w:{w}, b:{b}")
return [b, w] # 返回更新最后一次的w,b
有了核心步骤和代码,接下来就可以来训练我们的线性回归模型了。主函数的实现如下:
代码语言:txt复制def run():
"""
主训练函数
"""
learning_rate = 0.0001
initial_b = 0
initial_w = 0
num_iterations = 1000
print("Starting gradient descent at b = {0}, w = {1}, error = {2}"
.format(initial_b, initial_w,mse(initial_b, initial_w, points)))
print("Running...")
[b, w] = gradient_descent(points, initial_b, initial_w, learning_rate, num_iterations)
print("After {0} iterations b = {1}, w = {2}, error = {3}".
format(num_iterations, b, w,mse(b, w, points)))
if __name__ == '__main__':
run()
代码语言:txt复制Starting gradient descent at b = 0, w = 0, error = 5565.107834483211
Running...
iteration:0, loss:1484.586557408649, w:0.7370702973591052, b:0.014547010110737297
iteration:50, loss:112.64882489409928, w:1.4788605608606418,
...
After 1000 iterations b = 0.08893651993741346, w = 1.4777440851894448, error = 112.61481011613473
迭代 1000 次之后,w 和 b 的值基本稳定了。接下来将预测的模型也绘制到数据集分布图中。
代码语言:txt复制x = points[:,0]
w = 1.4777440851894448
b = 0.08893651993741346
y = w * x b
plt.scatter(points[:,0],points[:,1])
plt.plot(x,y,c = 'r')
References: 1. 龙良曲深度学习与PyTorch入门实战:https://study.163.com/course/introduction/1208894818.htm
原文地址:https://mp.weixin.qq.com/s/UUv2rUVmHsDtSurGASSGpQ