分类的目标变量是标称型数据。
回归与分类的不同,就在于其目标变量是连续数值型。
线性回归优缺点
优点:结果易于理解,计算上不复杂。
缺点:对非线性的数据拟合不好。
适用数据类型:数值型和标称型数据
回归的一般方法 :
1 收集数据:采用任意方法收集数据。
2 准备数据:回归需要数值型数据,标称型数据将被转成二值型数据。
3 分析数据:绘出数据的可视化二维图将有助于对数据做出理解和分析,在采用缩减法 求得新回归系数之后, 可以将新拟合线绘在图上作为对比。
训练算法:找到回归系数。
测试算法:使用幻或者预测值和数据的拟合度,来分析模型的效果。
使用算法:使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提升,因为这样可以预测连续型数据而不仅仅是离散的类别标签。
与分类使用步骤基本一致。
创建一个新的文件regression.py并添加代码:
from numpy import *
def loadDataSet(fileName): #general function to parse tab -delimited floats
numFeat = len(open(fileName).readline().split('\t')) - 1 #get number of fields
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("This matrix is singular, cannot do inverse")
return
ws = xTx.I * (xMat.T*yMat)
return ws
第一个函数 loadDataSet() 与之前的同名函数相同,打开一个用tab键分割的文本文件,仍然默认文件每行的最后一个值是目标值。第二个函数 standRegress() 用来计算最佳拟合直线。该函数首先读入x和y并将它们保存到矩阵中;然后计算,然后判断它的行列式是否为零,如果行列式为零,那么计算逆矩阵的时候将出现错误。NumPy提供一个线性代数的库linalg,其中包含很多有用的函数。可以直接调用 linalg.det() 来计算行列式。最后,如果行列式非零,计算并返回w。如果没有检查行列式是否为零就试图计算矩阵的逆,将会出现错误。NumPy的线性代数库还提供一个函数来解未知矩阵,如果使用该函数,代码ws=xTx.I*(xMat.TyMat)应写成ws=linalg.solve(xTx,xMat.TyMatT)。
使用 loadDataSet() 将从数组中得到两个数组,分别存放在x和y中。与分类算法中的类别标签类似,这里的y是目标值。
import regression
from numpy import *
xArr,yArr = regression.loadDataSet('ex0.txt')
ws = regression.standRegres(xArr,yArr)
print(xArr[0:2])
print(ws)
ws存放的就是回归系数。再用内积来预测y的时候,第一维将乘以前面的常数x0,第二维将乘以输入变量x1。因为前面假定了x0=1,所以最终会得到 y=ws[0]+ws[1]*X1。 这里的y实际是预测出的,为了和真实的y值区分开,将其记为yHat。下面使用新的ws值计算yHat,绘制数据集散点图和最佳拟合直线图:
import regression
from numpy import *
import matplotlib.pyplot as plt
xArr,yArr = regression.loadDataSet('ex0.txt')
ws = regression.standRegres(xArr,yArr)
xMat = mat(xArr)
yMat = mat(yArr)
yHat = xMat*ws
fig = plt.figure()
ax = fig.add_subplot(111)
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)
plt.show()
几乎任意数据集都可以用上述的方法建立模型,那么,如何判断模型的好坏呢?比较下面两张图,如果在两个数据集上分别作线性回归,将得到完全一样的模型(拟合直线)。显然两个数据是不一样的,那么我们该如何比较其效果呢?计算这两个序列的相关系数可以判断预测值yHat序列和真实值y序列的匹配程度。
线性回归的一个问题是有可能出现欠拟合现象,因为它求的是具有小均方误差的无偏估计。显而易见,如果模型出现欠拟合,将不能取得好的预测效果。所以有些方法允许在估计中引入一些偏差,从而降低预测的均方误差。
其中的一个方法是局部加权线性回归(Locally Weighted Linear Regression,LWLR)。在该方法中,我们给待预测点附近的每个点赋予一定的权重。与kNN一样,这种算法每次预测均需要事先选取出对应的数据子集。该算法解出回归系数W的形式如下:
w ^ = ( X T W X ) − 1 X T W y \widehat{w}=\left(X^{T} W X\right)^{-1} X^{T} W y w =(XTWX)−1XTWy
其中W是一个矩阵,这个公式跟上面推导的公式的区别就在于W,它用来给每个点赋予权重。
局部加权线性回归使用“核”(与支持向量机中的核类似)来对附近的点赋予更高的权重。核的类型可以自由选择,最常用的核就是高斯核,高斯核对应的权重如下:
w ( i , i ) = exp ( ∣ x ( i ) − x ∣ − 2 k 2 ) w(i, i)=\exp \left(\frac{\left|x^{(i)}-x\right|}{-2 k^{2}}\right) w(i,i)=exp(−2k2∣ ∣x(i)−x∣ ∣)
如上所示,我们可以看到参数k与权重的关系。
将下面的代码加入regression.py中
def lwlr(testPoint,xArr,yArr,k=1.0):
xMat = mat(xArr); yMat = mat(yArr).T
m = shape(xMat)[0]
weights = mat(eye((m)))#eye(m,n)产生mxn的单位矩阵
for j in range(m): #next 2 lines create weights matrix
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 = xTx.I * (xMat.T * (weights * yMat))
return testPoint * ws
def lwlrTest(testArr,xArr,yArr,k=1.0): #loops over all the data points and applies lwlr to each one
m = shape(testArr)[0]
yHat = zeros(m)
for i in range(m):
yHat[i] = lwlr(testArr[i],xArr,yArr,k)
return yHat
上述代码的作用是,给定x空间中的任意一点,计算出对应的预测值yHat。函数 lwlr() 的开头与之前的 standRegres() 类似,读入数据并创建所需矩阵,之后创建对角权重矩阵weights。权重矩阵是一个方阵,阶数等于样本点个数。也就是说,该矩阵为每个样本点初始化了一个权重。接着,算法将遍历数据集,计算每个样本点对应的权重值:随着样本点与待预测点距离的递增,权重将以指数级衰减。输入参数k控制衰减的速度。与之前的函数 stand-Regress() 一样,在权重矩阵计算完毕后,就可以得到对回归系数ws的一个估计。
另一个函数是 lwlrTest() ,用于为数据集中每个点调 lwlr(),这有助于求解k的大小。
import regression
from numpy import *
import matplotlib.pyplot as plt
xArr,yArr = regression.loadDataSet('ex0.txt')
# 对单点进行估计
print(yArr[0])
print(regression.lwlr(xArr[0],xArr,yArr,1.0))
print(regression.lwlr(xArr[0],xArr,yArr,0.001))
# 为了得到数据集里所有点估计,可以调用lwlrTest()函数
yHat = regression.lwlrTest(xArr,xArr,yArr,1.0)
# 绘出估计值和原始值,查看yHat拟合效果。绘图函数需要对数据点按序排列,首先对xArr排序:
xMat = mat(xArr)
srtInd = xMat[:,1].argsort(0)
xSort = xMat[srtInd][:,0,:]
# Matplotlib绘图
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(xSort[:,1],yHat[srtInd])
ax.scatter(xMat[:,1].flatten().A[0],mat(yArr).T.flatten().A[0],s=2,c='red')
plt.show()
上图给出了k=0.1、0.01、0.003的结果图。当k=1.0时权重很大,如同将所有的数据视为等权重,得出的最佳拟合直线与标准的回归一致。使用k=0.01得到了非常好的效果,抓住了数据的潜在模式。k=0.003纳入了过多的噪声点,拟合的直线与数据点过于接近。所以三者分别是欠拟合、正常、过拟合的图像。
鲍鱼年龄可以从鲍鱼壳的层数推算得到。
使用局部加权回归得到拟合曲线,之后比较预测精度。
在regression.py中加入下列代码:
def rssError(yArr,yHatArr): #yArr and yHatArr both need to be arrays
return ((yArr-yHatArr)**2).sum()
为了分析预测误差的大小,可以用 rssError() 计算出这一指标:
import regression
abX,abY = regression.loadDataSet('abalone.txt')
yHat01 = regression.lwlrTest(abX[0:99] ,abX[0:99],abY[0:99],0.1)
yHat1=regression. lwlrTest(abX[0:99],abX[0:99],abY[0:99],1)
yHat10=regression.lwlrTest(abX[0:99],abX[0:99],abY[0:99],10)
print(regression.rssError(abY[0:99],yHat01.T))
print(regression.rssError(abY[0:99],yHat1.T))
print(regression.rssError(abY[0:99],yHat10.T))
使用较小的核将得到较低的误差。但是,使用最小的核将造成过拟合,对新数据不一定能达到最好的预测效果。
import regression
from numpy import *
import matplotlib.pyplot as plt
abX,abY = regression.loadDataSet('abalone.txt')
yHat01 = regression.lwlrTest(abX[100:199],abX[0:99],abY[0:99],0.1)
print(regression.rssError(abY[100:199],yHat01.T))
yHat1 = regression. lwlrTest(abX[100:199],abX[0:99],abY[0:99],1)
print(regression.rssError(abY[100:199],yHat1.T))
yHat10 = regression.lwlrTest(abX[100:199],abX[0:99],abY[0:99],10)
print(regression.rssError(abY[100:199],yHat10.T))
ws = regression.standRegres(abX[0:99],abY[0:99])
yHat = mat(abX[100:199]) * ws
print(regression.rssError(abY[100:199],yHat.T.A))
三个参数中,核大小等于10时的测试误差最小,但在训练集上的误差却是最大的。
简单线性回归达到了与局部加权线性回归类似的效果。
果特征比样本点还多(n>m),也就是说输入数据的矩阵X是满秩矩阵。非满秩矩阵在求逆时会出现问题,即在计算的时候会出错。此不可以使用线性回归和之前的方法来做预测。为了解决这个问题,统计学家引入了岭回归的概念。
简单说来,岭回归就是在矩阵X的转置X中加入一个RI,进而能对 X T X + λ I \mathbf{X}^{T} \mathbf{X}+\lambda I XTX+λI求逆。其中矩阵I II是一个m*n的单位矩阵,λ \lambdaλ是一个用户定义的数值。这样,回归系数的计算公式就变成:
w ^ = ( X T X + λ I ) − 1 X T y \hat{w}=\left(\boldsymbol{X}^{\mathrm{T}} \boldsymbol{X}+\lambda \boldsymbol{I}\right)^{-1} \boldsymbol{X}^{\mathrm{T}} \boldsymbol{y} w^=(XTX+λI)−1XTy
通过引入λ来限制所有w之和,通过引入该惩罚项,能够减少不重要的参数,这个技术在统计学中也叫做缩减。通过预测误差最小化得到λ:数据获取之后,首先抽一部分数据用于测试,剩余的作为训练集用于训练参数w ww。训练完毕后在测试集上测试预测性能。通过选取不同的λ来重复上述测试过程,最终得到一个使预测误差最小的λ。
岭回归中的岭是什么?
岭回归使用了单位矩阵乘以常量λ,我们观察其中的单位矩阵I,可以看到值1贯穿整个对角线,其余元素全是0。形象地,在0构成的平面上有一条1组成的“岭”,这就是岭回归中“岭”的由来。
在regression.py中添加下列代码:
def ridgeRegres(xMat, yMat, lam = 0.2):
"""
函数说明:岭回归
Parameters:
xMat - x数据集
yMat - y数据集
lam - 缩减系数
Returns:
ws - 回归系数
"""
xTx = xMat.T * xMat
denom = xTx + np.eye(np.shape(xMat)[1]) * lam
if np.linalg.det(denom) == 0.0:
print("矩阵为奇异矩阵,不能转置")
return
ws = denom.I * (xMat.T * yMat)
return ws
def ridgeTest(xArr, yArr):
"""
函数说明:岭回归测试
Parameters:
xMat - x数据集
yMat - y数据集
Returns:
wMat - 回归系数矩阵
"""
xMat = np.mat(xArr); yMat = np.mat(yArr).T
#数据标准化
yMean = np.mean(yMat, axis = 0) #行与行操作,求均值,也就是求最后一列训练集真实值的平均值
yMat = yMat - yMean #数据减去均值,每一行y结果减去平均值
xMeans = np.mean(xMat, axis = 0) #行与行操作,求均值
xVar = np.var(xMat, axis = 0) #行与行操作,求方差
xMat = (xMat - xMeans) / xVar #数据减去均值除以方差实现标准化
numTestPts = 30 #30个不同的lambda测试
wMat = np.zeros((numTestPts, np.shape(xMat)[1])) #初始回归系数矩阵
for i in range(numTestPts): #改变lambda计算回归系数
ws = ridgeRegres(xMat, yMat, np.exp(i - 10)) #lambda以e的指数变化,最初是一个非常小的数,
wMat[i, :] = ws.T #计算回归系数矩阵
return wMat
def plotwMat():
"""
函数说明:绘制岭回归系数矩阵
"""
abX, abY = loadDataSet('abalone.txt')
redgeWeights = ridgeTest(abX, abY)
print(redgeWeights)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(redgeWeights)
ax_title_text = ax.set_title(u'The relationship between log(lambada) and regression coefficient')
ax_xlabel_text = ax.set_xlabel(u'log(lambada)')
ax_ylabel_text = ax.set_ylabel(u'regression coefficient')
plt.setp(ax_title_text, size = 20, weight = 'bold', color = 'red')
plt.setp(ax_xlabel_text, size = 10, weight = 'bold', color = 'black')
plt.setp(ax_ylabel_text, size = 10, weight = 'bold', color = 'black')
plt.show()
测试代码如下所示
import regression
regression.plotwMat()
实验结果
实验分析:在最左边,即λ最小时,可以得到所有系数的原始值(与线性回归一致);而在右边,系数全部缩减成0;在中间部分的某个位置,将会得到最好的预测结果。想要得到最佳的λ参数,可以使用交叉验证的方式获得。
不难证明,在增加如下约束时,普通的最小二乘法回归会得到与岭回归的一样的公式:
∑ k = 1 n w k 2 ⩽ λ \sum_{k=1}^{n} w_{k}^{2} \leqslant \lambda k=1∑nwk2⩽λ
上式限定了所有回归系数的平方和不能大于λ。使用普通的最小二乘法回归在当两个或更多的特征相关时,可能会得出一个很大的正系数和一个很大的负系数。正是因为上述限制条件的存在,使用岭回归可以避免这个问题。
与岭回归类似,另一个缩减方法lasso也对回归系数做了限定,对应的约束条件如下:
∑ k = 1 n ∣ w k ∣ ⩽ λ \sum_{k=1}^{n}\left|w_{k}\right| \leqslant \lambda k=1∑n∣wk∣⩽λ
不通电在于,这个约束条件使用绝对值取代了平方和。虽然约束形式只是稍作变化,结果却大相径庭:在λ足够小的时候,一些系数会因此被迫缩减到0,这个特性可以帮助我们更好地理解数据。这两个约束条件在公式上看起来相差无几,但细微的变化却极大地增加了计算复杂度(为了在这个新的约束条件下解除回归系数,需要使用二次规划算法)。
前向逐步回归算法可以得到与lasso差不多的效果,但更加简单。它属于一种贪心算法,即每一步都尽可能减少误差。一开始,所有的权重都设为1,然后每一步所做的决策是对某个权重增加或减少一个很小的值。伪代码如下:
在regression.py中添加下列代码:
def regularize(xMat, yMat):
"""
函数说明:数据标准化
Parameters:
xMat - x数据集
yMat - y数据集
Returns:
inxMat - 标准化后的x数据集
inyMat - 标准化后的y数据集
"""
inxMat = xMat.copy() # 数据拷贝
inyMat = yMat.copy()
yMean = np.mean(yMat, 0) # 行与行操作,求均值
inyMat = yMat - yMean # 数据减去均值
inMeans = np.mean(inxMat, 0) # 行与行操作,求均值
inVar = np.var(inxMat, 0) # 行与行操作,求方差
inxMat = (inxMat - inMeans) / inVar # 数据减去均值除以方差实现标准化
return inxMat, inyMat
def stageWise(xArr, yArr, eps=0.01, numIt=100):
"""
函数说明:前向逐步线性回归
Parameters:
xArr - x输入数据
yArr - y预测数据
eps - 每次迭代需要调整的步长
numIt - 迭代次数
Returns:
returnMat - numIt次迭代的回归系数矩阵
"""
xMat = np.mat(xArr);
yMat = np.mat(yArr).T # 数据集
xMat, yMat = regularize(xMat, yMat) # 数据标准化
m, n = np.shape(xMat)
returnMat = np.zeros((numIt, n)) # 初始化numIt次迭代的回归系数矩阵
ws = np.zeros((n, 1)) # 初始化回归系数矩阵
wsTest = ws.copy()
wsMax = ws.copy()
for i in range(numIt): # 迭代numIt次
# print(ws.T) #打印当前回归系数矩阵
lowestError = float('inf'); # 正无穷
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次迭代的回归系数矩阵
return returnMat
def plotstageWiseMat():
"""
函数说明:绘制岭回归系数矩阵
"""
xArr, yArr = loadDataSet('abalone.txt')
returnMat = stageWise(xArr, yArr, 0.005, 1000)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(returnMat)
ax_title_text = ax.set_title(u'The relationship between iterations and regression coefficient')
ax_xlabel_text = ax.set_xlabel(u'iterations')
ax_ylabel_text = ax.set_ylabel(u'regression coefficient')
plt.setp(ax_title_text, size=15, weight='bold', color='red')
plt.setp(ax_xlabel_text, size=10, weight='bold', color='black')
plt.setp(ax_ylabel_text, size=10, weight='bold', color='black')
plt.show()
实验分析
逐步线性回归算法的优点在于它可以帮助人们理解现有的模型并做出改进。当构建了一个模型后,可以运行该算法找出重要的特征,这样就有可能及时停止对那些不重要特征的收集。最后,如果用于测试,该算法每100次迭代后就可以构建出一个模型,可以使用类似于10折交叉验证的方法比较这些模型,最终选择使误差最小的模型。
任何时候,一旦发现模型和测量值之间存在差异, 就说出现了误差。当考虑模型中的“ 噪声“或者说误差时,必须考虑其来源。你可能会对复杂的过程进行简化,这将导致在模型和测量值之间出现“ 噪声”或误差,若无法理解数据的真实生成过程,也会导致差异的发生。另外,测量过程本身也可能产生“ 噪声”或者问题。
与分类一样,回归也是预测目标值的过程。回归与分类的不同点在于,前者预测连续型变量,而后者预测离散型变量。
在回归方程里,求得特征对应的最佳回归系数的方法是最小化误差的平方和。
数据集上计算出的回归方程并不一定意味着它是最佳的,可以便用预测值yHat和原始值y的相关性来度量回归方程的好坏
缩减法还可以看做是对一个模型增加偏差的同时减少方差。偏差方差折中是一个重要的概念 ,可以帮助我们理解现有模型并做出改进,从而得到更好的模型。
开始的过程我感觉和第四章逻辑回归有些类似。第四章是找到回归系数,并以此分类。相同的是都在找回归系数,不同的是,一个是为了划分数据,一个是为了预测数据