机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)

前言:

   上次写过一篇关于贝叶斯概率论的数学,最近时间比较紧,coding的任务比较重,不过还是抽空看了一些机器学习的书和视频,其中很推荐两个:一个是stanford的machine learning公开课,在verycd可下载,可惜没有翻译。不过还是可以看。另外一个是prml-pattern recognition and machine learning, Bishop的一部反响不错的书,而且是2008年的,算是比较新的一本书了。

   前几天还准备写一个分布式计算的系列,只写了个开头,又换到写这个系列了。以后看哪边的心得更多,就写哪一个系列吧。最近干的事情比较杂,有跟机器学习相关的,有跟数学相关的,也有跟分布式相关的。

   这个系列主要想能够用数学去描述机器学习,想要学好机器学习,首先得去理解其中的数学意义,不一定要到能够轻松自如的推导中间的公式,不过至少得认识这些式子吧,不然看一些相关的论文可就看不懂了,这个系列主要将会着重于去机器学习的数学描述这个部分,将会覆盖但不一定局限于回归、聚类、分类等算法。

回归与梯度下降:

   回归在数学上来说是给定一个点集,能够用一条曲线去拟合之,如果这个曲线是一条直线,那就被称为线性回归,如果曲线是一条二次曲线,就被称为二次回归,回归还有很多的变种,如locally weighted回归,logistic回归,等等,这个将在后面去讲。

   用一个很简单的例子来说明回归,这个例子来自很多的地方,也在很多的open source的软件中看到,比如说weka。大概就是,做一个房屋价值的评估系统,一个房屋的价值来自很多地方,比如说面积、房间的数量(几室几厅)、地段、朝向等等,这些影响房屋价值的变量被称为特征(feature),feature在机器学习中是一个很重要的概念,有很多的论文专门探讨这个东西。在此处,为了简单,假设我们的房屋就是一个变量影响的,就是房屋的面积。

   假设有一个房屋销售的数据如下:

   面积(m^2)  销售价钱(万元)

   123            250

   150            320

   87              160

   102            220

   …               …

   这个表类似于帝都5环左右的房屋价钱,我们可以做出一个图,x轴是房屋的面积。y轴是房屋的售价,如下:

   机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)_第1张图片

   如果来了一个新的面积,假设在销售价钱的记录中没有的,我们怎么办呢?

   我们可以用一条曲线去尽量准的拟合这些数据,然后如果有新的输入过来,我们可以在将曲线上这个点对应的值返回。如果用一条直线去拟合,可能是下面的样子:

    机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)_第2张图片

   绿色的点就是我们想要预测的点。

   首先给出一些概念和常用的符号,在不同的机器学习书籍中可能有一定的差别。

   房屋销售记录表 - 训练集(training set)或者训练数据(training data), 是我们流程中的输入数据,一般称为x

   房屋销售价钱 - 输出数据,一般称为y

   拟合的函数(或者称为假设或者模型),一般写做 y = h(x)

   训练数据的条目数(#training set), 一条训练数据是由一对输入数据和输出数据组成的

   输入数据的维度(特征的个数,#features),n

   下面是一个典型的机器学习的过程,首先给出一个输入数据,我们的算法会通过一系列的过程得到一个估计的函数,这个函数有能力对没有见过的新数据给出一个新的估计,也被称为构建一个模型。就如同上面的线性回归函数。

 

   机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)_第3张图片

    我们用X1,X2..Xn 去描述feature里面的分量,比如x1=房间的面积,x2=房间的朝向,等等,我们可以做出一个估计函数:

image

    θ在这儿称为参数,在这儿的意思是调整feature中每个分量的影响力,就是到底是房屋的面积更重要还是房屋的地段更重要。为了如果我们令X0 = 1,就可以用向量的方式来表示了:

image

    我们程序也需要一个机制去评估我们θ是否比较好,所以说需要对我们做出的h函数进行评估,一般这个函数称为损失函数(loss function)或者错误函数(error function),描述h函数不好的程度,在下面,我们称这个函数为J函数

    在这儿我们可以做出下面的一个错误函数:

image 

    这个错误估计函数是去对x(i)的估计值与真实值y(i)差的平方和作为错误估计函数,前面乘上的1/2是为了在求导的时候,这个系数就不见了。

    如何调整θ以使得J(θ)取得最小值有很多方法,其中有最小二乘法(min square),是一种完全是数学描述的方法,在stanford机器学习开放课最后的部分会推导最小二乘法的公式的来源,这个来很多的机器学习和数学书上都可以找到,这里就不提最小二乘法,而谈谈梯度下降法。

    梯度下降法是按下面的流程进行的:

    1)首先对θ赋值,这个值可以是随机的,也可以让θ是一个全零的向量。

    2)改变θ的值,使得J(θ)按梯度下降的方向进行减少。

    为了更清楚,给出下面的图:

机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)_第4张图片    这是一个表示参数θ与误差函数J(θ)的关系图,红色的部分是表示J(θ)有着比较高的取值,我们需要的是,能够让J(θ)的值尽量的低。也就是深蓝色的部分。θ0,θ1表示θ向量的两个维度。

    在上面提到梯度下降法的第一步是给θ给一个初值,假设随机给的初值是在图上的十字点。

    然后我们将θ按照梯度下降的方向进行调整,就会使得J(θ)往更低的方向进行变化,如图所示,算法的结束将是在θ下降到无法继续下降为止。

机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)_第5张图片     当然,可能梯度下降的最终点并非是全局最小点,可能是一个局部最小点,可能是下面的情况:

机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)_第6张图片

   上面这张图就是描述的一个局部最小点,这是我们重新选择了一个初始点得到的,看来我们这个算法将会在很大的程度上被初始点的选择影响而陷入局部最小点  

   下面我将用一个例子描述一下梯度减少的过程,对于我们的函数J(θ)求偏导J:(求导的过程如果不明白,可以温习一下微积分)

   image

    下面是更新的过程,也就是θi会向着梯度最小的方向进行减少。θi表示更新之前的值,-后面的部分表示按梯度方向减少的量,α表示步长,也就是每次按照梯度减少的方向变化多少。

image     一个很重要的地方值得注意的是,梯度是有方向的,对于一个向量θ,每一维分量θi都可以求出一个梯度的方向,我们就可以找到一个整体的方向,在变化的时候,我们就朝着下降最多的方向进行变化就可以达到一个最小点,不管它是局部的还是全局的。

    用更简单的数学语言进行描述步骤2)是这样的:

   机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent)_第7张图片    倒三角形表示梯度,按这种方式来表示,θi就不见了,看看用好向量和矩阵,真的会大大的简化数学的描述啊。

总结与预告:

    本文中的内容主要取自stanford的课程第二集,希望我把意思表达清楚了:)本系列的下一篇文章也将会取自stanford课程的第三集,下一次将会深入的讲讲回归、logistic回归、和Newton法,不过本系列并不希望做成stanford课程的笔记版,再往后面就不一定完全与stanford课程保持一致了。


源码如下:

#coding=utf-8


'''
Created on Jan 8, 2011

@author: Peter


利用回归预测数值型数据

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


                              回归的一般方法
(1)收集数据:采用任意方法收集数据。
(2)准备数据:回归需要数值型数据,标称型数据将被转成二值型数据。
(3)分析数据:绘出数据的可视化二维图将有助于对数据做出理解和分析,在采用缩减法
  求得新回归系数之后,可以将新拟合线绘在图上作为对比。
(4)训练算法:找到回归系数。
(5)测试算法:使用R2或者预测值和数据的拟合度,来分析模型的效果。
(6)使用算法:使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提
  升,因为这样可以预测连续型数据而不仅仅是离散的类别标签。


'''
from numpy import *
'''

    第一个函数loadDataSet()与第7章的同名函数是一样的。该函数打开一个用tab键分隔的文
本文件,这里仍然默认文件每行的最后一个值是目标值。第二个函数standRegres()用来计算
最佳拟合直线。该函数首先读人x和Y并将它们保存到矩阵中;然后计算X^T*X,然后判断它的行列
式是否为零,如果行列式为零,那么计算逆矩阵的时候将出现错误。NumPy提供一个线性代数的
库linalg,其中包含很多有用的函数。可以直接调用linalg.det()来计算行列式。最后,如果行
列式非零,计算并返回w。如果没有检查行列式是否为零就试图计算矩阵的逆,将会出现错误。
NumPy的线性代数库还提供一个函数来解未知矩阵,如果使用该函数,那么代码ws=xTx.I*
(xMat .T*yMat)应写成ws=linalg.solve(xTx, xMat.T*yMatT)。

'''
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
'''
>>> import regression
>>> from numpy import *
>>> xArr,yArr = regression.loadDataSet('ex0.txt')
>>> xArr[0:2]
[[1.0, 0.067732], [1.0, 0.42781]]
>>> ws = regression.standRegres(xArr,yArr)
>>> xMat = mat(xArr)
>>> yMat = mat(yArr)
>>> yHat = xMat*ws
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(xMat[:,1].flatten().A[0],yMat.T[:,0].flatten().A[0])
<matplotlib.collections.PathCollection object at 0x03C5EB90>
>>> xCopy = xMat.copy()
>>> xCopy.sort(0)
>>> yHat = xCopy*ws
>>> ax.plot(xCopy[:,1],yHat)
[<matplotlib.lines.Line2D object at 0x03C5E790>]
>>> plt.show()


可以通过命令corrcoef(yEstimate,yActual)来计算预测值和真实值的相关性。
局部加权线性回归
    线性回归的一个问题是有可能出现欠拟合现象,因为它求的是具有最小均方误差的无偏估
计。显而易见,如果模型欠拟合将不能取得最好的预测效果。所以有些方法允许在估计中引人一
些偏差,从而降低预测的均方误差。


    程序清单8-2中代码的作用是,给定x空间中的任意一点,计算出对应的预测值yHat。函数
lwlr ( )的开头与程序清单8-1类似,读人数据并创建所需矩阵,之后创建对角权重矩阵
weights.。权重矩阵是一个方阵,阶数等于样本点个数。也就是说,该矩阵为每个样本点初始
化了一个权重。接着,算法将遍历数据集,计算每个样本点对应的权重值:随着样本点与待预测
点距离的递增,权重将以指数级衰减O。输人参数k控制衰减的速度。与之前的函数stand-
Regress()一样,在权重矩阵计算完毕后,就可以得到对回归系数ws的一个估计。
    程序清单8-2中的另一个函数是1wlrTest(),用于为数据集中每个点调用lwlr ( ),这有助
于求解k的大小。

'''
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

def lwlr(testPoint,xArr,yArr,k=1.0):
    xMat = mat(xArr); yMat = mat(yArr).T
    m = shape(xMat)[0]
    weights = mat(eye((m)))
    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
'''
>>> xArr,yArr = regression.loadDataSet('ex0.txt')
>>> yArr[0]
3.176513
>>> regression.lwlr(xArr[0],xArr,yArr,1.0)
matrix([[ 3.12204471]])
>>> regression.lwlr(xArr[0],xArr,yArr,0.001)
matrix([[ 3.20175729]])


>>> yHat = regression.lwlrTest(xArr,xArr,yArr,0.003)
>>> xMat = mat(xArr)
>>> srtInd = xMat[:,1].argsort(0)
>>> xSort = xMat[srtInd][:,0,:]
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.plot(xSort[:,1],yHat[srtInd])
[<matplotlib.lines.Line2D object at 0x03E6DDB0>]
>>> ax.scatter(xMat[:,1].flatten().A[0],mat(yArr).T.flatten().A[0],s = 2,c = 'red')
<matplotlib.collections.PathCollection object at 0x03E6DD30>
>>> plt.show()


    局部加权线性回归也存在一个问题,即增加了计算量,因为它对每个点做预测时都必须使用整
个数据集。从图8-5可以看出,k= 0.01时可以得到很好的估计,但是同时看一下图8}中k= 0.01的情
况,就会发现大多数据点的权重都接近零。如果避免这些计算将可以减少程序运行时间,从而缓解
因计算量增加带来的问题。

'''
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

def lwlrTestPlot(xArr,yArr,k=1.0):  #same thing as lwlrTest except it sorts X first
    yHat = zeros(shape(yArr))       #easier for plotting
    xCopy = mat(xArr)
    xCopy.sort(0)
    for i in range(shape(xArr)[0]):
        yHat[i] = lwlr(xCopy[i],xArr,yArr,k)
    return yHat,xCopy
'''
预测鲍鱼 的年龄

>>> 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)
>>> regression.rssError(abY[0:99],yHat01.T)
56.818912090414081
>>> regression.rssError(abY[0:99],yHat1.T)
429.89056187030167
>>> regression.rssError(abY[0:99],yHat10.T)
549.11817088254827
使用较小的核将得到较低的误差
'''
def rssError(yArr,yHatArr): #yArr and yHatArr both need to be arrays
    return ((yArr-yHatArr)**2).sum()

def ridgeRegres(xMat,yMat,lam=0.2):
    xTx = xMat.T*xMat
    denom = xTx + eye(shape(xMat)[1])*lam
    if linalg.det(denom) == 0.0:
        print "This matrix is singular, cannot do inverse"
        return
    ws = denom.I * (xMat.T*yMat)
    return ws
    
def ridgeTest(xArr,yArr):
    xMat = mat(xArr); yMat=mat(yArr).T
    yMean = mean(yMat,0)
    yMat = yMat - yMean     #to eliminate X0 take mean off of Y
    #regularize X's
    xMeans = mean(xMat,0)   #calc mean then subtract it off
    xVar = var(xMat,0)      #calc variance of Xi then divide by it
    xMat = (xMat - xMeans)/xVar
    numTestPts = 30
    wMat = zeros((numTestPts,shape(xMat)[1]))
    for i in range(numTestPts):
        ws = ridgeRegres(xMat,yMat,exp(i-10))
        wMat[i,:]=ws.T
    return wMat
'''
    如果特征比样本点还多(n>m),也就是说输人数据的矩阵X不是满秩矩阵。非满秩矩阵在
求逆时会出现问题。

第一个函数ridgeRegres()实现了给定lambda下的岭回归求解。如果没指定lambda,则默
认为0.2。由于lambda是Python保留的关键字因此程序中使用了lam来代替。该函数首先构建矩
阵XTX,然后用lam乘以单位矩阵.如果lambda设定为。的时候一样可能会产生错误,所以这里仍需要做一个检查。
如果矩阵非奇异就计算回归系数并返回。



'''
def regularize(xMat):#regularize by columns
    inMat = xMat.copy()
    inMeans = mean(inMat,0)   #calc mean then subtract it off
    inVar = var(inMat,0)      #calc variance of Xi then divide by it
    inMat = (inMat - inMeans)/inVar
    return inMat
'''
前向逐步回归
    前向逐步回归算法可以得到与lasso差不多的效果,但更加简单。它属于一种贪心算法,即每
一步都尽可能减少误差。一开始,所有的权重都设为1,然后每一步所做的决策是对某个权重增
加或减少一个很小的值。

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


    程序清单8-4中的函数stageWise()是一个逐步线性回归算法的实现,它与lasso做法相近但
计算简单。该函数的输人包括:输人数据xArr和预测变量yArr。此外还有两个参数:一个是eps,
表示每次迭代需要调整的步长;另一个是num工七,表示迭代次数。
    函数首先将输人数据转换并存人矩阵中,然后把特征按照均值为0方差为1进行标准化处理。在
这之后创建了一个向量ws来保存w的值,并且为了实现贪心算法建立了ws的两份副本。接下来的优
化过程需要迭代~It次,并且在每次迭代时都打印出w向量,用于分析算法执行的过程和效果。
    贪心算法在所有特征上运行两次for循环,分别计算增加或减少该特征对误差的影响。这里
使用的是平方误差,通过之前的函数rssError()得到。该误差初始值设为正无穷,经过与所有
的误差比较后取最小的误差。整个过程循环迭代进行。

'''
def stageWise(xArr,yArr,eps=0.01,numIt=100):
    xMat = mat(xArr); yMat=mat(yArr).T
    yMean = mean(yMat,0)
    yMat = yMat - yMean     #can also regularize ys but will get smaller coef
    xMat = regularize(xMat)
    m,n=shape(xMat)
    #returnMat = zeros((numIt,n)) #testing code remove
    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

#def scrapePage(inFile,outFile,yr,numPce,origPrc):
#    from BeautifulSoup import BeautifulSoup
#    fr = open(inFile); fw=open(outFile,'a') #a is append mode writing
#    soup = BeautifulSoup(fr.read())
#    i=1
#    currentRow = soup.findAll('table', r="%d" % i)
#    while(len(currentRow)!=0):
#        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', '') #strips out Free Shipping
#            print "%s\t%d\t%s" % (priceStr,newFlag,title)
#            fw.write("%d\t%d\t%d\t%f\t%s\n" % (yr,numPce,newFlag,origPrc,priceStr))
#        i += 1
#        currentRow = soup.findAll('table', r="%d" % i)
#    fw.close()
'''
预测乐高玩具套装的价格

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

    上述程序清单中的第一个函数是searchForSet(),它调用Google购物API并保证数据抽取
的正确性。这里需要导人新的模块:time.sleep()、json和urllib2。但是一开始要休眠10
秒钟,这是为了防止短时间内有过多的API调用。接下来,我们拼接查询的URL字符串,添加API
的key和待查询的套装信息,打开和解析操作通过json.loads()方法实现。完成后我们将得到
一部字典,下面需要做的是从中找出价格和其他信息。
    部分返回结果的是一个产品的数组,我们将在这些产品上循环迭代,判断该产品是否是新产
品并抽取它的价格。我们知道,乐高套装由很多小插件组成,有的二手套装很可能会缺失其中一
两件。也就是说,卖家只出售套装的若干部件(不完整)。因为这种不完整的套装也会通过检索

结果返回,所以我们需要将这些信息过滤掉(可以统计描述中的关键词或者是用贝叶斯方法来判
断)。我在这里仅使用了一个简单的启发式方法:如果一个套装的价格比原始价格低一半以上,
则认为该套装不完整。程序清单8-s在代码0处过滤掉了这些套装,解析成功后的套装将在屏幕
上显示出来并保存在lis时象retX和retY中。
    程序清单8-5的最后一个函数是。etDataCollect(),它负责多次调用searchForSet()。
函数searchForSet()的其他参数是从www.brickset.com收集来的,它们也一并输出到文件中。

'''    
from time import sleep
import json
import urllib2
def searchForSet(retX, retY, setNum, yr, numPce, origPrc):
    sleep(10)
    myAPIstr = 'AIzaSyD2cR2KFyx12hXu6PFU-wrWot3NXvko8vY'
    searchURL = 'https://www.googleapis.com/shopping/search/v1/public/products?key=%s&country=US&q=lego+%d&alt=json' % (myAPIstr, setNum)
    pg = urllib2.urlopen(searchURL)
    retDict = json.loads(pg.read())
    for i in range(len(retDict['items'])):
        try:
            currItem = retDict['items'][i]
            if currItem['product']['condition'] == 'new':
                newFlag = 1
            else: newFlag = 0
            listOfInv = currItem['product']['inventories']
            for item in listOfInv:
                sellingPrice = item['price']
                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)
        except: print 'problem with item %d' % i
    
def setDataCollect(retX, retY):
    searchForSet(retX, retY, 8288, 2006, 800, 49.99)
    searchForSet(retX, retY, 10030, 2002, 3096, 269.99)
    searchForSet(retX, retY, 10179, 2007, 5195, 499.99)
    searchForSet(retX, retY, 10181, 2007, 3428, 199.99)
    searchForSet(retX, retY, 10189, 2008, 5922, 299.99)
    searchForSet(retX, retY, 10196, 2009, 3263, 249.99)


'''
交叉验证测试岭回归

    上述程序清单中的函数crossValidation()有三个参数,前两个参数1gX和1gY存有数据集中
的X和Y值的lis时象,默认1gX和1gY具有相同的长度。第三个参数~1是算法中交叉验证的次
数,如果该值没有指定,就取默认值10。函数crossValidataion()首先计算数据点的个数m。创
建好了训练集和测试集容器0,之后创建了一个list并使用Numpy提供的random.shuffle()函数
对其中的元素进行混洗(shui}le ),因此可以实现训练集或测试集数据点的随机选取。.处将数据
集的90%分割成训练集,其余10%为测试集,并将二者分别放人对应容器中。
    一旦对数据点进行混洗之后,就建立一个新的矩阵what来保存岭回归中的所有回归系数。
我们还记得在8.4.1节中,函数ridgeTest()使用30个不同的又值创建了30组不同的回归系数。接
下来我们也在上述测试集上用30组回归系数来循环测试回归效果。岭回归需要使用标准化后的数
据,因此测试数据也需要用与测试集相同的参数来执行标准化。在O处用函数rssError()计算
误差,并将结果保存在errorMat中。
    在所有交叉验证完成后,errorMat保存了ridgeTest()里每个lambda对应的多个误差值。为了
将得出的回归系数与。tandRegres()作对比,需要计算这些误差估计值的均值①。有一点值得注
意:岭回归使用了数据标准化,而standRegres()则没有,因此为了将上述比较可视化还需将
数据还原。在O处对数据做了还原并将最终结果展示。

'''    
def crossValidation(xArr,yArr,numVal=10):
    m = len(yArr)                           
    indexList = range(m)
    errorMat = zeros((numVal,30))#create error mat 30columns numVal rows
    for i in range(numVal):
        trainX=[]; trainY=[]
        testX = []; testY = []
        random.shuffle(indexList)
        for j in range(m):#create training set based on first 90% of values in indexList
            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)    #get 30 weight vectors from ridge
        for k in range(30):#loop over all of the ridge estimates
            matTestX = mat(testX); matTrainX=mat(trainX)
            meanTrain = mean(matTrainX,0)
            varTrain = var(matTrainX,0)
            matTestX = (matTestX-meanTrain)/varTrain #regularize test with training params
            yEst = matTestX * mat(wMat[k,:]).T + mean(trainY)#test ridge results and store
            errorMat[i,k]=rssError(yEst.T.A,array(testY))
            #print errorMat[i,k]
    meanErrors = mean(errorMat,0)#calc avg performance of the different ridge weight vectors
    minMean = float(min(meanErrors))
    bestWeights = wMat[nonzero(meanErrors==minMean)]
    #can unregularize to get model
    #when we regularized we wrote Xreg = (x-meanX)/var(x)
    #we can now write in terms of x not Xreg:  x*w/var(x) - meanX/var(x) +meanY
    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)




你可能感兴趣的:(机器学习中的数学(1)-回归(regression)、梯度下降(gradient descent))