这里记录一下关于回归方面的知识包括(线性回归、局部加权回归、岭回归、逐步线性回归)等基础思想和代码实现。以及sklearn的实现方法。(数据来自机器学习实战第八章)
回归:
分类的目标变量是标称型数据,而回归可以对连续型数据做预测,同样也是寻找一条最佳的拟合线。
回归的目的是预测数值型的目标值,最直接的办法是根据输入数据写出一个目标值的计算公式,即一个线性方程:y=kx+bz类似的函数,这就是所谓的回归方程,其中k、b称作回归系数,求这些回归系数的过程就是回归。一旦有了这些回归系数,给定输入,做预测就很容易了。具体的做法使用回归系数(每个特征一个对应的系数)乘以输入(特征)值,再将结果全部加在一起,就得到了预测值。当然还有非线性回归,该模型不认同上面的公式。
线性回归:
如何从一堆数据中求回归方程呢?假定输入数据为x,回归系数为向量w,那么预测结果 ,现在手里有数据x、y,我们通过最小误差来找w,即预测y与真实y的差值,为防止简单的差值求和正负抵消,一般采用平方误差。
平方误差可以写做: 使用矩阵表示,再对w求导得,令w=0得到最终方程:
将数据带进公式运算即可
其中表示当前可以估计出w的最优解,由于需要对矩阵求逆,所以必须判断矩阵是否能逆,可以使用Numpy的矩阵方法来运算。(”普通最小二乘法“)
代码:
样例数据(使用作者提供的数据,最后一列为目标变量):
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
# 绘制数据点和拟合线(线性回归)
xArr,yArr = loadDataSet('ex0.txt')
ws = standRegres(xArr,yArr)
xMat = mat(xArr)
yMat = mat(yArr)
yHat = xMat*ws
print(yHat.T) # 查看预测值
# 绘制图形,观察拟合线
fig = plt.figure()
ax = fig.add_subplot(111)
# !很方便的小知识,使用mat将序列转换为二维数组。使用flatten可以再转换为折叠的一维数组,矩阵.A = getA(),使用A[0]得到ndarray数组
ax.scatter(xMat[:,1].flatten().A[0],yMat.T[:,0].flatten().A[0])
xCopy = xMat.copy()
xCopy.sort(0) # 如果直线上的点次序混乱,绘图将出现问题
yHat = xCopy*ws
ax.plot(xCopy[:,1],yHat,color='red')
plt.show()
普通线性回归很简单,只需使用上述的公式带入数据即可会的回归系数,获得预测值,然后在绘图查看拟合线与原数据。
这里有个问题就是,有可能不同的数据获得的拟合线相同,如何判断模型的好坏?使用np.corrcoer求预测值与真实值的相关度,相关度越高,则模型相对较好。
# 求该模型的好坏,使用corrcoer求预测值和真实值的相关度
yHat = xMat*ws
k = corrcoef(yHat.T,yMat)
print(k)
对角线为yMat与自己匹配为1,yHat与yMat相关系数为0.986。
局部加权线性回归:
由上面绘制的图形可以看出,线性回归可能出现欠拟合的问题(求的是最小均方误差的无偏估计),所以可以引入一些偏差,降低预测的均方误差。
原理:
给待预测点附近的每个点都赋予一定的权重,然后与上面一样基于最小均方误差进行普通的线性回归。
问题:
与KNN一样每次预测都需要事先选取对应子集(遍历数据集),时间复杂度提高。
同SVM一样采用核来对附近的点赋予权重,比较常用的核为高斯核,越近的点权重越大:
最终公式为(W是一个矩阵,用来给每个点赋予权重):
代码(同样的样例数据):
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
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值的选择,1时等权重,欠拟合;0.01效果很好;0.003纳入太多噪声点,过拟合了。
局部加权线性回归的计算量较大(遍历数据),当k=0.01时,很多数据点的权重接近0,如果可以避免这些计算,则可以减少程序运行时间。
岭回归(鲍鱼年龄预测):
岭回归属于一种缩减方法,当特征>样本数量时X不是满秩矩阵求逆有问题,简单来说,就是在矩阵上加入一个从而使得矩阵非奇异,进而能对+求逆。现在也用来在估计中加入偏差,得到更好的估计,引入惩罚项限制了所有w的和,减少不重要的参数项,统计学中也叫“缩减”。
最终回归公式:
代码:
样例数据(使用作者提供的数据,最后一列为目标变量):
# 岭回归 返回回归系数
def ridgeRegres(xMat, yMat, lam=0.2): #lambda
xTx = xMat.T * xMat
denom = xTx + eye(shape(xMat)[1]) * lam # 这几行为岭回归的公式
if linalg.det(denom) == 0.0: # 依旧需要判断存在逆矩阵
print("行列式为0,奇异矩阵,不能做逆")
return
ws = denom.I * (xMat.T * yMat)
return ws # 返回每个特征的回归系数 8个
# 循环30次,获得不同大小λ下的回归系数
def ridgeTest(xArr, yArr):
xMat = mat(xArr) # 数据需要做标准化处理,最后得到xMat、yMat
yMat = mat(yArr).T
yMean = mean(yMat,0) # 该函数第二个参数(压缩)=0表示对各列求平均值得到1*n的矩阵,=1表示对给行求平均值m*1矩阵
yMat = yMat - yMean
xMeans = mean(xMat, 0) # 对数据集做同样的操作
xVar = var(xMat, 0) # 每一列 var方差,第二个参数=0表示求样本的无偏估计值(除以N-1),=1求方差(除以N) cov协方差
xMat = (xMat - xMeans) / xVar
numTestPts = 30
wMat = zeros((numTestPts, shape(xMat)[1]))
for i in range(numTestPts): # λ值改变
ws = ridgeRegres(xMat, yMat, exp(i - 10)) # 行列格式一样但处理了的数据集 行列格式一样但处理了的目标值 e的i-10次方
wMat[i, :] = ws.T # 将第i次每个特征的回归系数向量按行保存到30次测试的第i行
return wMat
abX,abY = loadDataSet('abalone.txt')
ridgeWeights = ridgeTest(abX,abY) # 返回 此处30次改变λ值后,得到的30行回归系数
fig = plt.figure() # 为了看到缩减(惩罚项)的效果而画图
ax = fig.add_subplot(111) # 及回归系数和惩罚项的关系
ax.plot(ridgeWeights) # 每列
plt.show()
(按列绘制每个特征随λ变化得线条)该图绘出了每个回归系数与的关系,在最左边,即λ最小时,可以得到所有系数的原始值(与线性回归一致);在右边,系数全部缩减成0;中间部分的某值可以取得最好得预测效果,为了定量得找到最佳参数值,需要进行交叉验证。另外需要判断哪些变量对数据预测最具影响力。(λ一般选择当所有线条趋于稳定得时候)
前向逐步回归:
逐步前向回归可以得到与lasso差不多得效果,但更简单,它属于一种贪心算法,即每一步都尽可能减少误差,一开始,所有得权重都设置为1,然后每一步所做的决策时对某个权重增加或减少一个很小的值。
代码:
样例数据与上面相同
# 使用该函数来计算 真实值和预测值 的平方误差和
def rssError(yArr,yHatArr):
return ((yArr-yHatArr)**2).sum()
# 这里将标准化构建成一个函数
def regularize(xMat):
inMat = xMat.copy()
inMeans = mean(inMat,0) #计算平均数,然后减去它
inVar = var(inMat,0)
inMat = (inMat-inMeans)/inVar
return inMat
# 前向逐步回归 不断地在新获得的权重上更新
def stageWise(xArr,yArr,eps=0.01,numIt=100):
xMat = mat(xArr); yMat=mat(yArr).T
yMean = mean(yMat,0)
yMat = yMat - yMean
xMat = regularize(xMat) # 调用函数标准化数据 在岭回归中同样的处理但直接在函数中
m,n = shape(xMat)
ws = zeros((n,1)); wsTest = ws.copy(); wsMax = ws.copy()
for i in range(numIt): # 不断更新
print(ws.T) # 打印出来便于观察每次权重的变化
lowestError = inf # 初始化最大误差
for j in range(n): # 循环每个特征
for sign in [-1,1]: # 增大或减小
wsTest = ws.copy()
wsTest[j] += eps*sign # eps每次迭代的步长
yTest = xMat*wsTest
rssE = rssError(yMat.A,yTest.A) # 预测值和真实值的误差
if rssE < lowestError:
lowestError = rssE
wsMax = wsTest # 更新wsMax,ws、wsMax、wsTest三者之间相互copy来保证每次结果的保留和更改
ws = wsMax.copy() # 当所有特征循环完了,找到错误最小的wsMax赋值给新的ws
#returnMat[i,:]=ws.T
#return returnMat
xArr,yArr = loadDataSet('abalone.txt')
stageWise(xArr,yArr,0.01,300) # 做300次的更新
每次循环都将每个特征的增大或减小情况运算,比较最小误差,记录当前最小误差的特征是增加还是减少的情况,得到本次循环的回归系数。
最后我们再与最开始的“最小二乘法”进行比较,看看两者的差别:
# 与最小二乘法比较
xMat = mat(xArr)
yMat = mat(yArr).T
xMat = regularize(xMat)
yM = mean(yMat,0)
yMat = yMat - yM
weights = standRegres(xMat,yMat.T)
print(weights.T)
两次结果两者之间还是有较大差别的。
应用缩减方法(如逐步线性回归或岭回归)时,模型也就增加了偏差,与此同时却减小了模型的方差。
作者总结:
sklearn实现:
由于篇幅已经较多了,所以这里只是简单提一下sklearn的实现方法,sklearn的实现还是有很多参数需要注意的,所以最好还是根据官方提供的资料来运用。
from sklearn.linear_model import Ridge
clf = Ridge(alpha=.5)
X = [[0,0],[0,0],[1,1]]
y = [0,.1,1]
clf.fit(X,y)
print(clf.coef_)
print(clf.intercept_)
from sklearn import linear_model
clf = linear_model.Lasso(alpha=0.1)
clf.fit([[0,0], [1, 1], [2, 2]], [0, 1, 2])
print(clf.coef_)
print(clf.intercept_)
这里仅仅简单介绍,以上给出了官网地址,具体请参考。结合具体数据,使用合适的模型,调整最佳参数,才能得到更好的效果。
参考书籍:《机器学习实战》