【机器学习实战】——预测数值型数据:回归

【引言】本章节代码实现基于python3.7环境
同部西瓜书学习,对应《机器学习实战》第八章——预测数值型数据:回归
(Page136-158)

本章首先介绍线性回归,包括其名称的由来和python实现。在这之后引入了局部平滑技术,分析如何更好地拟合数据。接下来,本章将探讨回归在欠拟合情况下地缩减技术,探讨偏差和方差的概念。最后将融合所有技术,预测鲍鱼的年龄和玩具的售价。此外还将介绍一些python进行书去采集的工作。


8.1用线性回归找到最佳拟合直线

@概念

优点:结果易于理解,计算上不复杂;
缺点:对非线性的数据拟合不好;
适合数据类型:数值型和标称型数据。

*回归的目的是预测数值型的目标值。*所谓回归方程,就是常见的y=kx+b,其中k称为回归系数,而求回归系数的过程就是回归。一旦有了恒定且最优的回归系数,那么用来做预测就显得轻而易举了。回归一般指的是线性回归

回归的一般方法:
(1)收集数据;
(2)准备数据,预处理成数值型数据;
(3)分析数据,利用可视化和缩减法求出不同的回归系数;(4)训练算法,找到回归系数;
(5)测试算法,用均方、平方误差法分析模型效果、泛化能力;
(6)使用算法。

本节采用平方误差法,w作为回归系数向量写作:
i = 1 m ( y i x i T w ) 2 \sum_{i=1}^{m}(y_i-x_i^Tw)^2
写为矩阵形式如西瓜书上详细推导过程求解可知,对w求导,得到:
X T ( Y X w ) X^T(Y-Xw)
令其等于0,解出w如下:
w ^ = ( X T X ) 1 X T y \hat{w}=(X^TX)^{-1}X^Ty
其中 w ^ \hat{w} 回归系数w向量的最优解。该方法采用的是非常常见的普通最小二乘法(Ordinary Least Squares——OLS)

  • 注:因为矩阵形式的回归系数必须要求XTX可逆,即存在逆矩阵不存在的可能性,因此需要在代码中对此作出判别:
if linalg.det(xTx) == 0.0:
	print(" This matrix is sigular, cannot to inverse.")
	return
//在python中,Numpy提供一个线性代数库linalg
//其中包涵linalg.det()方法用来计算行列式
//因此我们可以用此方法来判断XTX的行列式是否为0,从而判断是否可逆。

//此外,linalg还提供一个函数来解未知矩阵
//linalg.solve(A,B)用来计算{A逆 * B}的解

程序清单8.1

regression.py
from numpy import *

def loadDataSet(filename):
    numFeat = len(open(filename).readline().split('\t')) - 1
    //readline()方法用来查看单行代码字符串个数
    //split('\t')方法:以tab符为断开,对此行数据进行分段
    
    dataMat = []; labelMat = []
    fr = open(filename)
    for line in fr.readlines():
    //readlines()方法用来查看读取的文件共计多少行
        lineArr = []
        curLine = line.strip().split()
        //strip()方法用来删除首尾的字符,此处为‘空格’,则删除首尾多余的空格
        //split()以空格断开
        
        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("This matrix is singular, cannot do inverse.")
        return
        //判断是否可逆
        
    ws = xTx.I * (xMat.T*yMat)
    //最优回归系数求解公式
    return ws
测试程序——Console
//console运行
import regression
from numpy import *
xArr, yArr = regression.loadDataSet(‘ex0.txt’)
//文件路径需一致
------

ws = regression.standRegres(xArr, yArr)
//计算最优回归系数向量
------

xMat = mat(xArr)
yMat = mat(yArr)
//矩阵化数据
------

yHat = xMat*ws	
//给出预测结果yHat用于和真实结果yMat进行对比判断泛化性能
------

//利用可视化matplotlib绘图
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)	
//add_subplot(abc)方法用于绘制子图
//其中,a表示子图的行数,b表示子图的列数(想象画面被分为a*b的表格);c表示在表格中的坐标
//此处为111,则窗口只显示一张图片
ax.scatter(xMat[:,1].flatten().A[0], yMat.T[:,0].flatten().A[0])
-----

xCopy = xMat.copy()
xCopy.sort(0)
//对原本混乱的数据点进行升序排序,sort(0)表示升序
ax.plot(xCopy[:,1], yHat)
plt.show()

几乎任意一数据模型都可以用上述方法建模,有时需要判断模型的好坏。因此需要计算yHat序列和真实值y序列的匹配程度,即计算两个序列的相关系数

在python中,Numpy库提供了相关系数的计算方法:**corrcoef(预测值,真实值)**来计算相关性。
例如我们可以输入:

yHat = xMat*ws
corrcoef(yHat.T, yMat)

会得到:

array([[1.                ,  0.98647356],
[0.98647356,  1.                ]])

显然这个矩阵中包涵两两组合的相关系数,观察对角线可以看出,该矩阵相当于:

* yHat yMat
yHat 1 0.98647356
yMat 0.98647356 1

即yMat和yHat与自己匹配永远是最完美的,而yHat和yMat的相关系数约等于0.99,相关系数越高越好


8.2局部加权线性回归

线性回归的一个问题是容易出现欠拟合的现象,因为它求的是具有最小均方误差的无偏估计,因此有些方法允许在估计中引入一些偏差,从而降低预测的均方误差。代表性的方法就是**局部加权线性回归(Locally Weighted Linear Regression, LWLR)**方法。

思路:同归给待预测点附近的每个点赋予一定的权重,与前一节类似,在这个子集上基于最小均方差来进行普通的回归。该方法每次预测均需事先选取出对应的数据子集,公式如下:
w ^ = ( X T W X ) 1 X T W y \hat{w}=(X^TWX)^{-1}X^TWy
显然,在标准线性回归方程中w作为回归系数向量,而在局部加权线性回归中,w作为权重矩阵更具有高维度的通用性。

LWLR使用的权重计算称之为核(kernel),类似于SVM中的核函数,利用核来对附近的点赋予更高的权重。核函数可以自由选择,其中最常用的就是高斯核(Radial Basis Function RBF-径向基函数),公式如下:
w ( i , i ) = e x ( i ) x 2 k 2 w(i,i)=e^{\frac{|x^{(i)}-x|}{-2k^2}}
一般的,也可以用二范式来计算x(i)-x。

构建了一个只含有对角元素的权重矩阵w,并且点x与x(i)越接近,w(i,i)将会越大。因此,上述公式包含一个需要用户指定的参数k,它决定了对附近的点赋予多大的权重,这也是LWLR方法唯一需要考虑的参数。随着样本点与待预测点距离的递增,权重将因k的取值以指数级衰减,而通常我们称参数k为衰减速度或带宽
k=1
上下两图分别是衰减速度k=1和k=5的二范式径向基函数图像。
k=5
可以看出,k作为局部加权线性回归方法中最重要的参数,控制了径向基函数的作用范围,即高斯核的局部作用范围,带通越大,高斯核函数的影响范围越大,超出一个范围后,核函数的值基本不再变化。


程序清单8.2

添加代码至8.1py文件中:

regression.py
//lwlr函数中for循环遍历整个数据集,用于计算每一个点的最优权重向量weights
//lwlrTest函数用于测试最优权重矩阵WS,为数据集中的每个点调用lwlr()从而用于测试最合适的K大小
------------
//取衰减系数为1.0
def lwlr(testPoint, xArr, yArr, k=1.0):

    //创建对角矩阵eye((m)),并且将数组矩阵化
    xMat = mat(xArr); yMat = mat(yArr).T
    m = shape(xMat)[0]
    weights = mat(eye((m)))

    //计算每一个点的最优权重向量weights
    for j in range(m):
        diffMat = testPoint - xMat[j,:]
        weights[j,j] = exp(diffMat * diffMat.T/(-2.0*k**2))
    xTx = xMat.T * (weights * xMat)
    
    //判断是否可逆
    if linalg.det(xTx) == 0.0:
        print("This matrix is singular, cannot do inverse.")
        return
        
    //计算最优权重矩阵WS
    ws = xTx.I * (xMat.T * (weights * yMat))
    return testPoint * ws
------------
def lwlrTest(testArr, xArr, yArr, k=1.0):
    m = shape(testArr)[0]
    yHat = zeros(m)
    for i in range(m):
        yHat[i] = lwlr(testArr[i], xArr, yArr, k )
    return yHat

再python提示符中运行以下代码:

测试程序——Console
//重载数据集
reload(regression.py)
xArr,yArr = regression.loadDataSet('ex0.txt')
-------------
//单点估计测试
//分别取k=1.0和k=0.01,计算第一个点xArr[0]的预测结果
yArr[0]
regression.lwlr(xArr[0], xArr, yArr, 1.0)
regression.lwlr(xArr[0], xArr, yArr, 0.01)

结果为

- k=1.0 k=0.01 实际值
输出 3.12204471 3.2017529 3.176513

以上为对单个点进行测试的结果,为了得到数据集所有点的估计,调用lwlrTest()函数:

//调用整个数据集xArr进行测试,选取带宽k=0.003
yHat = regression.lwlrTest(xArr, xArr, yArr, 0.003)

//绘制图像并重新排序
xMat = mat(xArr)
srtInd = xMat[:, 1].argsort(0)
xSort = xMat[strInd][:, 0, :]

import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(xSort[:, 1], yHat[strInd])
ax.scatter(xMat[:, 1].flatten().A[0], mat(yArr).T.flatten.A[0], s = 2, c = 'red')
plt.show()

通过图像可以看到k在分别取1.0、0.01和0.003的结果图,列出表格:

- k=1.0 k=0.01 k=0.003
特点 最佳拟合直线与标准回归一致,出现欠拟合 较为合适,可以挖掘出数据的潜在规律 拟合了太多的噪声数据,出现了过拟合

因此可见,为避免出现欠拟合和过拟合的现象,合适的k值才是局部加权的关键。而LWLR方法页存在一个问题:需要对每个点预测时都要调整数据集,因此其计算开销非常大


8.3 示例:预测鲍鱼的年龄

简单线性回归达到了与局部加权线性回归类似的效果,因此也证明了必须在未知数据上比较效果才能选取到最佳模型

通过设置k为0.1、1和10三个值来预测鲍鱼的年龄可以得出,在训练集(前100项数据)中10未必是最最合适的核,但是在测试集(后100项数据)中,10明显比0.1和1的效果更好。

可是,10却不一定是对于全局数据而言最合适的核,因此若想得到更好的效果,应该用10个不同的样本集做10次测试来比较结果

本例只是说明了如何利用局部加权线性回归来构建模型,可以得到比普通线性回归更好的效果。LWLR的问题在于每次必须在整个数据集上运行,因此必须保证所有的训练数据才能够做出相对较为准确的预测。


8.4 利用缩减系数来理解数据

当数据特征比样本点还多的时候便不可以使用线性回归和之前的方法来做预测了,因为输入数据的矩阵X不是满秩矩阵,从而会导致计算(XTX)-1时出错。为了解决这个问题,统计学家们提出了岭回归、lasso法和前向逐步回归法

8.4.1 岭回归

针对非满秩矩阵无法求逆的问题,岭回归加入了λI,其中I是一个m*m的单位矩阵,其对角线元素为1,其余元素为0。原式为XTX,经过矩阵非奇异化后变为XTX+λI,进而可以进行求逆运算,W最优权重矩阵计算公式则变为:
w ^ = ( X T X + λ I ) 1 X T y \hat{w}=(X^TX+λI)^{-1}X^Ty
λ被称为惩罚项,是一个用户定义的数值。通过引用惩罚项λ限制了所有w的和,该过程能够减少不重要的参数,这在统计学中也被称为缩减

岭回归之所以被称为岭回归,就是因为其单位矩阵I,对角线就像是一座“山岭”,这便是其名称的由来。

缩减之所以能够达到更好的预测效果,是因为它能够去掉不重要的参数,从而更好地理解数据

因此,采用岭回归的首要任务便是通过预测误差最小化得到最合适的λ


程序清单8.3

regression.py

def ridgeRegres(xMat, yMat, lam=0.2):
    xTx = xMat.T*xMat
    denon = xTx + eye(shape(xMat)[1])*lam
    if linalg.det(denon) == 0.0:
        print("this matrix is singular, cannot do inverse")
        return
    ws = denon.I * (xMat.T*yMat)
    return ws

def ridgeTest(xArr, yArr):
    xMat = mat(xArr); yMat = mat(yArr).T
    yMean = mean(yMat, 0)
    yMat = yMat - yMean

//进行标准化处理,将所有特征减去各自的均值并除以方差
    xMeans = mean(xMat, 0)
    xVar = var(xMat, 0)
    xMat = (xMat - xMeans)/xVar
----------------------------- 
//设置i的步长,确定lambda的变化范围为[0, 30]
    numTestPts = 30
-----------------------------
//设置0矩阵wMat用于置放每一个样本,对8个特征每一次进行计算时的ws权值
    wMat = zeros((numTestPts, shape(xMat)[1]))
    for i in range(numTestPts):
        ws = ridgeRegres(xMat, yMat, exp(i-10))
        xMat[i,:] = ws.T
    return wMat

ridgeRegres()函数用于计算回归系数ws,而ridgeTest()函数用于测试λ。之所以再ridgeRegres中仍然需要进行行列式检查,原因是当lambda设置为0的时候一样可以会产生奇异矩阵的错误。

标准化处理是为了使每维特征具有相同的重要性,其具体做法是所有特征都减去各自的均值mean()并处以方差var()

在30个不同的lambda下调用ridgeRegres()函数,其中lambda呈指数级变化e(i-10)的目的在于可以看出lambda在取值极小和极大时分别对结果所造成的影响。最后将所有的回归系数输出到一个矩阵并返回。

测试程序——Console
abX, abY = loadDataSet('ex0.txt')
ridgeWeights = ridgeTest(abX, abY)

import matplotlib.pyplot as plt
fig  = plt.figure()
ax = fig.add_subplot(111)
ax.plot(ridgeWeights)
plt.show()

log(λ)
横坐标为i的取值(或log(λ)),[0, 30];纵坐标为回归系数ws。

当i为0时 特征1 特征2 特征3 …… 特征8
样本①——回归系数ws值 0.043 -0.022 0.132 …… 0.166

由上表可知当i=0时,ws值通过ridgeRegres()函数计算,得到了分别属于8个特征回归系数的值点如图所示。

依此类推,图中8条线则为8个特征在所有样本上不同的回归系数ws(或权值)大小的变化曲线。当i达到一定程度时,所有回归系数ws收敛于横轴(=0),在一定程度上缩减了回归系数的范围,这便是缩减法名字的由来

显然,在中间部分的某值将可以取得最好的预测结果,为了定量地找到最佳参数值,还需要进行交叉验证。也可以比较对应的系数大小,并通过图示观察得到哪些变量对结果预测最具有影响力,


8.4.2 lasso法

通过增加约束:
k = 1 n w k 2 λ \sum_{k=1}^{n}{w_k^2≤λ}
则普通的最小二乘法回归会得到和岭回归一样的公式在最小二乘法OLS回归过程中若两个或多个特征相关时,会得出一个很大的正系数和负系数,因此使用岭回归可以避免这个过程。

与此同时,lasso法也是一样,通过对回归系数做出限定,约束式如下:
k = 1 n w k λ \sum_{k=1}^{n}{|w_k|≤λ}
其实和岭回归的作用和方式大相径庭,在λ足够小时,一些系数会因此被迫缩减到0,这一过程有助于帮助我们理解数据。但是计算复杂度上却变得更加繁杂,因为绝对值的求解需要使用二次规划法。


8.4.3 前向逐步回归

为了解决岭回归,尤其是lasso回归计算过于复杂的问题,前向逐步回归算法可以更加简便地达到lasso差不多地效果。它采用了贪心策略,即每一步都尽可能减少误差。对所有初始权重设置均为1,然后每一步的决策就是对某一个权重增加或减少一个很小的值

伪代码如下:

数据标准化,使其分布满足0均值和单位方差
在每轮迭代过程中:
	设置当前最小误差lowestError为正无穷
	对每个特征:
		增大或减小:
			改变一个系数得到一个新的W
			计算新W下的误差
			如果误差Error小于当前最小误差lowestError:设置Wbest等于当前的W
		将W设置为新的Wbest

程序清单8.3

regression.py

//定义标准化函数regularize()
def regularize(xMat):
    inMat = xMat.copy()
    inMean = mean(inMat, 0)
    inVar = var(inMat, 0)
    inMat = (inMat - inMean)/inVar
    return inMat

----------------------------
//eps为迭代的步长,numIt为迭代次数
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)
    returnMat = zeros((numIt, n))
    
//为实现贪心算法,建立了两份ws的副本
    ws = zeros((n,1)); wsTest = ws.copy(); wsMax = ws.copy()
   	
   	//开始进行迭代,每次迭代结束都会输出ws
    for i in range(numIt):
        print(ws.T)
        lowestError = inf;//inf的意思是正无穷
        
//开始进行贪心算法,针对整个数据集迭代一次,即上面的for循环中的一次i
//输出结果为returnMat矩阵中的一行,1x8的数组
		//迭代8个特征,因为此数据集中n=8
        for j in range(n):
            for sign in [-1,1]:
                wsTest =ws.copy()
                wsTest[j] += eps*sign
                yTest = xMat*wsTest
                rssE = rssError(yMat.A, yTest.A)
                if rssE < lowestError:
                    lowestError = rssE
                    wsMax = wsTest
        ws = wsMax.copy()
        returnMat[i,:] = ws.T
 
 //整合全部迭代次数numIt之后,输出returnMat矩阵,规格为[numIt*8]
    return returnMat

如上所示,stageWise()是一个逐步线性回归算法的实现,与lasso法相似但是更加简单。eps作为迭代步长,numIt表示迭代次数,本例中,以[-1, 1]为振荡范围,eps*[-1, 1]得到每次迭代的振幅范围。

通过ws来保存回归系数,为了实现贪心算法,特意准备了ws的两份副本wsTest和wsMax便于使用

  • 第一个for循环迭代numIt次,最后输出numIt*n的回归系数矩阵

  • 第二个for循环迭代n次,即特征个数,输出每个样例特征对应的回归系数

  • 第三个for循环用来进行迭代步长的振荡,从而逼近最合适的回归系数

      这里用到了之前使用的rssError()函数,在8.3节中使用的预测鲍鱼年龄,以及自定义了regularize()标准化函数。
      
      lowestError = inf代码定义了误差初始值设置为**正无穷,inf表示正无穷**
    

在反复振荡之后,经过与所有误差比较后取出最小的误差保存,便是三个for循环的用意

测试程序——Console
xArr, yArr = loadDataSet('abalone.txt')
//这里设置步长eps=0.01,numIt=200
stageWise(xArr, yArr, 0.01, 200)

//设置步长eps=0.001,numIt=5000
stageWise(xArr, yArr, 0.001, 5000)

//将结果与最小二乘法比较,后者的结果通过如下代码获得:
//进行标准化和公式化处理
xMat = mat(xArr)
yMat = mat(yArr).T
xMat = regularize(xMat)
yM = mean(yMat, 0)
yMat = yMat - yM
//用最小二乘法计算回归系数
weights = standRegres(xMat, yMat.T)
weights.T

结果表明,经过5000次迭代的逐步先行回归算法和常规的最小二乘法效果类似。而使用eps=0.005,numIt=1000的表明逐步线性回归法得到了和lasso相似的结果,但是计算起来更加的简便。其优点主要在于可以帮助人们理解现有模型并且做出改进,可以在此过程中剔除不相关的特征。
例如某些回归系数在最终返回的额returnMat中自始至终都是返回0.0值为结果,那么说明该列所在的特征对目标值的结果并不会造成影响(无关因素),因此有助于做特征选择和特征收集。

最后,用于测试时,该算法每100次迭代后就可以构建出一个模型,可以使用类似10折交叉验证的方法比较模型,最终选择误差最小的模型

无论是逐步线性回归还是岭回归,这类缩减方法在模型中增加了偏差(bias),与此同时却减小了模型的方差


8.5 权衡偏差与方差

概念

偏差:模型预测值与数据之间的差异;
方差:模型之间的差异。

一旦发现模型和测量值之间存在差异,就出现了误差。通常的误差分为两种:
1、由于对复杂的过程进行简化,因此模型与测量值之间出现“噪声”或误差,例如对数据的真是过程的错误理解也会导致该结果。
2、测量过程本身也可能产生“噪声”或者问题。
上述两种误差由三个部分组成:偏差、测量误差和随机噪声

通过前述实验可知,如果降低核的大小,那么训练误差将会变小,但并非无限缩小,即在一个范围内达到最小值

正如8.2局部加权线性回归程序清算8.2结果所示,核(kernel)越小,模型的复杂度越高,当达到某个k值时,模型达到最优:即测试误差达到最小值的时候。(此处可见书P152页图8-8)

方差是可以度量的。由前几节的实验结果可知:回归系数间的差异大小也就是模型方差大小的反映(因为方差反映的就是模型间的差异;偏差反映的是预测结果和实际结果间的差异)


8.6 预测乐高玩具套装的价格

算法实践步骤:

(1)收集数据:用Google Shopping的API收集数据;
(2)准备数据:从返回的JSON数据中抽取价格;
(3)分析数据:可视化并观察数据;
(4)训练算法:构建不同的模型,采用逐步线性回归和标准线性回归;
(5)测试算法:使用交叉验证来测试不同模型,分析效果;
(6)使用算法:生成数据模型。

通过Google API抓取购物信息,以JSON格式返回产品数据信息。

实验表明,在特征数量较小的情况下,使用缩减法(如岭回归)进行10折交叉验证得出的回归系数结果与使用最小二乘法得出的结果大相径庭,且在最终的系数矩阵中,ws的值越大,则说明与线性回归方程的相关性就越高

综上可知,当特征数目较多时(如100个)使用缩减法结合交叉验证的方式能够有效提升标准线性回归方程的拟合效率,因为有利于进行特征选择。


小结

1、只要XTX的逆矩阵存在并可求,则都可以直接使用回归法。数据集上的回归方程并不一定是最佳的,可以预测yHat和原始值y的相关性来度量回归方程的好坏。

2、有时样本数<特征数,则(XTT)-1无法直接计算,因可以使用岭回归,因为其在该情况下仍能保证求得回归参数

3、与岭回归相同的缩减法还有Lasso法,但其难以求解,因此可以使用更加简便的逐步先行回归方法来求近似结果。

4、同时,缩减法还可以对模型增加偏差的同时减少方差,帮助更好地理解模型。

5、不过,当预测值与特征值之间是非线性关系时,再使用线性模型便难以拟合了。

猜你喜欢

转载自blog.csdn.net/qq_25134999/article/details/82825789
今日推荐