前面博客有讲到,样本如果不是线性的可以通过多项式扩展,映射到多维空间来拟合。如此之外,还可以做一个局部加权线性回归(Locally Weighted Linear Regression,LWLR)。
直观图:
公式
在该算法中,我们给待预测点附近的每个点赋予一定的权重。与kNN一样,这种算法每次预测均需要事先选取出对应的数据子集。
该算法解出回归系数 w 的形式如下:
其中 w 是一个矩阵,用来给每个数据点赋予权重。
LWLR使用“核”(与支持向量机中的核类似)来对附近的点赋予更高的权重 。核的类型可以自由选择,最常用的核就是高斯核,高斯核对应的权重如下:
这样就构建了一个只含对角元素的权重矩阵
,该函数称为指数衰减函数。它根据要预测的点与数据集中的点的距离来为数据集中的点赋权值,并且点
与
越近,
将会越大,也就是说,当某点离要预测的点越远,其权重越小,否则越大。上述公式包含一个需要用户指定的参数 k(也叫波长参数) ,它决定了对附近的点赋予多大的权重,可以说是控制了权重值随距离下降的速度,这也是使用LWLR时唯一需要考虑的参数,在下图中可以看到参数 k 与权重的关系:
每个点的权重图(假定我们正预测的点是 x = 0.5 ),最上面的图是原始数据集,第二个图显示了当 k = 0.5 时,大部分的数据都用于训练回归模型;而最下面的图显示当 k = 0.01 时,仅有很少的局部点被用于训练回归模型。
注意:使用该方式主要应用到样本之间的相似性考虑,主要内容在SVM中再考虑(核函数)。
除引之外还有另外一种常用的指数衰减函数:
当然这两种是类似,只不过一个是绝对值,一个是平方的形式。
损失函数
局部加权线性回归的损失函数,与普通线性回归的损失函数平方和损失函数
- 普通线性回归损失函数
- 局部加权线性回归损失函数:
注:以上两个式子同样可以加上正则项
代码实现
from numpy import *
import matplotlib.pyplot as plt
# 加载数据 返回数据和目标值
def loadDataSet(fileName):
numFeat = len(open(fileName).readline().split('\t')) - 1
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = []
curLine = line.strip().split('\t')
for i in range(numFeat):
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
labelMat.append(float(curLine[-1]))
return dataMat, labelMat
# 利用公式计算回归系数
def standRegres(xArr, yArr):
xMat = mat(xArr); yMat = mat(yArr).T
xTx = xMat.T * xMat # 公式步骤
if linalg.det(xTx) == 0.0:
print("行列式为0,奇异矩阵,不能做逆")
return
ws = xTx.I * (xMat.T * yMat) # 解线性方程组
# ws = linalg.solve(xTx,xMat.T*yMat) # 也可以使用函数来计算 线性方程组
return ws
# 局部加权线性回归 返回该条样本预测值
def lwlr(testPoint, xArr, yArr, k=1.0):
xMat = mat(xArr); yMat = mat(yArr).T
m = shape(xMat)[0]
weights = mat(eye((m))) # 创建为单位矩阵,再mat转换数据格式 因为后面是与原数据矩阵运算,所以这里是为了后面运算且不带来其他影响
for j in range(m): # 利用高斯公式创建权重W 遍历所有数据,给它们一个权重
diffMat = testPoint - xMat[j, :] # 高斯核公式1
weights[j, j] = exp(diffMat * diffMat.T / (-2.0 * k ** 2)) # 高斯核公式2 矩阵*矩阵.T 转行向量为一个值 权重值以指数级衰减
xTx = xMat.T * (weights * xMat) # 求回归系数公式1
if linalg.det(xTx) == 0.0: # 判断是否有逆矩阵
print("行列式为0,奇异矩阵,不能做逆")
return
ws = xTx.I * (xMat.T * (weights * yMat)) # 求回归系数公式2
return testPoint * ws
# 循环所有点求出所有的预测值
def lwlrTest(testArr, xArr, yArr, k=1.0): # 传入的k值决定了样本的权重,1和原来一样一条直线,0.01拟合程度不错,0.003纳入太多噪声点过拟合了
m = shape(testArr)[0]
yHat = zeros(m)
for i in range(m):
yHat[i] = lwlr(testArr[i], xArr, yArr, k) # 返回该条样本的预测目标值
return yHat
if __name__ == '__main__':
xArr, yArr = loadDataSet('ex0.txt')
# 求所有预测值
yHat = lwlrTest(xArr, xArr, yArr, 0.01)
print(yHat)
# 绘制数据点和拟合线(局部加权线性回归)
xMat = mat(xArr)
srtInd = xMat[:, 1].argsort(0) # 画拟合线 需要获得所有横坐标从小到大的下标
xSort = xMat[srtInd][:, 0, :] # 获得排序后的数据
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(xSort[:, 1], yHat[srtInd], color='red')
ax.scatter(xMat[:, 1].flatten().A[0], mat(yArr).T.flatten().A[0])
plt.show()
代码中写的是K=0.01。下图中给出了k在三种不同取值下的结果图。当k = 1.0时权重很大,如同将所有的数据视为等权重,得出的最佳拟合直线与标准的回归一致。使用k = 0.01得到了非常好的效果,抓住了数据的潜在模式。下图使用k = 0.003纳入了太多的噪声点,拟合的直线与数据点过于贴近。所以,图中的最下图是过拟合的一个例子,而最上图则是欠拟合的一个例子。
使用3种不同平滑值绘出的局部加权线性回归结果。上图中的平滑参数k =1.0,中图k = 0.01,下图k = 0.003。可以看到,k = 1.0时的模型效果与最小二乘法差不多,k = 0.01时该模型可以挖出数据的潜在规律,而k = 0.003时则考虑了太多的噪声,进而导致了过拟合现象。
总结
局部加权线性回归也存在一个问题,即增加了计算量,因为它对每个点做预测时都必须使用整个数据集。从上图可以看出,k = 0.01时可以得到很好的估计,但是同时看一下图3中k = 0.01的情况,就会发现大多数据点的权重都接近零。如果避免这些计算将可以减少程序运行时间,从而缓解因计算量增加带来的问题。