报告内容仅供学习参考,请独立完成作业和实验喔~
\qquad 编程实现线性回归模型,使用批梯度下降优化算法,基于MAE评估模型性能,对比不同学习率,绘制损失函数曲线图。
\qquad 使用Python读取Boston Housing Data并使用批量梯度下降方法的训练一个多元线性回归模型,随后使用生成的模型将数据进行回归预测,并根据MAE、MSE评测模型性能。同时,修改学习率,测试回归效果,绘制损失函数曲线图。
\qquad 本实验训练了一个多元线性回归模型,并对Boston Housing Data进行回归预测,通过测试,在学习率为0.001时表现较好,此时MAE为3.082038,MSE为16.581673。
(1)线性回归
\qquad 线性回归是利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法,运用十分广泛。可是表示为 f ( x ) = w 1 x 1 + w 2 x 2 + . . . + w d x d + b f(x)\ =\ w_1x_1+w_2x_2+...+w_dx_d+b f(x) = w1x1+w2x2+...+wdxd+b,也可以直接用向量形式表示为 f ( x ) = w T x + b f(x)\ =\ w^Tx+b f(x) = wTx+b。
\qquad 回归分析中,如果只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。
\qquad 在线性回归中,数据使用线性预测函数来建模,并且未知的模型参数也是通过数据来估计。这些模型被叫做线性模型。最常用的线性回归建模是给定X值的y的条件均值是X的仿射函数。不太一般的情况,线性回归模型可以是一个中位数或一些其他的给定X的条件下y的条件分布的分位数作为X的线性函数表示。像所有形式的回归分析一样,线性回归也把焦点放在给定X值的y的条件概率分布,而不是X和y的联合概率分布。
\qquad 线性回归是回归分析中第一种经过严格研究并在实际应用中广泛使用的类型。这是因为线性依赖于其未知参数的模型比非线性依赖于其未知参数的模型更容易拟合,而且产生的估计的统计特性也更容易确定。
\qquad 线性回归模型经常用最小二乘逼近来拟合,两者紧密相连,但不能划等号。
(2)多元线性回归
\qquad 在回归分析中,如果有两个或两个以上的自变量,就称为多元回归。事实上,由于一种现象常常是与多个因素相联系的,由多个自变量的最优组合共同来预测或估计因变量,比只用一个自变量进行预测或估计更有效,更符合实际。因此多元线性回归比一元线性回归的实用意义更大。
(3)梯度下降法
\qquad 梯度下降法(Gradient descent)是一个一阶最优化算法,通常也称为最速下降法。梯度下降法是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以)。在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。在求解损失函数的最小值时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数和模型参数值。反过来,如果我们需要求解损失函数的最大值,这时就需要用梯度上升法来迭代了。
\qquad 常用的梯度下降法分为三种形式,分别为随机梯度下降法、批量梯度下降法和小批量梯度下降法。其中随机梯度下降法训练速度快,但准确率较差;批量梯度下降法准确率高,但训练速度较慢;小批量梯度下降法结合在上述两种方法的基础上折中,保证训练准确度的同时,可以取得较准确的结果。
\qquad 使用批量梯度下降法求解时,有以下两个公式:
\qquad 线性回归函数:
h θ ( x ( i ) ) = θ 1 x ( i ) + θ 0 h_\theta(x^{(i)})=\theta_1x^{(i)}+\theta_0 hθ(x(i))=θ1x(i)+θ0
\qquad 损失函数:
J ( θ 0 , θ 1 ) = 1 2 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) 2 J(\theta_0,\theta_1)=\frac{1}{2m}\sum_{i=1}^{m}{(h_\theta(x^{(i)})-y^{(i)})}^2 J(θ0,θ1)=2m1i=1∑m(hθ(x(i))−y(i))2
\qquad 每次迭代时,对损失函数求偏导,
\qquad 并同步更新 θ \theta θ值:
θ \qquad\theta θ值会随着迭代不断缩小,当\theta值不变时,意味着此时导数为0,即到达了最小值点,完成迭代。
硬件环境:Intel® Core™ i5-10300H CPU + 16G RAM
软件环境:Windows 11 家庭中文版 + Python 3.8
(1)预处理数据
\qquad 由于数据较多且量纲不同,在使用数据前需要对数据进行归一化和标准化处理,并将数据集维度与参数维度统一,以方便后续计算。
x = X
x = ((x[:,0] - x[:,0].min()) / (x[:,0].max() - x[:,0].min())).reshape(-1,1)
for i in range(1,13):
x1 = (X[:,i] - X[:,i].min()) / (X[:,i].max() - X[:,i].min())
x = np.hstack((x,x1.reshape(-1,1)))
x = np.hstack((x, np.ones((x.shape[0], 1))))
y = Y
y = y.reshape(-1, 1)
\qquad 同时,按照实验要求,需要将数据划分为训练集与测试集,其中训练集为前406条数据,测试集为后100条数据。
X_train = x[:406]
Y_train = y[:406]
X_test = x[-100:]
Y_test = y[-100:]
x = X_train
y = Y_train
(2)实现线性回归函数h(x)与代价函数J(x)
\qquad 根据上一部分中的分析,批量梯度下降法求线性回归需要使用线性回归函数h(x)和代价函数J(x),这两个函数在 \qquad Python的具体实现如下:
def h(theta, X):
return np.dot(X, theta)
def J(theta, X, Y):
m = len(X)
return np.sum(np.dot((h(theta, X) - Y).T, (h(theta, X) - Y)) / (2 * m))
(3)实现批量梯度下降方法
\qquad 具体实现时,由于我们使用的是批量梯度下降法,需要对全部特征系数进行处理,因此就需要获取具体有多少个特征系数,这里的特征系数就是特征值的数量加上一个常数项。
\qquad 此外,在实现代码时,为了控制计算次数不会无休止的计算下去,引入了两个控制条件:maxloop和epsilon,分别用于控制最大循环次数和每次循环的精度。最大循环次数相对好理解,用于跳出死循环或接近死循环的过程。引入每次循环的精度是为了阻止系数在最小值附近一直纠结但很难再有大的精进,尽可能保证速度和精度。
\qquad 第三点,再实现代码时,为了绘制代价函数的变化值,将每次迭代时得到的代价函数值记录在数组中,方便后期使用。
\qquad 具体代码如下:
def bgd(alpha, maxloop, epsilon, X, Y):
m, n = X.shape
theta = np.zeros((n, 1)) #确定特征系数,特征值+1个
count = 0 #记录迭代次数
converged = False
error = np.inf
errors = [J(theta, X, Y), ] #记录全部损失函数值
thetas = {}
for i in range(n):#赋初值为0
thetas[i] = [theta[i, 0], ]
while count <= maxloop:
if(converged):
break
count = count + 1
for j in range(n):
deriv = np.dot(X[:,j].T, (h(theta, X) - Y)).sum() / m
thetas[j].append(theta[j, 0] - alpha*deriv)
for j in range(n):
theta[j,0] = thetas[j][-1]
error = J(theta, X, Y)
errors.append(error)
if(abs(errors[-1] - errors[-2]) < epsilon):
#精度处理,防止振荡
converged = True
return theta, errors, thetas, count
\qquad 在代码中具体具体调用方法如下:
theata=0.1 #学习率
errors=[] #记录损失函数值
thetas=[] #参数值
maxloop=10000 #最大循环次数
epsilon=0.00001 #精度
w,errors,thetas,k=bgd(theata,maxloop,epsilon,x,y)
(4)使用测试集进行测试
\qquad 利用得到的参数对测试集进行计算,并绘图,代码及图片如下。图中蓝色为预测值,红色为真实值。
r_y = h(w,X_test) #计算测试集结果
plt.figure(figsize=(12, 6)) #绘图
plt.plot(Y_test, color="red", marker="o", label="实际")
plt.plot(r_y, color="blue", marker=".", label="预测")
plt.xlabel("Sample", fontsize=14)
plt.ylabel("y", fontsize=14)
plt.legend()
plt.show()
\qquad 利用MSE和MAE两个指标检验模型效果,计算结果如下:
errorsFig = plt.figure()
ax = errorsFig.add_subplot(111)
ax.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.2e'))
ax.plot(range(len(errors)), errors)
ax.set_xlabel(u'迭代次数')
ax.set_ylabel(u'损失函数')
(6)修改学习率,测试效果并进行对比
\qquad 1)当学习率为0.5时,MSE= 31.776114861448185,MAE= 4.666108862293138,迭代次数1870次,效果如下图:
\qquad 2)当学习率为0.1时,MSE= 29.75094902909577,MAE= 4.509225551121416,迭代次数6928次,效果如下图:
\qquad 3)当学习率为0.05时,MSE= 26.881488537054878,MAE= 4.282828665292168,迭代次数超过10000次,效果如下图:
\qquad 4)当学习率为0.01时,MSE= 21.285632207400113,MAE= 3.411732460338943,迭代次数超过10000次,效果如下图:
\qquad 5)当学习率为0.001时,MSE= 16.581673176413943,MAE= 3.082037992494502,迭代次数超过10000次,效果如下图:
\qquad 实验与理论内容的主要区别在于迭代的截止条件上。
\qquad 在梯度下降算法中,迭代的截止条件应该为导数为0的情况,也就是函数达到最小值点的时候停止迭代。但实际的代码实现中,直接迭代到导数为0的情况实际上是很难发生的,或者由于学习率设置过大时,无法迭代到这个真正的最低点。
\qquad 因此,结合准确性和效率,我们还添加了另外两个控制条件:maxloop和epsilon,分别用于控制最大循环次数和每次循环的精度。最大循环次数相对好理解,用于跳出死循环或接近死循环的过程。引入每次循环的精度是为了阻止系数在最小值附近一直纠结但很难再有大的精进,尽可能保证速度和精度。
\qquad 实验数据为来自UCI的波士顿房价数据集Boston Housing Data。
\qquad 数据集共包含506组数据。每组数据包括14个参数和1个分类标签,分类标签为当前房价,14个参数分别为:
- CRIM - 城镇人均犯罪率
- ZN - 超过25000平方英尺用地划为居住用地的百分比
- INDUS - 城镇非零售商业用地比例
- CHAS - 是否临近Charles River(临近为1,不临近为0)
- NOX - 氮氧化物浓度
- RM - 住宅平均房间数
- AGE - 1940年前建造的房屋比例
- DIS - 到5个波士顿就业服务中心的加权距离
- RAD - 公路可达性指数
- TAX - 每10000美元全值物业税税率
- PTRATIO - 城镇师生比例
- B - 1000(Bk - 0.63)^2,Bk为城镇黑人占比
- LSTAT - 下层经济阶级比例
- MEDV - 业主自住房屋中值
\qquad 为方便使用,也可以直接使用sklearn.datasets库中的load_boston()方法直接加载该数据集。
\qquad 评价指标选择为均方误差MSE和平均绝对误差MAE,计算公式如下:
\qquad 本次实验中,具体代码实现如下:
mse = 0
for i in range(0,len(Y_test)):
mse = mse + (Y_test[i]-r_y[i])*(Y_test[i]-r_y[i])
mse = mse / len(Y_test)
print("MSE:"+str(mse[0]))
mae = 0
for i in range(0,len(Y_test)):
mae = mae + abs(Y_test[i]-r_y[i])
mae = mae / len(Y_test)
print("MAE:"+str(mae[0]))
\qquad 当学习率过大时,算法可能会直接跳过最小值,从而无法到达截至点;当学习率过小时,又会导致训练时间过长或者与其他参数配合不好,出现异常结果。
\qquad 因此,结合上述测试结果可知,在我们当前参数下,学习率为0.001时可以获得更好的训练效果。
\qquad 本次实验的主要内容为使用批量梯度下降法训练线性回归模型,实现对波士顿房价数据集的多元回归预测,并通过修改学习率查看学习率对模型训练的影响,进一步理解线性回归。
\qquad 在本次实验中,可以通过查阅资料解决实验中产生的问题,并成功完成全部实验任务。
[1] 周志华. 机器学习[M]. 清华大学出版社, 2016.
[2] API Reference — scikit-learn 1.1.1 documentation [EB/OL]. [2022-5-10]. https://scikit-learn.org/stable/modules/classes.html.
[3] 使用梯度下降优化方法,编程实现多元线性回归[EB/OL]. [2022-5-10]. https://blog.csdn.net/wjz0626/article/details/124108824.
[4] [机器学习]—用python写一个多元线性回归[EB/OL]. [2022-5-10]. https://blog.csdn.net/kepengs/article/details/84844957.
[5] 详解批量梯度下降法(BGD)、随机梯度下降法(SGD)和小批量梯度下降法(MBGD)[EB/OL]. [2022-5-10]. https://blog.csdn.net/Serendi_patty/article/details/106206787.
%matplotlib inline
from sklearn.datasets import load_boston #引入波士顿房价数据集
import numpy as np
from matplotlib import pyplot as plt, cm
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
#代价函数j(x)
def J(theta, X, Y):
m = len(X)
return np.sum(np.dot((h(theta, X) - Y).T, (h(theta, X) - Y)) / (2 * m))
#h(x)
def h(theta, X):
return np.dot(X, theta)
#批量梯度下降
def bgd(alpha, maxloop, epsilon, X, Y):
m, n = X.shape
theta = np.zeros((n, 1)) # 因为有n个特征系数,一元线性回归的时候是2
#print(m,n)
count = 0
converged = False
error = np.inf
errors = [J(theta, X, Y), ] # 记录所有的代价函数输出值用来画图
thetas = {}
for i in range(n):#赋初值为0
thetas[i] = [theta[i, 0], ]
#print(thetas)
while count <= maxloop:
if(converged):
break
count = count + 1
# 所有的梯度一起算
for j in range(n):
deriv = np.dot(X[:,j].T, (h(theta, X) - Y)).sum() / m
thetas[j].append(theta[j, 0] - alpha*deriv)
for j in range(n):
theta[j,0] = thetas[j][-1]
error = J(theta, X, Y)
errors.append(error)
if(abs(errors[-1] - errors[-2]) < epsilon):
converged = True
return theta, errors, thetas, count
#数据采用网络上的数据
#需要归一化处理
X, Y = load_boston(return_X_y=True)
x = X
#进行归一化处理
x = ((x[:,0] - x[:,0].min()) / (x[:,0].max() - x[:,0].min())).reshape(-1,1)
for i in range(1,13):
x1 = (X[:,i] - X[:,i].min()) / (X[:,i].max() - X[:,i].min())
x = np.hstack((x,x1.reshape(-1,1)))
x = np.hstack((x, np.ones((x.shape[0], 1))))
y = Y
y = y.reshape(-1, 1)
X_train = x[:406]
Y_train = y[:406]
X_test = x[-100:]
Y_test = y[-100:]
x = X_train
y = Y_train
theata=0.01
errors=[]
thetas=[]
maxloop=10000
epsilon=0.00001
w,errors,thetas,k=bgd(theata,maxloop,epsilon,x,y)
print("参数",w)
print("迭代次数",k)
r_y = h(w,X_test)
plt.figure(figsize=(12, 6))
plt.plot(Y_test, color="red", marker="o", label="实际")
plt.plot(r_y, color="blue", marker=".", label="预测")
plt.xlabel("Sample", fontsize=14)
plt.ylabel("y", fontsize=14)
plt.legend()
plt.show()
mse = 0
for i in range(0,len(Y_test)):
mse = mse + (Y_test[i]-r_y[i])*(Y_test[i]-r_y[i])
mse = mse / len(Y_test)
print("MSE:"+str(mse[0]))
mae = 0
for i in range(0,len(Y_test)):
mae = mae + abs(Y_test[i]-r_y[i])
mae = mae / len(Y_test)
print("MAE:"+str(mae[0]))
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm # 彩色映射函数库 color map
import matplotlib.ticker as mtick # 设置坐标轴
errorsFig = plt.figure()
ax = errorsFig.add_subplot(111)
ax.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.2e'))
ax.plot(range(len(errors)), errors)
ax.set_xlabel(u'迭代次数')
ax.set_ylabel(u'损失函数')