梯度下降法(Gradient desent) 是一个一阶最优算法,通常也称为最速下降法。要使用梯度下降法找到一个函数的局部极小值,必须向函数上当前点对应梯度(或者是近似梯度)的反方向的规定步长距离点进行迭代搜索。如果相反地梯度正方向迭代进行搜索,则会接近函数的局部极大值点;这个过长被称为梯度上升法。
在模拟梯度下降算法之前,我们将做好前期准备工作,也就是先准备好离散的数据,然后利用线性回归模型去拟合这些离散的数值点,代码如下:
import numpy as np
# os 模块在运维工作中是很常用的一个模块。通过os模块调用系统命令。os模块可以跨平台使用。
import os
# 使图片可以在Jupyer notebook的行间显示
%matplotlib inline
from matplotlib import pyplot as plt
# 设置随机种子
np.random.seed(42)
# 保存图片
PROJECT_ROOT_DTR = "." # 一个点代表当前路径,两个点代表当前路径的上级
MODEL_ID = "linear_models"
# 定义一个图片的存储函数
def save_fig(fig_id, tight_layout=True) # tight_layout会自动调整子图参数,使之填充整个图像区域
path = os.path.join(PROJECT_ROOT_DTR, "images", MODEL_ID, fig_id + ".png") # 设置保存图片的路径,在当前目录下的images文件夹下的linear_models文件夹中
print("Saving figure", fig_id)
plt.savefig(path, format="png", dpi=300)
# 由于运行过可能会产生对程序结果没有影响的错误警告,我们可以将这些警告信息过滤掉
import warnings
warnings.filterwarnings(action="ignore", message="^internal gelsd")
# 生成训练数据集
X = 2 * np.random.rand(100, 1) # 特征部分
y = 4 + 3 * X + np.random.randn(100, 1)
# 画图
plt.plot(X, y, "b.")
# 设置x、y轴的标签和字号
plt.xlabel("$x_1$", fontsize=18) # $…$ 为markdown语法,数学公式编辑语言
plt.ylabel("$y$", rotation=0, fontsize=18)
# 设置x、y轴的范围
plt.axis([0, 2, 0, 15])
# 调用图片存储函数保存图片
save_fig("generated_data_plot")
# 展示图片
plt.show()
画出的图片如上图所示,此时我们进一步添加新的数据创建测试数据,利用线性回归模型拟合原始数据,并计算预测值,代码如下:
# 添加新数据
X_b = np.c_[np.ones((100, 1)), X]
# 创建测试数据
X_new = np.array([[0], [2]])
X_new = np.c_[np.ones((2, 1)), X_new]
# 从sklearn包中导入线性回归模型
from sklearn.linear_model import LinearRegression
# 创建线性回归的对象
lin_reg = LinearRegression()
# 拟合训练集
lin_reg.fit(X, y)
# 输出截距(intercept)和相关系数(coefficient)
lin_reg.intercept_, lin_reg.coef_
# 根据拟合函数预测y值
lin.reg.predict(X, new)
运行程序得出线性回归模型为: y = 2.770 x + 4.215 y = 2.770x + 4.215 y=2.770x+4.215 (保留三位小数),当 x = ( 0 , 2 ) x = (0, 2) x=(0,2)时预测的 y y y值为 ( 4.215 , 9.755 ) (4.215, 9.755) (4.215,9.755)。
1. 批量梯度下降算法(Batch Gradient Descent)
alpha = 0.1 # 设置步长
n_iterations = 1000 # 设置循环次数
m = 100 # 训练数据集中数据的个数
theta = np.random.randn(2, 1) # 随机一个初始的theta(两行一列)
# 迭代循环
for iteration in range(n_iterations): # 指定迭代次数
gradient = 2 / m * X_b.T.dot(X_b.dot(theta) - y) # 下降梯度值
theta = theta - alpha * gradient # 每循环一次更新一次theta的值
theta_path_bgd = []
# 定义批量梯度下降法的画图函数
def plot_gradient_descent(theta, alpha, theta_path=None):
m = len(X_b)
plt.plot(X, y, "b.")
n_iterations = 1000
for iteration in range(n_iterations):
if iteration < 10:
y_predict = X_new_b.dot(theta)
style = "b-"
plt.plot(X_new, y_predict, style)
gradients = 2 / m * X_b.T.dot(X_b.dot(theta) - y)
theta = theta - alpha * gradient
if theta_path is not None:
theta.path.append(theta)
plt.xlable("$x_1$", fontsize=18)
plt.axis([0, 2, 0, 15])
plt.title(r"$\alphat = {}$".format(alpha), fontsize=16)
np.random.seed(42)
theta = np.random.randn(2, 1)
plt.figure(figsize=(10, 4)) # 设置画图和画布大小
# 画布分为一行三列,三小块
plt.subplot(131) # 第一块
plot_gradient_descent(theta, alpha=0.02)
plt.ylable("$y$", rotation=0, fontsize=18) # 设置y轴的标签
plt.subplot(132) # 第二块
plot_gradient_descent(theta, alpha=0.1, theta_path= theta_path_bgd)
plt.subplot(133) # 第三块
plot_gradient_descent(theta, alpha=0.5)
save_fig("gradient_descent_plot") # 保存图片
plt.show()
由上图可知,当使用梯度下降算法求极值时,步长太小、太大都不好。如果步长太小,迭代速度太慢,而且不能很快的接近最优解(左边的图);如果步长太大,会导致迭代速度过快,甚至有可能错过最优解(右边的图),所以不长的选择应该恰当(中间的图)。
2. 随机梯度下降算法(Stochatic Gradient Descent)
theta_path_sgd = []
m = len(X_b)
np.random.seed(42)
n_epochs = 50
theta = np.random.randn(2, 1) # 随机初始化
# 设置循环
for epoch in range(n_epochs):
for i in range(m):
if epoch == 0 and i < 20:
y_predict = X_new_b.dot(theta)
style = "b-"
plt.plot(X_new, y_predict, style)
xi = X_b[i : i + 1]
yi = y[i : i + 1]
gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
alpha = 0.1
theta = theta - alpha * gradients
theta_path_sgd.append(theta)
plt.plot(X, y, "b.")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([0, 2, 0, 15])
save_fig("sgd_plot")
plt.show()
# 输出迭代结束后的thata
print(theta)
# 上述theta值与python中SGDRegressor模块计算的theta进行对比
from sklearn.linear_model import SDGRegressior
sgd_reg = SDGRegressior(max_iter=1000, tol=-np.infty, penalty=None, eta0=0.1, random_state=42)
sgd_reg.fit(X, y.ravel())
# 输出截距和相关系数
sgd_reg.intercept_, sgd_reg.coef_
程序运行结果theta=(3.763, 2.589), sgd_reg.intercept_=4.168,sgd_reg.coef_=2.726。对比可知,自己构造的函数得出来的结果和Python模块提供的函数计算的结果还是有些差异的。
3. 小批量梯度下降算法(Mini Batch Gradient Descent)
theta_path_mgd = []
n_iterations = 50
minibatch_size = 20
np.random.seed(42)
theta = np.random.randn(2, 1)
for epoch in range(n_iterations):
shuffled_indices = np.random.permutation(m)
X_b_shuffled = X_b[shuffled_indices]
y_shuffled = y[shuffled_indices]
for i in range(0, m, minibatch_size):
xi = X_b_shuffled[i : i + minibatch_size]
yi = y_shufffled[i : i + minibatch_size]
gradients = 2 / minibatch_size * xi.T.dot(xi.dot(theta) - yi)
alpha = 0.1
theta = theta - alpha * gradients
theta_path_mgd.appdend(tehta)
theta_path_bgd = np.array(theta_path_bgd)
theta_path_sgd = np.array(theta_path_sgd)
theta_path_mgd = np.array(theta_path_mgd)
plt.figure(figsize=(7, 4))
plt.plot(theta_path_bgd[:, 0], theta_path_bgd[:,1], "b-o", linewidth=1, label="BGD")
plt.plot(theta_path_sgd[:, 0], theta_path_sgd[:,1], "r-s", linewidth=2, label="SGD")
plt.plot(theta_path_mgd[:, 0], theta_path_mgd[:,1], "g-+", linewidth=3, label="MGD")
plt.legend(loc="upper left", fontsize=16)
plt.xlabel(r"$\theta_0$", fontsize=20)
plt.ylabel(r"$theta_1$", fontsize=20, rotation=0)
plt.axis([2.3, 4.5, 2.3, 3.9])
sav_fig("gradient_descent_paths_plot")
plt.show()
由上图可知,三种梯度下降的方法,MGD方法相对而言能够更好的去逼近最优解(极小值),尤其是SGD在梯度下降的过程中,没有方向感求解的速度会受到影响,而BGD虽然最快但是其求得的局部最小值可能不等于全局最小值,综上所述,小批量梯度下降的方法相对而言是最好的。