1.什么是线性回归?
线性回归是试图在一堆数据中训练得到自变量x和因变量y中一组线性关系,如。例如把人脚底板长度作为自变量,身高作为因变量,那么在这两种数据之间就可以做一个简单线性回归,可以得到脚底板长度和身高的关系式。
维基百科:线性回归
在统计学中,线性回归是利用称为线性回归方程的最小二乘函数对一个或多个自变量和因变量之间关系进行建模的一种回归分析。周志华:机器学习
基于均方误差最小化来进行模型求解的方法称为“最小二乘法”,线性回归中最小二乘法就是试图找到一条直线,使所有样本到直线上的欧氏距离之和最小。
2. 线性回归的目标函数
要想求得这组线性关系,即求得相应的回归系数的值。那么先讲解一下线性回归的目标函数。
假设一堆数据中因变量为y,自变量为,对其进行线性回归,求得结果会如下所示:
其中w0是x0=1的系数,表示为全局偏移量,在平面直线中的具体意义为截距,也作为自变量的一个维度,其中w就是我们需要求得的。那么怎么去求得这个w呢?假设一共有m个样本,对于第i个数据,有
对于一个样本来说,为一个样本存在的误差。显然求得需要的w的方法,就是找出使误差最小的w,然而使用这个误差的简单累加将使得正差值和复差值抵消,所以我们采用均方误差,那么对于全部的m个样本来说,总误差为
均方误差是回归任务中最常用的性能度量,有非常好的几何意义,它对应了常用的欧几里得距离或简称“欧氏距离”
显然我们需要这个总误差的值为最小,所以取目标函数为
求这个目标函数最小值时的w,就是我们需要的w了。
3. 求目标函数得回归系数
3.1 梯度下降算法
对于求最小值的问题,我们能想到的第一个应该是梯度下降算法。在实验过程中发现,在对上述的目标函数进行梯度下降时,需要把步长设置的很小很小才行,为此我们先将目标函数改为如下形式
之后我们使用梯度下降算法来求这个目标函数的最小值。该目标函数的梯度为
而
所以使用梯度算法迭代的公式为
然而对于迭代过程中使用的训练集数量不同,会有不同细分的梯度下降算法,但是都是梯度下降算法,核心没有变。
-
批量梯度下降算法
使用所有的数据集,存在的问题是每次下降都要对整个数据集进行遍历运算,当数据集很大时,他的计算机会出现溢出,或者计算时间非常长。然而随机梯度下降就可以解决这些问题。
-
随机梯度下降算法
每次迭代使用的是一组数据。然而随机梯度下降算法由于每次只是用了一组数据,因为他受噪声/离群点/异常值的影响非常大,由此有了一种折中的方法,那就是小批量梯度下降。
-
小批量梯度下降算法
每次迭代时使用一批数据,这批数据可以自行选择也可以随机产生,大小也可自由定,越大就越接近批量梯度下降,越小就越接近随机梯度下降。
下面是上述三种算法的Python代码实现,可以参考一下:
'''
批量梯度下降算法
'''
def BGD(xArr, yArr, alpha, theta, times):
m = len(xArr)
for k in range(times):
# 一次迭代
for j in range(len(theta)):
sum = 0.0
# 进行求和
for xi, yi in zip(xArr, yArr):
sum += ((theta * xi).sum() - yi) * xi[j]
# 更新
theta[j] = theta[j] - (alpha * sum) / m # 不除以m的话,得把步长设置的很小很小
return theta
'''
随机梯度下降算法
'''
def SGD(xArr, yArr, alpha, theta, times):
m = len(xArr)
for k in range(times * 10):
t = random.randint(0, m - 1)
# 一次迭代
for j in range(len(theta)):
sum = ((theta * xArr[t]).sum() - yArr[t]) * xArr[t][j]
# 更新
theta[j] = theta[j] - (alpha * sum) / m
return theta
'''
小批量梯度下降算法
'''
def MBGD(xArr, yArr, alpha, theta, times):
m = len(xArr)
n = 5 # 小批量数据的数量
for k in range(times):
# 随机产生小批量的数据
train_set = []
for t in range(n):
train_set.append(random.randint(0, m - 1))
# 一次迭代
for j in range(len(theta)):
sum = 0.0
for i in train_set:
sum += ((theta * xArr[i]).sum() - yArr[i]) * xArr[i][j]
# 更新
theta[j] = theta[j] - (alpha * sum) / m
return theta
3.2 矩阵求导
针对这个最小值的求法我们还可以使用的矩阵的方法,上述的目标函数
可以用矩阵表示为,
其中X表示m个n维样本,每一行表示一个样本,即X为mxn阶矩阵。之后求向量w的导数
令上式为0,求得
这个w就是我们需要的回归系数。但是需要注意的是,在求该式的时候涉及到了矩阵逆的计算,因此这个方程只能在逆矩阵存在的时候适用。
矩阵求导方式的核心代码实现
'''
矩阵的方法求参数值
'''
def mat(xArr, yArr):
xMat = np.mat(xArr) # x矩阵
xMatT = xMat.T # x的转置矩阵
yMat = np.mat(yArr).T # y矩阵
xTx = xMatT * xMat
if (np.linalg.det(xTx) != 0):
theta = xTx.I * xMatT * yMat
return theta
return None
4. 简单线性回归的Python实现
下面我们模拟出分布在某个函数周围的数据,然后使用上述四种方法进行线性回归,全部代码如下
import numpy as np
import math
import random
import matplotlib.pyplot as plt
'''
批量梯度下降算法
'''
def BGD(xArr, yArr, alpha, theta, times):
m = len(xArr)
for k in range(times):
# 一次迭代
for j in range(len(theta)):
sum = 0.0
# 进行求和
for xi, yi in zip(xArr, yArr):
sum += ((theta * xi).sum() - yi) * xi[j]
# 更新
theta[j] = theta[j] - (alpha * sum) / m # 不除以m的话,得把步长设置的很小很小
return theta
'''
随机梯度下降算法
'''
def SGD(xArr, yArr, alpha, theta, times):
m = len(xArr)
for k in range(times * 10):
t = random.randint(0, m - 1)
# 一次迭代
for j in range(len(theta)):
sum = ((theta * xArr[t]).sum() - yArr[t]) * xArr[t][j]
# 更新
theta[j] = theta[j] - (alpha * sum) / m
return theta
'''
小批量梯度下降算法
'''
def MBGD(xArr, yArr, alpha, theta, times):
m = len(xArr)
n = 5 # 小批量数据的数量
for k in range(times):
# 随机产生小批量的数据
trainSet = []
for t in range(n):
trainSet.append(random.randint(0, m - 1))
# 一次迭代
for j in range(len(theta)):
sum = 0.0
for i in trainSet:
sum += ((theta * xArr[i]).sum() - yArr[i]) * xArr[i][j]
# 更新
theta[j] = theta[j] - (alpha * sum) / m
return theta
'''
矩阵的方法求参数值
'''
def mat(xArr, yArr):
xMat = np.mat(xArr) # x矩阵
xMatT = xMat.T # x的转置矩阵
yMat = np.mat(yArr).T # y矩阵
xTx = xMatT * xMat
if (np.linalg.det(xTx) != 0):
theta = xTx.I * xMatT * yMat
return theta
return None
'''
画图
'''
def draw_line(xArr, theta):
y = []
for i in xArr:
y.append((theta * i).sum())
plt.plot(xArr[:, 1], np.array(y), 'r', lw=2)
def main():
x = np.arange(0, 25, 0.5)
xArr = []
yArr = []
# 产生用于训练的数据
for xi in x:
xArr.append([1, xi])
yArr.append(0.5 * xi + 3 + random.uniform(0, 1) * math.sin(xi))
xArr = np.array(xArr)
yArr = np.array(yArr)
alpha = 0.005 # 步长
theta = np.array([0.0, 0.0]) # 回归系数
times = 10000 # 迭代次数
theta_re = BGD(xArr, yArr, alpha, theta, times)
print("批量梯度下降算法下求的theta值 ", theta_re)
print("批量梯度下降算法求得相关系数为\n", # 计算相关系数
np.corrcoef(yArr, np.array(np.mat(xArr) * np.mat(theta_re).T)[:,0]))
draw_line(xArr, theta_re)
theta_re = SGD(xArr, yArr, alpha, theta, times)
print("随机梯度下降算法下求的theta值", theta_re)
print("随机梯度下降算法求得相关系数为\n", # 计算相关系数
np.corrcoef(yArr, np.array(np.mat(xArr) * np.mat(theta_re).T)[:, 0]))
draw_line(xArr, theta_re)
theta_re = MBGD(xArr, yArr, alpha, theta, times)
print("小批量梯度下降算法下求的theta值", theta_re)
print("小批量梯度下降算法求得相关系数为\n",
np.corrcoef(yArr, np.array(np.mat(xArr) * np.mat(theta_re).T)[:, 0]))
draw_line(xArr, theta_re)
theta_re = mat(xArr, yArr)
print("矩阵求逆法求得的theta值", np.array(theta_re)[:, 0])
print("小批量梯度下降算法求得相关系数为\n",
np.corrcoef(yArr, np.array(np.mat(xArr) * theta_re)[:, 0]))
draw_line(xArr, np.array(theta_re)[:, 0])
plt.scatter(x, yArr, 60)
plt.show()
if __name__ == "__main__":
main()
上述的梯度下降算法中,没有加上对函数值变换很小时的判断
控制台的输出结果如下所示
批量梯度下降算法下求的theta值 [3.15890008 0.48804966]
批量梯度下降算法求得相关系数为
[[1. 0.99426138]
[0.99426138 1. ]]
随机梯度下降算法下求的theta值 [3.15560184 0.49059031]
随机梯度下降算法求得相关系数为
[[1. 0.99426138]
[0.99426138 1. ]]
小批量梯度下降算法下求的theta值 [3.16014486 0.48668596]
小批量梯度下降算法求得相关系数为
[[1. 0.99426138]
[0.99426138 1. ]]
矩阵求逆法求得的theta值 [3.15890797 0.48804918]
小批量梯度下降算法求得相关系数为
[[1. 0.99426138]
[0.99426138 1. ]]
- 可以通过相关系数来判断预测值和真实值之间的匹配程度,以此来比较效果好坏。
- Numpy库提供了相关系数的计算方法
corrcoef()
,返回的矩阵包含所有两两组合的相关系数。
批量梯度下降算法的图形效果如下:
随机梯度下降算法的效果如下
小批量效果如下
矩阵求逆的效果如下
5. 局部加权线性回归
线性回归的一个问题是很可能出现欠拟合的现象,因为它求的是具有最小均方误差的无偏估计。然而有时候模型会出现欠拟合的情况,那么这种情况下将不能取得很好的预测效果。所以有些方法允许在估计中引入一些偏差,来降低预测的均方误差。其中一个方法就是局部加权线性回归(LWLR)。
依旧使用前面使用过的那个目标函数,只是现在是进行加权之后的,如下所示
其中w(i)是权重,根据要预测的点与数据集中的点的距离来为数据集中的点赋权值。某点离要预测的点越远,其权重越小,否则越大。LWLR使用“核”来对附近的点赋予更高的权重,最常用的核实高斯核,高斯核对应的权重如下:
上述公式中包含一个需要用户指定的参数k,它决定了对附近的点赋予多大的权重,这也是使用LWLR时唯一需要考虑的参数。然后对上式进行求解,解得回归系数如下
W是权重矩阵,其中
根据上述公式,编写相应的Python代码,通过改变K值来调节回归效果,代码如下
import numpy as np
import random
import matplotlib.pyplot as plt
import math
'''
针对一个点的局部线性回归
'''
def lwlr(testPoint, xArr, yArr, k=1.0):
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
m = np.shape(xMat)[0]
weight = np.mat(np.eye(m)) # 对角都是1的矩阵
for i in range(m):
diffMat = testPoint - xMat[i, :]
weight[i, i] = np.exp((diffMat * diffMat.T) / (-2 * k ** 2))
xTx = xMat.T * weight * xMat
if np.linalg.det(xTx) != 0:
theta = xTx.I * xMat.T * weight * yMat
return theta
return None
def lwlrAll(testArr, xArr, yArr, k=1.0):
m = np.shape(testArr)[0]
yHat = np.zeros(m)
for i in range(m):
thetaRe = lwlr(testArr[i], xArr, yArr, k)
yHat[i] = testArr[i] * thetaRe
return yHat
def main():
# 产生用于训练的数据
x = np.arange(0, 25, 0.5)
xArr = []
yArr = []
for xi in x:
xArr.append([1, xi])
yArr.append(0.5 * xi + 3 + random.uniform(0, 1) * math.sin(xi))
xArr = np.array(xArr)
yArr = np.array(yArr)
# 对所有样本依次进行局部线性回归,并画图
plt.figure(1)
yHat = lwlrAll(xArr, xArr, yArr, 1.0)
plt.scatter(xArr[:, 1], yArr, 60)
plt.plot(xArr[:, 1], yHat, 'r', lw=2)
plt.figure(2)
yHat = lwlrAll(xArr, xArr, yArr, 0.3)
plt.scatter(xArr[:, 1], yArr, 60)
plt.plot(xArr[:, 1], yHat, 'r', lw=2)
plt.figure(3)
yHat = lwlrAll(xArr, xArr, yArr, 0.1)
plt.scatter(xArr[:, 1], yArr, 60)
plt.plot(xArr[:, 1], yHat, 'r', lw=2)
plt.show()
if __name__ == "__main__":
main()
上述代码的效果如下,第一张图是K=1.0的,拟合的效果较好;第二张图是K=0.3的;第三张图是K=0.1,考虑太多噪声,有点过拟合了。
最后说一下局部线性回归存在的问题,局部线性回归增加一定的计算量,因为它对每个点做预测时都必须使用整个数据集。从上述图中K=0.1的情况中可以看出,大多数据点的权重都接近零,如果避免这些计算将可以减少程序运行时间,从而缓解计算机量增加带来的问题。
6. 示例:预测鲍鱼的年龄
这个示例是机器学习实战这本书上的一个示例,将局部线性回归知识用于真实数据。使用的是来自UCI数据集合的数据,数据集大概情况如下(数据集地址见文末)
1 0.455 0.365 0.095 0.514 0.2245 0.101 0.15 15
1 0.35 0.265 0.09 0.2255 0.0995 0.0485 0.07 7
-1 0.53 0.42 0.135 0.677 0.2565 0.1415 0.21 9
1 0.44 0.365 0.125 0.516 0.2155 0.114 0.155 10
0 0.33 0.255 0.08 0.205 0.0895 0.0395 0.055 7
0 0.425 0.3 0.095 0.3515 0.141 0.0775 0.12 8
-1 0.53 0.415 0.15 0.7775 0.237 0.1415 0.33 20
-1 0.545 0.425 0.125 0.768 0.294 0.1495 0.26 16
1 0.475 0.37 0.125 0.5095 0.2165 0.1125 0.165 9
-1 0.55 0.44 0.15 0.8945 0.3145 0.151 0.32 19
-1 0.525 0.38 0.14 0.6065 0.194 0.1475 0.21 14
1 0.43 0.35 0.11 0.406 0.1675 0.081 0.135 10
1 0.49 0.38 0.135 0.5415 0.2175 0.095 0.19 11
-1 0.535 0.405 0.145 0.6845 0.2725 0.171 0.205 10
-1 0.47 0.355 0.1 0.4755 0.1675 0.0805 0.185 10
1 0.5 0.4 0.13 0.6645 0.258 0.133 0.24 12
0 0.355 0.28 0.085 0.2905 0.095 0.0395 0.115 7
-1 0.44 0.34 0.1 0.451 0.188 0.087 0.13 10
1 0.365 0.295 0.08 0.2555 0.097 0.043 0.1 7
从数据集可以看到,数据集是多维的,最后一列代表鲍鱼的真实年龄,前面几列的数据是一些鲍鱼的特征,例如鲍鱼壳的层数等,基于的推论是鲍鱼的真实年龄可由鲍鱼壳的层数等鲍鱼的特征推算而出。下面我们不对数据进行处理,直接测试局部加权回归的效果:
import numpy as np
def loadDataSet(filePath):
f = open(filePath)
numFeat = len(f.readline().split("\t")) - 1 # 获取一行特征的数量
dataList = []
labelList = []
f.seek(0) # 文件指针回到初始位置
for line in f.readlines():
lineArr = []
curLine = line.strip().split("\t") # strip去掉\n
for i in range(numFeat):
lineArr.append(float(curLine[i]))
dataList.append(lineArr)
labelList.append(float(curLine[-1]))
return dataList, labelList
def lwlr(testPoint, xArr, yArr, k=1.0):
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
m = np.shape(xMat)[0]
weight = np.mat(np.eye(m))
for i in range(m):
diffMat = testPoint - xMat[i, :]
weight[i, i] = np.exp((diffMat * diffMat.T) / (-2.0 * k ** 2))
xTx = xMat.T * (weight * xMat)
if np.linalg.det(xTx) != 0.0:
theta = xTx.I * (xMat.T * (weight * yMat))
return testPoint * theta
return None
def lwlrAll(testArr, xArr, yArr, k=1.0):
m = np.shape(testArr)[0]
yHat = np.zeros(m)
for i in range(m):
yHat[i] = lwlr(testArr[i], xArr, yArr, k)
return yHat
def standRegres(xArr, yArr):
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
xTx = xMat.T * xMat
if np.linalg.det(xTx) != 0.0:
theta = xTx.I * (xMat.T * yMat)
return theta
return None
def rssError(yArr, yHatArr):
return ((yArr - yHatArr) ** 2).sum()
if __name__ == '__main__':
abaloneX, abaloneY = loadDataSet("abalone.txt")
yHat01 = lwlrAll(abaloneX[0:99], abaloneX[0:99], abaloneY[0:99], 0.1)
yHat1 = lwlrAll(abaloneX[0:99], abaloneX[0:99], abaloneY[0:99], 1)
yHat10 = lwlrAll(abaloneX[0:99], abaloneX[0:99], abaloneY[0:99], 10)
print("k=0.1时,训练集上的误差为:", rssError(abaloneY[0:99], yHat01))
print("k=1时,训练集上的误差为:", rssError(abaloneY[0:99], yHat1))
print("k=10时,训练集上的误差为:", rssError(abaloneY[0:99], yHat10))
yHat01 = lwlrAll(abaloneX[100:199], abaloneX[0:99], abaloneY[0:99], 0.1)
yHat1 = lwlrAll(abaloneX[100:199], abaloneX[0:99], abaloneY[0:99], 1)
yHat10 = lwlrAll(abaloneX[100:199], abaloneX[0:99], abaloneY[0:99], 10)
print("k=0.1时,测试集上的误差为:", rssError(abaloneY[100:199], yHat01))
print("k=1时,测试集上的误差为:", rssError(abaloneY[100:199], yHat1))
print("k=10时,测试集上的误差为:", rssError(abaloneY[100:199], yHat10))
theta = standRegres(abaloneX[0:99],abaloneY[0:99])
yHat = np.mat(abaloneX[100:199])*theta
print("简单线性回归时的误差为:", rssError(abaloneY[100:199], yHat.T.A))
输出结果如下:
k=0.1时,训练集上的误差为: 56.78868743050092
k=1时,训练集上的误差为: 429.89056187038
k=10时,训练集上的误差为: 549.1181708827924
k=0.1时,测试集上的误差为: 57913.51550155911
k=1时,测试集上的误差为: 573.5261441895982
k=10时,测试集上的误差为: 517.5711905381903
简单线性回归时的误差为: 518.6363153245542
从输出结果可以看出,当核k=10的时候,训练集上的误差是最大的,但是测试集上的误差是最小的,同时它效果和简单线性回归的效果类似。所以在所有数据集上都使用最小的核的话,将会造成过拟合,在新数据上不一定能达到最好的预测效果。
注意:
1.在局部加权线性回归中,过小的核可能导致过拟合现象,即在训练集上表现良好,但在测试集上表现就不行了;
2.训练的模型要在测试集上比较他们的效果,而不是训练集;
7.如何用线性回归模型拟合非线性关系
线性模型也可以拟合自变量和因变量之间的非线性关系,只是需要对样本数据进行修改。比如有一些样本,只有一个特征,我们把特征和结果作图以后发现,可能是这个样子的:
从样本特征和结果关系走势来看,这不是一个直线,看起来挺像二阶曲线的,那么我们可以把一个特征“变成”两个,如
那么在添加“新的特征”之后的样本集上去训练这个函数
那么得到回归系数之后代回函数
那么就相当于拟合了一个二阶多项式了。这种操作以此类推都可以,比如三阶、四阶、对数都行。
附
参考内容
- 机器学习,周志华
- 机器学习实战,[美]Peter Harrington
- CSDN_线性回归及其Python实现(最大似然法)
- CSDN_Python实现批量梯度下降 随机梯度下降 小批量梯度下降 代码
- CSDN_【机器学习】局部加权线性回归
欢迎关注微信公众号【一口程序锅】,一口想煮点技术的锅。
来源:CSDN
作者:一口程序锅
链接:https://blog.csdn.net/dianningke0532/article/details/101919073