Merge pull request #157 from chenyyx/master

修改第八章回归的文档
This commit is contained in:
Joy yx
2017-09-15 23:40:52 +08:00
committed by GitHub
11 changed files with 673 additions and 102 deletions

View File

@@ -522,134 +522,257 @@ def regression3():
![lasso_2](../images/8.Regression/LinearR_14.png)
唯一的不同点在于,这个约束条件使用绝对值取代了平方和。虽然约束形式只是稍作变化,结果却大相径庭: 在 λ 足够小的时候,一些系数会因此被迫缩减到 0.这个特性可以帮助我们更好地理解数据。
#### 4.3、前向逐步回归
前向逐步回归算法可以得到与 lasso 差不多的效果,但更加简单。它属于一种贪心算法,即每一步都尽可能减少误差。一开始,所有权重都设置为 1然后每一步所做的决策是对某个权重增加或减少一个很小的值。
## 岭回归和逐步线性回归
> 缩减系数来“理解”数据
伪代码如下:
```
如果数据的特征比样本点还多应该怎么办?是否还可以使用线性回归和之前的方法来做预测?答案是否定的,即不能再使用前面介绍的方法。
这是因为在计算 (x^TX)^(-1) 的时候会出错。
如果特征比样本点还多(n > m)也就是说输入数据的矩阵X不是满秩矩阵。非满秩矩阵在求逆时会出现问题。
为了解决这个问题,统计学家引入了岭回归(ridge regression)的概念,这就是本节将要介绍的第一种缩减方法。
数据标准化,使其分布满足 0 均值 和单位方差
在每轮迭代过程中:
设置当前最小误差 lowestError 为正无穷
对每个特征:
增大或缩小:
改变一个系数得到一个新的 w
计算新 w 下的误差
如果误差 Error 小于当前最小误差 lowestError: 设置 Wbest 等于当前的 W
将 W 设置为新的 Wbest
```
> 岭回归
##### 4.3.1、前向逐步回归 原始代码
```
简单来说,岭回归就是在矩阵 X^TX 上加一个 λI 从而使得矩阵非奇异,进而能对 X^TX+λI 求逆。其中矩阵I是一个 m*m 的单位矩阵,
对角线上元素全为1其他元素全为0。而λ是一个用户定义的数值后面会做介绍。在这种情况下回归系数的计算公式将变成
w = (X^TX + λI)^(-1)X^Ty
岭回归最先用来处理特征数多于样本数的情况现在也用于在估计中加入偏差从而得到更好的估计。这里通过引入λ来限制了所有w之和通过引入该惩罚项
能够减少不重要的参数,这个技术在统计学中也叫作缩减(shrinkage)。
```python
def stageWise(xArr,yArr,eps=0.01,numIt=100):
xMat = mat(xArr); yMat=mat(yArr).T
yMean = mean(yMat,0)
yMat = yMat - yMean # 也可以规则化ys但会得到更小的coef
xMat = regularize(xMat)
m,n=shape(xMat)
#returnMat = zeros((numIt,n)) # 测试代码删除
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
yTest = xMat*wsTest
rssE = rssError(yMat.A,yTest.A)
if rssE < lowestError:
lowestError = rssE
wsMax = wsTest
ws = wsMax.copy()
returnMat[i,:]=ws.T
return returnMat
#test for stageWise
def regression4():
xArr,yArr=loadDataSet("input/8.Regression/abalone.txt")
stageWise(xArr,yArr,0.01,200)
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)
```
> 岭回归中的岭是什么
##### 4.3.2、逐步线性回归在鲍鱼数据集上的运行效果
![逐步线性回归运行效果](../images/8.Regression/LinearR_15.png)
逐步线性回归算法的主要优点在于它可以帮助人们理解现有的模型并作出改进。当构建了一个模型后可以运行该算法找出重要的特征这样就有可能及时停止对那些不重要特征的手机。最后如果用于测试该算法每100次迭代后就可以构建出一个模型可以使用类似于10折交叉验证的方法比较这些模型最终选择使误差最小的模型。
#### 4.4、小结
当应用缩减方法如逐步线性回归或岭回归模型也就增加了偏差bias与此同时却见笑了模型的方差。
### 5、回归 项目案例
#### 项目案例1: 预测乐高玩具套装的价格
##### 项目概述
Dangler 喜欢为乐高套装估价,我们用回归技术来帮助他建立一个预测模型。
##### 开发流程
```
岭回归使用了单位矩阵乘以常量λ我们观察其中的单位矩阵I可以看到值1贯穿整个对角线其余元素全是0.形象地在0构成的平面上有一条1组成的“岭”
这就是岭回归中的“岭”的由来
```
> 岭回归示例图
![岭回归示例图](/images/8.Regression/岭回归示例图.png "岭回归示例图")
> 前向逐步回归简介
```
前向逐步回归算法可以得到与 lasso 差不多的效果,但更加简单。它属于一种贪心算法,即每一步都尽可能减少误差。
一开始所有的权重都设为1然后每一步所做的决策是对某个权重增加或减少一个很小的值。
(1) 收集数据:用 Google Shopping 的API收集数据。
(2) 准备数据从返回的JSON数据中抽取价格
(3) 分析数据:可视化并观察数据。
(4) 训练算法:构建不同的模型,采用逐步线性回归和直接的线性回归模型。
(5) 测试算法:使用交叉验证来测试不同的模型,分析哪个效果最好。
(6) 使用算法:这次练习的目标就是生成数据模型。
```
> 前向逐步回归伪代码
> 收集数据: 使用 Google 购物的 API
```
数据标准化使其分布满足0均值和单位方差
在每轮迭代过程中:
设置当前最小误差lowestError为正无穷
对每个特征:
增大或缩小:
改变一个系数得到一个新的w
计算新w下的误差
如果误差Error小于当前最小误差lowestError设置Wbest等于当前的w
将W设置为新的Wbest
由于 Google 提供的 api 失效,我们只能自己下载咯,将数据存储在了 input 文件夹下的 setHtml 文件夹下
> 准备数据: 从返回的 JSON 数据中抽取价格
因为我们这里不是在线的,就不再是 JSON 了,我们直接解析线下的网页,得到我们想要的数据。
> 分析数据: 可视化并观察数据
这里我们将解析得到的数据打印出来,然后观察数据。
> 训练算法: 构建不同的模型
```python
from numpy import *
from bs4 import BeautifulSoup
# 从页面读取数据生成retX和retY列表
def scrapePage(retX, retY, inFile, yr, numPce, origPrc):
# 打开并读取HTML文件
fr = open(inFile)
soup = BeautifulSoup(fr.read())
i=1
# 根据HTML页面结构进行解析
currentRow = soup.findAll('table', r="%d" % i)
while(len(currentRow)!=0):
currentRow = soup.findAll('table', r="%d" % i)
title = currentRow[0].findAll('a')[1].text
lwrTitle = title.lower()
# 查找是否有全新标签
if (lwrTitle.find('new') > -1) or (lwrTitle.find('nisb') > -1):
newFlag = 1.0
else:
newFlag = 0.0
# 查找是否已经标志出售,我们只收集已出售的数据
soldUnicde = currentRow[0].findAll('td')[3].findAll('span')
if len(soldUnicde)==0:
print "item #%d did not sell" % i
else:
# 解析页面获取当前价格
soldPrice = currentRow[0].findAll('td')[4]
priceStr = soldPrice.text
priceStr = priceStr.replace('$','') #strips out $
priceStr = priceStr.replace(',','') #strips out ,
if len(soldPrice)>1:
priceStr = priceStr.replace('Free shipping', '')
sellingPrice = float(priceStr)
# 去掉不完整的套装价格
if sellingPrice > origPrc * 0.5:
print "%d\t%d\t%d\t%f\t%f" % (yr,numPce,newFlag,origPrc, sellingPrice)
retX.append([yr, numPce, newFlag, origPrc])
retY.append(sellingPrice)
i += 1
currentRow = soup.findAll('table', r="%d" % i)
# 依次读取六种乐高套装的数据,并生成数据矩阵
def setDataCollect(retX, retY):
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego8288.html', 2006, 800, 49.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10030.html', 2002, 3096, 269.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10179.html', 2007, 5195, 499.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10181.html', 2007, 3428, 199.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10189.html', 2008, 5922, 299.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10196.html', 2009, 3263, 249.99)
```
```
贪心算法在所有特征上运行两次for循环分别计算增加或减少该特征对误差的影响。这里使用的是平方误差通过之前的函数rssError()得到。
该误差初始值设为正无穷,经过与所有的误差比较后取最小的误差。整个过程循环迭代进行。
> 测试算法:使用交叉验证来测试不同的模型,分析哪个效果最好
```python
# 交叉验证测试岭回归
def crossValidation(xArr,yArr,numVal=10):
# 获得数据点个数xArr和yArr具有相同长度
m = len(yArr)
indexList = range(m)
errorMat = zeros((numVal,30))
# 主循环 交叉验证循环
for i in range(numVal):
# 随机拆分数据将数据分为训练集90%和测试集10%
trainX=[]; trainY=[]
testX = []; testY = []
# 对数据进行混洗操作
random.shuffle(indexList)
# 切分训练集和测试集
for j in range(m):
if j < m*0.9:
trainX.append(xArr[indexList[j]])
trainY.append(yArr[indexList[j]])
else:
testX.append(xArr[indexList[j]])
testY.append(yArr[indexList[j]])
# 获得回归系数矩阵
wMat = ridgeTest(trainX,trainY)
# 循环遍历矩阵中的30组回归系数
for k in range(30):
# 读取训练集和数据集
matTestX = mat(testX); matTrainX=mat(trainX)
# 对数据进行标准化
meanTrain = mean(matTrainX,0)
varTrain = var(matTrainX,0)
matTestX = (matTestX-meanTrain)/varTrain
# 测试回归效果并存储
yEst = matTestX * mat(wMat[k,:]).T + mean(trainY)
# 计算误差
errorMat[i,k] = ((yEst.T.A-array(testY))**2).sum()
# 计算误差估计值的均值
meanErrors = mean(errorMat,0)
minMean = float(min(meanErrors))
bestWeights = wMat[nonzero(meanErrors==minMean)]
# 不要使用标准化的数据,需要对数据进行还原来得到输出结果
xMat = mat(xArr); yMat=mat(yArr).T
meanX = mean(xMat,0); varX = var(xMat,0)
unReg = bestWeights/varX
# 输出构建的模型
print "the best model from Ridge Regression is:\n",unReg
print "with constant term: ",-1*sum(multiply(meanX,unReg)) + mean(yMat)
# predict for lego's price
def regression5():
lgX = []
lgY = []
setDataCollect(lgX, lgY)
crossValidation(lgX, lgY, 10)
```
> 逐步线性回归示例图
> 使用算法:这次练习的目标就是生成数据模型
![逐步线性回归示例图](/images/8.Regression/逐步线性回归示例图.png "逐步线性回归示例图")
### 6、附加 权衡偏差和方差
## 预测鲍鱼年龄和玩具售价
任何时候,一旦发现模型和测量值之间存在差异,就说出现了误差。当考虑模型中的 “噪声” 或者说误差时,必须考虑其来源。你可能会对复杂的过程进行简化,这将导致在模型和测量值之间出现 “噪声” 或误差,若无法理解数据的真实生成过程,也会导致差异的产生。另外,测量过程本身也可能产生 “噪声” 或者问题。下面我们举一个例子,我们使用 `线性回归``局部加权线性回归` 处理过一个从文件导入的二维数据。
> 示例:预测鲍鱼的年龄
![生成公式](../images/8.Regression/LinearR_16.png)
```
鲍鱼年龄可以从鲍鱼壳的层数推算得到。
为了分析预测误差的大小可以用函数rssError()计算出这一指标,下面是三种不同的预测误差:
>>> regression.rssError(abY[0:99],yHat01.T)
56.842594430533545
>>> regression.rssError(abY[0:99],yHat1.T)
429.89056187006685
>>> regression.rssError(abY[0:99],yHat10.T)
549.11817088257692
可以看到,使用较小的核将得到较低的误差。那么,为什么不在所有数据集上都使用最小的核呢?
这是因为使用最小的核将造成过拟合,对新数据不一定能达到最好的预测效果。
其中的 N(0, 1) 是一个均值为 0、方差为 1 的正态分布。我们尝试过禁用一条直线来拟合上述数据。不难想到,直线所能得到的最佳拟合应该是 3.0+1.7x 这一部分。这样的话,误差部分就是 0.1sin(30x)+0.06N(0, 1) 。在上面,我们使用了局部加权线性回归来试图捕捉数据背后的结构。该结构拟合起来有一定的难度,因此我们测试了多组不同的局部权重来找到具有最小测试误差的解。
简单线性回归达到了与局部线性回归类似的效果。这也表明一点必须在未知数据上比较效果才能选取到最佳模型。那么最佳的核大小是10吗
或许是但如果想得到最好的效果应该用10个不同的样本集做10次测试来比较结果。
```
下图给出了训练误差和测试误差的曲线图,上面的曲面就是测试误差,下面的曲线是训练误差。我们根据 预测鲍鱼年龄 的实验知道: 如果降低核的大小,那么训练误差将变小。从下图开看,从左到右就表示了核逐渐减小的过程。
> 示例:预测乐高玩具套装的价格
![偏差方差图](../images/8.Regression/LinearR_17.png)
> 用回归法预测乐高套装的价格的一般过程
一般认为,上述两种误差由三个部分组成: 偏差、测量误差和随机噪声。局部加权线性回归 和 预测鲍鱼年龄 中,我们通过引入了三个越来越小的核来不断增大模型的方差。
```
(1) 收集数据:用 Google Shopping 的API收集数据。
(2) 准备数据从返回的JSON数据中抽取价格。
(3) 分析数据:可视化并观察数据。
(4) 训练算法:构建不同的模型,采用逐步线性回归和直接的线性回归模型。
(5) 测试算法:使用交叉验证来测试不同的模型,分析哪个效果最好。
(6) 使用算法:这次练习的目标就是生成数据模型。
```
在缩减系数来“理解”数据这一节中,我们介绍了缩减法,可以将一些系数缩减成很小的值或直接缩减为 0 ,这是一个增大模型偏差的例子。通过把一些特征的回归系数缩减到 0 ,同时也就减小了模型的复杂度。例子中有 8 个特征,消除其中两个后不仅使模型更易理解,同时还降低了预测误差。对照上图,左侧是参数缩减过于严厉的结果,而右侧是无缩减的效果。
## 预测数值型数据:回归 小结
方差是可以度量的。如果从鲍鱼数据中取一个随机样本集(例如取其中 100 个数据)并用线性模型拟合,将会得到一组回归系数。同理,再取出另一组随机样本集并拟合,将会得到另一组回归系数。这些系数间的差异大小也就是模型方差的反映。
与分类一样,回归也是预测目标值的过程。回归与分类的不同点在于,前者预测连续型变量,而后者预测离散型变量。回归是统计学中最有力的的工具之一。
在回归方程里求得特征对应的最佳回归系数的方法是最小化误差的平方和。给定输入矩阵x如果 x^Tx的逆存在并可以求得的话回归法都可以直接使用。
数据集上计算出的回归方程并不一定意味着它是最佳的可以使用预测值yHat和原始值y的相关性来度量回归方程的好坏。
当数据的样本数比特征数还少的时候矩阵x^Tx的逆不能直接计算。即便当样本数比特征数多时x^Tx的逆仍有可能无法直接计算这是因为特征有可能高度相关。
这时可以考虑使用岭回归因为当x^Tx的逆不能计算时它仍保证能求得回归系数。
岭回归是缩减法的一种相当于对回归系数的大小施加了限制。另一种很好的缩减法是lasso。Lasso难以理解但可以使用计算简便的逐步线性回归方法来求得近似结果。
缩减法还可以看做是对一个模型增加偏差的同时减少方差。偏差方差折中是一个重要的概念,可以帮助我们理解现有模型并做出改进,从而得到最好的模型。
* * *

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -284,7 +284,11 @@ def stageWise(xArr,yArr,eps=0.01,numIt=100):
# currentRow = soup.findAll('table', r="%d" % i)
# fw.close()
'''
#--------------------------------------------------------------
# 预测乐高玩具套装的价格 ------ 最初的版本,因为现在 google 的 api 变化,无法获取数据
# 故改为了下边的样子,但是需要安装一个 beautifulSoup 这个第三方爬虫库,安装很简单,见下边
'''
from time import sleep
import json
import urllib2
@@ -329,7 +333,7 @@ def crossValidation(xArr,yArr,numVal=10):
#基于indexList中的前90%的值创建训练集
if j < m*0.9:
trainX.append(xArr[indexList[j]])
trainY.append(yArr[indexList[j]])
gt56 trainY.append(yArr[indexList[j]])
else:
testX.append(xArr[indexList[j]])
testY.append(yArr[indexList[j]])
@@ -357,10 +361,126 @@ def crossValidation(xArr,yArr,numVal=10):
# ----------------------------------------------------------------------------
# 预测乐高玩具套装的价格 可运行版本,我们把乐高数据存储到了我们的 input 文件夹下,使用 beautifulSoup 爬去一下内容
# 前提:安装 BeautifulSoup 第三方爬虫库,步骤如下
# 在这个页面 https://www.crummy.com/software/BeautifulSoup/bs4/download/4.4/ 下载beautifulsoup4-4.4.1.tar.gz
# 将下载文件解压,使用 windows 版本的 cmd 命令行,进入解压的包,输入以下两行命令即可完成安装
# python setup.py build
# python setup.py install
#
from numpy import *
from bs4 import BeautifulSoup
# 从页面读取数据生成retX和retY列表
def scrapePage(retX, retY, inFile, yr, numPce, origPrc):
# 打开并读取HTML文件
fr = open(inFile)
soup = BeautifulSoup(fr.read())
i=1
# 根据HTML页面结构进行解析
currentRow = soup.findAll('table', r="%d" % i)
while(len(currentRow)!=0):
currentRow = soup.findAll('table', r="%d" % i)
title = currentRow[0].findAll('a')[1].text
lwrTitle = title.lower()
# 查找是否有全新标签
if (lwrTitle.find('new') > -1) or (lwrTitle.find('nisb') > -1):
newFlag = 1.0
else:
newFlag = 0.0
# 查找是否已经标志出售,我们只收集已出售的数据
soldUnicde = currentRow[0].findAll('td')[3].findAll('span')
if len(soldUnicde)==0:
print "item #%d did not sell" % i
else:
# 解析页面获取当前价格
soldPrice = currentRow[0].findAll('td')[4]
priceStr = soldPrice.text
priceStr = priceStr.replace('$','') #strips out $
priceStr = priceStr.replace(',','') #strips out ,
if len(soldPrice)>1:
priceStr = priceStr.replace('Free shipping', '')
sellingPrice = float(priceStr)
# 去掉不完整的套装价格
if sellingPrice > origPrc * 0.5:
print "%d\t%d\t%d\t%f\t%f" % (yr,numPce,newFlag,origPrc, sellingPrice)
retX.append([yr, numPce, newFlag, origPrc])
retY.append(sellingPrice)
i += 1
currentRow = soup.findAll('table', r="%d" % i)
# 依次读取六种乐高套装的数据,并生成数据矩阵
def setDataCollect(retX, retY):
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego8288.html', 2006, 800, 49.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10030.html', 2002, 3096, 269.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10179.html', 2007, 5195, 499.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10181.html', 2007, 3428, 199.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10189.html', 2008, 5922, 299.99)
scrapePage(retX, retY, 'input/8.Regression/setHtml/lego10196.html', 2009, 3263, 249.99)
# 交叉验证测试岭回归
def crossValidation(xArr,yArr,numVal=10):
# 获得数据点个数xArr和yArr具有相同长度
m = len(yArr)
indexList = range(m)
errorMat = zeros((numVal,30))
# 主循环 交叉验证循环
for i in range(numVal):
# 随机拆分数据将数据分为训练集90%和测试集10%
trainX=[]; trainY=[]
testX = []; testY = []
# 对数据进行混洗操作
random.shuffle(indexList)
# 切分训练集和测试集
for j in range(m):
if j < m*0.9:
trainX.append(xArr[indexList[j]])
trainY.append(yArr[indexList[j]])
else:
testX.append(xArr[indexList[j]])
testY.append(yArr[indexList[j]])
# 获得回归系数矩阵
wMat = ridgeTest(trainX,trainY)
# 循环遍历矩阵中的30组回归系数
for k in range(30):
# 读取训练集和数据集
matTestX = mat(testX); matTrainX=mat(trainX)
# 对数据进行标准化
meanTrain = mean(matTrainX,0)
varTrain = var(matTrainX,0)
matTestX = (matTestX-meanTrain)/varTrain
# 测试回归效果并存储
yEst = matTestX * mat(wMat[k,:]).T + mean(trainY)
# 计算误差
errorMat[i,k] = ((yEst.T.A-array(testY))**2).sum()
# 计算误差估计值的均值
meanErrors = mean(errorMat,0)
minMean = float(min(meanErrors))
bestWeights = wMat[nonzero(meanErrors==minMean)]
# 不要使用标准化的数据,需要对数据进行还原来得到输出结果
xMat = mat(xArr); yMat=mat(yArr).T
meanX = mean(xMat,0); varX = var(xMat,0)
unReg = bestWeights/varX
# 输出构建的模型
print "the best model from Ridge Regression is:\n",unReg
print "with constant term: ",-1*sum(multiply(meanX,unReg)) + mean(yMat)
@@ -453,8 +573,18 @@ def regression4():
weights = standRegres(xMat, yMat.T)
print (weights.T)
# predict for lego's price
def regression5():
lgX = []
lgY = []
setDataCollect(lgX, lgY)
crossValidation(lgX, lgY, 10)
if __name__ == "__main__":
# regression1()
regression2()
# regression2()
# regression3()
# regression4()
regression5()