说明:本文示例所使用的数据集gongyeyuan.xlsx来源于链家网,里面含有5306条苏州市工业园区内二手房的售价与其面积信息。另:本文使用的工具主要是Python3+Jupyter Notebook。
本文的主要内容是利用三种不同的梯度下降法求解已建立的线性回归模型中的参数,并据此对三种梯度下降进行一个简单的比较。
先导入要用到的各种包:
%matplotlib notebook
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
读入数据并查看数据的相关信息:
查看data中前五条数据:
data = pd.read_excel('gongyeyuan.xlsx','Sheet2')
data.head()
查看data的各描述统计量信息:
data.describe()
绘制原始数据的散点图:
fig,axes = plt.subplots()
data.plot(kind='scatter',x='Area',y='Price',marker='o',color='k',ax=axes)
axes.set(xlabel='Area',ylabel='Price',title='Price vs. Area')
fig.savefig('p1.png')
建立一个简单的线性回归方程y=b+ax,然后以此方程为基础,希望能通过二手房的面积对其售价有一个大致的预测。
下面分别利用三种不同的梯度下降方法求解线性回归方程中的未知参数:
数据预处理:
# 向data中插入一列便于矩阵计算的辅助列:
data.insert(0,'Ones',1)
data.head()
利用批量梯度下降
cols = data.shape[1]
X = data.iloc[:,0:cols-1] # X是data中不包括索引列的前两列
y = data.iloc[:,cols-1:cols] # y是data中的最后一列
# 将X和y转化成矩阵形式:
X = np.matrix(X.values)
y = np.matrix(y.values)
# 定义代价函数:
def computeCost(X, y, theta):
inner = np.power(((X * theta.T) - y), 2)
return np.sum(inner) / (2 * len(X))
# 定义批量梯度下降函数:
def batch_gradient_descent(X,y,theta,alpha,iters):
temp = np.matrix(np.zeros(theta.shape))
parameters = int(theta.ravel().shape[1])
cost = np.zeros(iters)
for i in range(iters):
error = (X * theta.T) - y
for j in range(parameters):
term = np.multiply(error, X[:,j])
temp[0,j] = theta[0,j] - ((alpha / len(X)) * np.sum(term))
theta = temp
cost[i] = computeCost(X, y, theta)
return theta, cost
# 初始化相关参数:
theta = np.matrix(np.array([0,0]))
alpha = 0.0001
iters = 400000
# 调用批量梯度下降函数求解回归方程中的未知数theta
g, cost = batch_gradient_descent(X, y, theta, alpha, iters)
# g就是参数theta,其值为matrix([[-2.28519367, 3.3129529 ]])
绘制代价函数具体值与迭代次数的关系图像:
fig, axes = plt.subplots()
axes.plot(np.arange(iters), cost, 'r')
axes.set_xlabel('Iterations')
axes.set_ylabel('Cost')
axes.set_title('Error vs. Training Epoch')
fig.savefig('p3.png')
利用随机梯度下降
# 定义数据特征和标签的提取函数:
def get_fea_lab(data):
cols = data.shape[1]
X = data.iloc[:,0:cols-1] # X是data中的前两列(不包括索引列)
y = data.iloc[:,cols-1:cols] # y是data中的最后一列
# 将X和y都转化成矩阵的形式:
X = np.matrix(X.values)
y = np.matrix(y.values)
return X,y
# 定义代价函数:
def computeCost(data,theta,i):
X,y = get_fea_lab(data)
inner = np.power(((X*theta.T)-y),2)
return (float(inner[i]/2))
# 定义随机梯度下降函数:
def stochastic_gradient_descent(data,theta,alpha,epoch):
X0,y0 = get_fea_lab(data) # 提取X和y矩阵
temp = np.matrix(np.zeros(theta.shape))
parameters = int(theta.shape[1])
cost = np.zeros(len(X0))
avg_cost = np.zeros(epoch)
for k in range(epoch):
new_data = data.sample(frac=1) # 打乱数据
X,y = get_fea_lab(new_data) # 提取新的X和y矩阵
for i in range(len(X)):
error = X[i:i+1]*theta.T-y[i]
cost[i] = computeCost(new_data,theta,i)
for j in range(parameters):
temp[0,j] = theta[0,j] - alpha*error*X[i:i+1,j]
theta = temp
avg_cost[k] = np.average(cost)
return theta,avg_cost
# 初始化参数theta、学习率和迭代轮次:
theta = np.matrix(np.array([0,0]))
alpha = 0.000001
epoch = 30
# 调用随机梯度下降函数求解回归方程中的未知数theta
g,avg_cost = stochastic_gradient_descent(data,theta,alpha,epoch)
# g就是参数theta,其值为matrix([[-0.00486685, 3.39251634]])
绘制每轮迭代中代价函数的平均值与迭代轮次的关系图像:
fig, axes = plt.subplots()
axes.plot(np.arange(epoch), avg_cost, 'r')
axes.set_xlabel('Epoch')
axes.set_ylabel('avg_cost')
axes.set_title('avg_cost vs. Epoch')
fig.savefig('p2.png')
利用小批量梯度下降
# 定义数据特征和标签的提取函数:
def get_fea_lab(train_data):
cols = train_data.shape[1]
X = train_data.iloc[:,0:cols-1] # X取data中不包括索引列的前两列
y = train_data.iloc[:,cols-1:cols] # y取data中的最后一列
X = np.matrix(X.values)
y = np.matrix(y.values)
return X,y
# 定义小批量样本的代价函数:
def computeCost(train_data,theta,k,mb_size):
X,y = get_fea_lab(train_data)
X = X[k:k+mb_size]
y = y[k:k+mb_size]
inner = np.power(((X*theta.T)-y),2)
term = np.sum(inner)/(2*mb_size)
return term
#定义小批量梯度下降函数:
def mb_gradient_descent(train_data,theta,alpha,mb_size):
X,y = get_fea_lab(train_data)
temp = np.matrix(np.zeros(theta.shape)) # temp用于存放theta参数的值
parameters = int(theta.shape[1]) # parameter用于存放theta参数的个数
m = len(X) # m用于存放数据集中的样本个数
cost = np.zeros(int(np.floor(m/mb_size))) # cost用于存放代价函数
st_posi = list(np.arange(0,m,mb_size)) # st_posi用于存放每次小批量迭代开始的位置
new_st_posi = st_posi[:len(cost)] # 去掉最后一次小批量迭代开始的位置
k = 0
for i in new_st_posi:
cost[k] = computeCost(train_data,theta,i,mb_size)
k = k + 1
error = (X*theta.T) - y
for j in range(parameters):
t = np.multiply(error,X[:,j])
term = t[i:i + mb_size]
temp[0,j] = theta[0,j] - (alpha/mb_size)*(np.sum(term))
theta = temp
return theta, cost
# 初始化相关参数:
theta = np.matrix(np.array([0,0]))
alpha = 0.00001
mb_size = 50
# 调用小批量梯度下降函数求解回归方程中的未知数:
new_data = data.sample(frac=1) # 打乱数据,没有这一步也可以
g,cost = mb_gradient_descent(new_data,theta,alpha,mb_size)
# g就是参数theta,其值为matrix([[0.02717606, 3.3047963 ]])
绘制代价函数具体值与迭代次数的关系图像:
注:因为笔者为三种不同的梯度下降算法创建了三个不同的文件,所以上述代码中有相同的变量名和函数名。
此外,利用正规方程求出的线性回归方程中未知参数的精确值为matrix([[-2.34868067],[ 3.31348565]])。关于用正规方程求解线性回归参数,可以参考:
https://blog.csdn.net/qq_41080850/article/details/85292769
、
https://blog.csdn.net/qq_41080850/article/details/85159645
。
三种梯度下降算法的比较:
Normal Equation | BGD | MBGD | SGD | |
learning rate | —— | 0.0001 | 0.00001 | 0.000001 |
iterations | —— | 400000 | 100 | 150000 |
final theta | (-2.34868067,3.31348565) | (-2.28519367, 3.3129529) | (0.02717606, 3.3047963) | (-0.00486685, 3.39251634) |
从上表中我们可以很直观的看出,三种梯度下降算法中参数迭代更新的次数从小到大为MBGD<SGD<BGD,三种梯度下降算法最终输出结果的精确程度从小到大为SGD<MBGD<GBD。除此之外,我们还可以看到,从BGD到MBGD再到SGD,算法中设置的学习率越来越小。原因很好理解,从BGD到MBGD再到SGD,算法中用于计算代价函数的样本数逐渐减少,从而导致代价函数在某点的梯度值(实际上是偏导数)整体上是逐渐增大的,为了使代价函数值随着迭代次数增加能够收敛,算法中设置的学习率便越来越小。
总结:
对于求解基于某个数据集而建立起来的线性回归方程中的未知参数:如果该数据集比较小,同时要求最后计算出的参数结果比较精确,此时更适合选择使用批量梯度下降算法。如果该数据集非常大,可以选择采用随机梯度下降,也可以选用小批量梯度下降,视具体情况而定。总的来说,SGD的速度比BGD要快,而且能保证得到一个可以接受的计算结果。而MBGD计算结果的准确程度在BGD和SGD二者之间,但值得指出的是,在一些情况下,MBGD的速度比BGD和SGD都要快,比如本文中的示例,基于得到大致相同的计算结果,在每次迭代更新利用50个样本计算代价函数的情况下,MBGD只需迭代几十次最终就能得到想要的结果。
参考:
Andrew Ng机器学习公开课
https://blog.csdn.net/qq_41080850/article/details/85159645
https://blog.csdn.net/qq_41080850/article/details/85316527
https://blog.csdn.net/qq_41080850/article/details/85391267
PS1:虽然本文是以单变量线性回归为示例,但文中的代码稍作改动后同样适用于求解多变量线性回归中的未知参数。
PS2:本文为博主原创文章,转载请注明出处。