数据挖掘经典算法:线性回归、局部加权回归、岭回归、逐步线性回归 sklearn实现

这里记录一下关于回归方面的知识包括(线性回归、局部加权回归、岭回归、逐步线性回归)等基础思想和代码实现。以及sklearn的实现方法。(数据来自机器学习实战第八章)

回归:

    分类的目标变量是标称型数据,而回归可以对连续型数据做预测,同样也是寻找一条最佳的拟合线。

    回归的目的是预测数值型的目标值,最直接的办法是根据输入数据写出一个目标值的计算公式,即一个线性方程:y=kx+bz类似的函数,这就是所谓的回归方程,其中k、b称作回归系数,求这些回归系数的过程就是回归。一旦有了这些回归系数,给定输入,做预测就很容易了。具体的做法使用回归系数(每个特征一个对应的系数)乘以输入(特征)值,再将结果全部加在一起,就得到了预测值。当然还有非线性回归,该模型不认同上面的公式。

线性回归:

    如何从一堆数据中求回归方程呢?假定输入数据为x,回归系数为向量w,那么预测结果 y = x^{T}w,现在手里有数据x、y,我们通过最小误差来找w,即预测y与真实y的差值,为防止简单的差值求和正负抵消,一般采用平方误差。

    平方误差可以写做:\sum_{i=1}^{m}(y_{i}-x_{i}^{T}w)^{2}         使用矩阵表示(y-Xw)^{T}(y-Xw),再对w求导得X^{T}(Y-Xw),令w=0得到最终方程:

        \varpi =(X^{T}X)^{-1}X^{T}y                       将数据带进公式运算即可

其中\varpi表示当前可以估计出w的最优解,由于(X^{T}X)^{-1}需要对矩阵求逆,所以必须判断矩阵是否能逆,可以使用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是一个矩阵,用来给每个点赋予权重):

    \varpi =(X^{T}WX)^{-1}X^{T}Wy

代码(同样的样例数据):

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不是满秩矩阵求逆有问题,简单来说,就是在矩阵X^{T}X上加入一个\lambda I从而使得矩阵非奇异,进而能对X^{T}X+\lambda I求逆。现在也用来在估计中加入偏差,得到更好的估计,引入惩罚项\lambda限制了所有w的和,减少不重要的参数项,统计学中也叫“缩减”。

最终回归公式:

    \varpi =(X^{T}X+\lambda I)^{-1}X^{T}y

代码:

样例数据(使用作者提供的数据,最后一列为目标变量):

# 岭回归 返回回归系数
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()

(按列绘制每个特征随λ变化得线条)该图绘出了每个回归系数与log(\lambda )的关系,在最左边,即λ最小时,可以得到所有系数的原始值(与线性回归一致);在右边,系数全部缩减成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_)

Lasso

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_)

这里仅仅简单介绍,以上给出了官网地址,具体请参考。结合具体数据,使用合适的模型,调整最佳参数,才能得到更好的效果。

 

参考书籍:《机器学习实战》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值