ML in Action Note - Day 3/4 - Logistics Regression

嘻嘻嘻,终于到这一章~

自动去找到拟合参数简直太棒了好嘛> w <

Logistics Regression:包括Gradient Regression和Stochastic Regression。

优点:计算代价不高

缺点:容易欠拟合,分类精度可能不高

适用于数值型和标称型

在这里,我们需要的函数是,接受所有的输入然后预测出类别。例如,在两个分类情况,输出0或1,这一的函数称为Heaviside step function,即单位阶跃函数。这个函数的问题在于:瞬间跳跃过程有时很难处理,于是需要和数学上的sigmoid函数一起合并。

sigmoid函数:\sigma (z)=\frac{1}{1+e^{-z}}

当x为0,sigmoid函数值为0.5。随着x增大,对应的sigmoid值趋近1,随着x减小,sigmoid值趋近0。当横坐标足够大,sigmoid函数看起来像是一个阶跃函数。

所以结合logistics regress和sigmoid得到solution是:每个特征都乘以一个回归系数,把结果总和代入sigmoid函数中,得到一个0~1范围的数值。

sigmoid函数的输入为z:

z=w_0x_0+w1x_1+...+w_nx_n

也就是z=W^{T}X

P1:Gradient Regression

找到某个函数的最大值(最小值),最好的方法是沿着函数的梯度方向去找。

函数f(x, y)的梯度▽函数为:\bigtriangledown f(x, y)=\begin{pmatrix} {\frac{\partial f(x,y)}{\partial x}} \\ {\frac{\partial f(x,y)}{\partial y}} \end{pmatrix}

这个梯度意味沿着x的方向移动\frac{\partial f(x,y)}{\partial x},沿着y的方向移动\frac{\partial f(x,y)}{\partial y},其中函数f(x, y)必须要在待计算的点上有定义并且可微。

梯度的方向就是导数最大值的方向,即函数变化率最高的方向。因此,梯度方向可以通过对函数求导得到。

梯度算法一直迭代执行,在每个点会重新计算移动的方向,直到达到某个停止条件位置,比如迭代次数达到某个指定值,或者算法达到某个范围。迭代公式:

梯度上升:w:=w+\alpha \bigtriangledown _wf(w)

梯度下降:w:=w-\alpha \bigtriangledown _wf(w)

文件格式如下:

-0.017612    14.053064    0
-1.395634    4.662541    1
-0.752157    6.538620    0
-1.322371    7.152853    0
0.423363    11.054677    0

下面是具体实现:

def loadDataSet():
    dataMat = []
    labelMat = []
    fr = open('testSet.txt', 'rb')
    for line in fr.readlines():
        lineArr = line.strip().split()
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) # 1.0, 第1列,第2列
        labelMat.append(int(lineArr[2])) # 第3列为分类
    return dataMat, labelMat

def sigmoid(inX):
    return 1.0/(1 + np.exp(-inX))

def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn) # list to mat
    labelMat = mat(classLabels).transpose() # 本来是list,转成(m, 1)的vector
    m, n = shape(dataMatrix) # (m=样本数量, n=w权重+x特征数量)
    alpha = 0.001
    maxCycles = 500
    weights = ones((n, 1))
    for k in range(maxCycles):
        h = sigmoid(dataMatrix * weights) # mat[m, 1] = sigmoid(mat[m, n] * mat[n, 1]) 按每个样本计算
        error = (labelMat - h) # mat[m, 1] = mat[m, 1] - mat[m, 1]
        weights = weights + alpha * dataMatrix.transpose() * error # mat[n, 1] = mat[n, 1] + alpha * mat[n, m] * mat[m, 1] 按每个特征计算
    return weights

接下来画数据集和最佳拟合线的函数

def plotBestFit(weights):
    import matplotlib.pyplot as plt
    dataMat, labelMat = loadDataSet()
    dataArr = array(dataMat)
    n = shape(dataArr)[0]
    xcord1 = []
    ycord1 = []
    xcord2 = []
    ycord2 = []
    for i in range(n):
        if int(labelMat[i]) == 1:
            xcord1.append(dataArr[i, 1])
            ycord1.append(dataArr[i, 2])
        else:
            xcord2.append(dataArr[i, 1])
            ycord2.append(dataArr[i, 2])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
    ax.scatter(xcord2, ycord2, s=30, c='green')
    x = mat(arange(-3.0, 3.0, 0.1))
    y = (-weights[0] - weights[1] * x) / weights[2] # 0 = w0x0 + w1x1 + w2x2 这里的y就是x2
    ax.plot(x, y)
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()

啊啊啊啊啊。为什么我的线不粗来呢!!!TUT试了好多次都不行!!

先继续吧……

stochastic regression:每次更新回归系数只通过随机的一个样本点。因为之前的gradient regression要把整个数据集都遍历太耗时间了。

由于可以在新样本到来对分类器进行增量式更新,所有stochastic regression属于在线学习算法。与“在线学习”相对应的,一次处理所有数据被称为是“批处理”。

def stocGradAscent0(dataMatrix, classLabels):
    m, n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n) # ndarray [1, 1, 1..]
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i] * weights)) # array * array, 然后sum(array)变成num
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMatrix[i] # 这里是单个样本的总权重,而gradient的这一行是所有样本总权重
    return weights

stochastic和gradient很相似,区别在于gradient用的向量,需要转换矩阵,而stochastic用的是数值,数据类型都是numpy数组。

执行画图发现还是没用我的线……T T

这里没有进行多次迭代。修改代码:

def stocGradAscent1(dataMatrix, classLabels, oriAlpha, numIter):
    m, n = shape(dataMatrix)
    weights = ones(n) # ndarray [1, 1, 1..]
    for j in range(numIter):
        dataIndex = range(m)
        for i in range(m):
            alpha = 4/(1.0 + j + i) + oriAlpha # 每次迭代都调整
            randIndex = int(random.uniform(0, len(dataIndex)))
            h = sigmoid(sum(dataMatrix[randIndex] * weights))
            error = classLabels[randIndex] - h
            weights = weights + alpha * error * dataMatrix[randIndex]
            del(dataIndex[randIndex])
    return weights

alpha随着迭代次数不断减少,但永远不会变成0,因为有一个常数项,这一是为了保证在多次迭代后新数据仍然有一定的影响。可以通过增大常数项来确保新的获得更大的回归系数。

alpha每次减少1/(j+i),其中j是迭代次数,i是样本的下表。这一当j<<max(i)时,alpha就不是严格下降的。避免参数的严格下降也是常见于模拟退火算法等其他优化算法中。【第一次听说退火算法……TODO】

先从书上截图回归系数的变化:

然后下面这是我自己的:【一样的参数因为画图用了不同的画图语法而不同……真好玩~嘻嘻嘻=w=至于区别点嘛,TODO

这是上图的代码:

def wPlot(wTrend):
    import matplotlib.pyplot as plt
    fig = plt.figure(figsize=(8,7))
    ax1 = fig.add_subplot(311)
    ax2 = fig.add_subplot(312)
    ax3 = fig.add_subplot(313)
    xcord1 = []
    ycord1 = []
    xcord2 = []
    ycord2 = []
    xcord3 = []
    ycord3 = []
    m = len(wTrend)
    for i in range(m):
        dt = wTrend[i]
        xcord1.append(i)
        xcord2.append(i)
        xcord3.append(i)
        ycord1.append(dt[0])
        ycord2.append(dt[1])
        ycord3.append(dt[2])
    ax1.scatter(xcord1, ycord1, s=1)
    ax2.scatter(xcord2, ycord2, s=1)
    ax3.scatter(xcord3, ycord3, s=1)
    plt.show()

这是另一图和代码:

def wPlot1(wTrend):
    import matplotlib.pyplot as plt
    fig = plt.figure(figsize=(8,7))
    xcord1 = []
    ycord1 = []
    xcord2 = []
    ycord2 = []
    xcord3 = []
    ycord3 = []
    m = len(wTrend)
    for i in range(m):
        dt = wTrend[i]
        xcord1.append(i)
        xcord2.append(i)
        xcord3.append(i)
        ycord1.append(dt[0])
        ycord2.append(dt[1])
        ycord3.append(dt[2])
    plt.subplot(311)
    plt.plot(xcord1, ycord1)
    plt.subplot(312)
    plt.plot(xcord2, ycord2)
    plt.subplot(313)
    plt.plot(xcord3, ycord3)
    plt.show()

P2:预测马的死亡率

之前研究kaggle时候就提到了数据的缺失和处理方法(天啊,我到现在还没有把titanic给全部整完……抽自己耳光T T),判断如何处理这些数据,需要根据不同的情况,下面举例一些方法:

1. 用均值来填补

2. 用特殊值比如-1来填补

3. 删除这些样本

4. 用相似样本的均值来填补

5. 用其他的学习算法来预测缺失值

numpy数据类型不允许缺失值,所以需要找数字来替换缺失值。

接下来要用实数0来替换所有缺失值,切好能适用于logistics regression,这一时为了在更新时不会影响系数。

回归系数的更新公式为:

weights = weights + alpha * error * dataMatrix[randIndex]

而当dataMatrix的某个特征值对应值为0,那么该特征的系数不做更新:

weights = weights

另外,由于sigmoid(0)=0.5,即它对结果的预测不具有任何倾向性,因为上述做法不会对误差项造成影响。所以用0来代替缺失值可以保留现有数据,也不需要对优化算法进行修改。数据集中的特征一般也不会为0,所以可以说这一做满足“特殊值”。

如果测试【???不是训练?】数据集中发现类别标签缺失,那么简单做法是删除该数据,因为类别标签和特征不同,很难确定用某个合适的值来替换。采用logistics regression进行分类时这样做是合理的,但是kNN的话就不行。【TODO:to figure out为什么不行】

【突然插入自己一段去研究<<如何迅速得到一个不知道多少数据的txt文件的数据量>>】

# 也就是list转matrix了
fr = open('aaa.txt')
dataMat = []
for line in fr.readlines():
    dataMat.append(line.strip().split('\t'))
ds = np.mat(dataMat)

对样本文件进行测试和分类:

def classifyVector(inX, weights):
    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0

def colicTest():
    frTrain = open('horseColicTraining.txt', 'rb')
    frTest = open('horseColicTest.txt', 'rb')
    trainingSet = []
    trainingLabels = []
    for line in frTrain.readlines():
        currLine = line.strip().decode().split('\t')
        lineArr = []
        for i in range(21):
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[21]))
    trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 0.01, 500)
    errorCount = 0
    numTestVec = 0.0
    for line in frTest.readlines():
        numTestVec += 1.0
        currLine = line.strip().decode().split('\t')
        lineArr = []
        for i in range(21):
            lineArr.append(float(currLine[i]))
        if int(classifyVector(array(lineArr), trainWeights)) != int(currLine[21]): # 按照每一个样本计算错误
            errorCount += 1
    errorRate = (float(errorCount) / numTestVec)
    print("error rate: %f" % errorRate)
    return errorRate

def multiTest():
    numTests = 10
    errorSum = 0.0
    for k in range(numTests):
        errorSum += colicTest()
    print("after %d iteration: %f" % (numTests, errorSum/float(numTests)))

上面multiTest是调用10次的colicTest,而colicTest也是可以设置原来的迭代次数的。

。。。

在这一章被卡了好久,因为我自己给自己挖了一个坑。。好心塞。。幸好还是找到了TwT

>>>>>

logistics regression的目的是为了找一个非线性函数sigmoid的最佳拟合参数,过程通过最优化算法来完成,而最优化算法中,最常用的就是gradient regression。stochastic gradient regression与batch gradient regression相比,效果相当,但前者占用的计算资源更少,而且SGR属于online learning,可以在新数据来时就完成参数更新,不需要重新读取整个数据集进行批运算。

吐槽:

我本来以为这一章还会更具体介绍处理缺失数据……

猜你喜欢

转载自blog.csdn.net/gritsissi/article/details/86765619