逻辑回归(logistics regression)及其应用(MLIA第五章)

一、基本原理

逻辑回归与线性回归

Logistic Regression和Linear Regression的原理是相似的,按照我自己的理解,可以简单的描述为这样的过程:

(1)找一个合适的预测函数(Andrew Ng的公开课中称为hypothesis),一般表示为h函数,该函数就是我们需要找的分类函数,它用来预测输入数据的判断结果。这个过程时非常关键的,需要对数据有一定的了解或分析,知道或者猜测预测函数的“大概”形式,比如是线性函数还是非线性函数。

(2)构造一个Cost函数(损失函数),该函数表示预测的输出(h)与训练数据类别(y)之间的偏差,可以是二者之间的差(h-y)或者是其他的形式。综合考虑所有训练数据的“损失”,将Cost求和或者求平均,记为J(θ)函数,表示所有训练数据预测值与实际类别的偏差的估计,称为风险函数或期望损失函数。

(3)显然,J(θ)函数的值越小表示预测函数越准确(即h函数越准确),所以这一步需要做的是找到J(θ)函数的最小值。找函数的最小值有不同的方法,Logistic Regression实现时有的是梯度下降法(Gradient Descent)。

分类问题与Sigmoid函数

σ(z)=11+ez

Sigmoid函数看起来很像一个阶跃函数。
海维赛德阶跃函数——heaviside step function

自变量为0,函数值为0.5
自变量趋于正无穷,函数值趋近于1
自变量趋于负无穷,函数值趋近于0

(为了实现Logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,)将这个总和代入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类,小于0.5即被归入0类。所以, Logistic回归也可以被看成是一种概率估计。

括号内的后面会讲。简单来说Sigmoid做分类器使用,逻辑回归计算出最佳拟合的回归系数。

线性回归的参数或系数

假设结果为若干属性(特征)值的线性组合 z=w0x0+w1x1+...+wnxn
写为向量:
z=wTx(1)

其中的向量x是分类器的输入数据,向量w也就是我们要找到的最佳参数

预测函数

将上两小结内容整合起来,有逻辑回归的预测函数为:
hθ(x)=11+eθT(x) ,其中 θ 是上小节中w的估计值。

Cost函数

损失函数:表示预测的输出(h)与训练数据类别(y)之间的偏差,可以是二者之间的差(h-y)或者是其他的形式

损失函数最常见的形式就是 (h(i)y(i))
上标(i)表示第i个样本,而不是指数
风险函数常见形式为:

J(θ)=1mi=1N(hθ(x(i))y(i))2

N是样本数,求θ使得J(θ)最小,这样的θ就是理想的参数,对线性回归来说θ就是理想的回归系数。

但是,对于逻辑回归(及任何二分类问题)来说, hθ(x(i)) 的取值是0或者1,所以J(θ)不是一个凸函数,难以通过简单的手段求极小值。

所以我们必须找一个新的损失函数:

loss(hθ(x(i)),y(i))={log(hθ(x(i)))when:y(i)=1log(1hθ(x(i)))when:y(i)=0

什么意思呢?y的真实值为1,预测值也为1时,损失为0,但预测值为0时,损失为正无穷;可类推y=0的叙述。

统一成一个式子,在某个样本上的损失函数定义为:

loss(hθ(x(i)),y(i))=[y(i)log(hθ(x(i)))+(1y(i)))log(1hθ(x(i)))]

风险函数为:

J(θ)=1mi=1N[y(i)log(hθ(x(i)))+(1y(i)))log(1hθ(x(i)))](2)

我们可以写出这个风险函数,但是这个函数是怎么得来的?
实际上这里的Cost函数和J(θ)函数是基于最大似然估计推导得到的,本文暂时略掉这个部分。

梯度上升(下降)法求J(θ)的最小值

梯度上升法基于的思想是:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。
梯度算法的迭代公式如下:
θ:=θ+αθJ(θ)(3) θJ(θ) 是当前迭代位置的梯度,它是函数函数f对各坐标轴求偏导的结果向量。

θJ(θ)=J(θ)θ0J(θ)θ1...J(θ)θn(4)

θi 是θ向量的各分量

注意,(式4)是一个列向量(偏微分向量),所以以矩阵形式来写(式3)所表示的迭代公式:

θ0θ1...θn:=θ0θ1...θn+αJ(θ)θ0J(θ)θ1...J(θ)θn=θ0+αJ(θ)θ0θ1+αJ(θ)θ1...θn+αJ(θ)θn(5)

求解偏微分
J(θ)θj=θj1mi=1n[y(i)log(hθ(x(i))+(1y(i))log(1hθ(x(i)))]=1mi=1n[y(i)1hθ(x(i))θjhθ(x(i))(1y(i))11hθ(x(i))θjhθ(x(i))]=1mi=1n[y(i)1g(θTx(i))(1y(i))11g(θTx(i))]θjg(θTx(i))

z=θTx(i) g(z)=11+exp(z),dg(z)dθj=g(z)(g(z)1)dzdθj 继续写上式:

1mi=1n[y(i)1g(z)(1y(i))11g(z)]g(z)(1g(z))θjz=1mi=1n[y(i)(1g(z))(1y(i))g(z)]x(i)j=1mi=1n[y(i)g(z)]x(i)j=1mi=1n[y(i)hθ(x(i))]x(i)j=1mxTjE

其中 xj 是样本集全部第j个特征值组成的列向量,转置后乘以E,E(error)是各样本在当前(理解当前) θ 下的误差( y(i)h(i) )组成的列向量。它们的乘积是一个标量。

梯度下降过程向量化

重新表示下 θJ(θ)

θJ(θ)=1mxT0ExT1E...xTnE=1mXTE(6)

再考虑迭代公式(式3和式5),迭代公式写为:
θ:=θ+αXTE(7)

因为有 α 的存在, 1m 省略了(因为是常量,可以认为合并到 α 里面了)

到这里,就很简单了,X就是样本矩阵,一行是一个样本,一列是一个特征,E是每次迭代时上一个 θ 决定的误差列向量, α 是一个小量(偏微分向量决定梯度方向, α 决定变化步长),可以固定也可以每次变得更小。

代码实现

计算理想的回归系数

def gradientAscent(dataMat, labelMat):
    '''
    梯度下降求权重向量(回归系数)
    :param dataMat: 训练集矩阵
    :param labelMat: 标签列表
    :return:
    '''
    dataMatrix = mat(dataMat)  # convert to NumPy matrix
    labelMatrix = mat(labelMat).transpose()  # convert to NumPy matrix,行向量转列向量
    # m:样本数,n:特征数
    m, n = shape(dataMatrix)
    alpha = 0.001  # 步长
    # 最多循环次数
    maxCycles = 500
    weights = ones((n, 1))  # 单位列向量,初始化各属性的权重为1
    for k in range(maxCycles):
        # 数据集乘以回归系数后进入sigmoid,结果是一个列向量
        h = sigmoid(dataMatrix * weights)  # 这就是对样本类别的预测
        # 真实值-预测值,仍是一个列向量
        error = labelMatrix - h
        # 将误差带入回归系数迭代公式 θ+= α*X.t*error
        weights += alpha * dataMatrix.transpose() * error
    return weights

问题1:sigmoid函数

def sigmoid(x):
    return 1 / (1 + exp(-x))

问题2:迭代次数

这里没有考虑其他终止迭代的办法(如收敛到一定程度),而是直接固定一个迭代次数。

是可以优化的。

问题3:性能

由于每次迭代,都要用整个样本矩阵的转置去✖️当下的误差,在m和n都很大时这个计算量非常大;

测试与可视化

样本集的加载和处理

def loadDataSet() -> (list, list):
    dataMat = []
    labelMat = []
    fr = open('testSet_logRegress.txt')
    for line in fr.readlines():
        lineArr = line.strip().split()
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
        labelMat.append(int(lineArr[2]))
    return dataMat, labelMat

样本是一些只有两个属性的点及其对应的分类(0|1),在文末给出这样大家就不用再去下载了

加载完数据后,就可以用其返回值作为输入值调用gradientAscent函数了:

if __name__ == '__main__':
    dataMat, labelMat = loadDataSet()
    print(dataMat)
    print(labelMat)
    weights = gradientAscent(dataMat, labelMat)
    print(weights)
    plotBestFit(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 = arange(-3.0, 3.0, 0.1)
    y = (-weights[0] - weights[1] * x) / weights[2]
    ax.plot(x, y)
    plt.xlabel('X1');
    plt.ylabel('X2');
    plt.show()

直接用就好了

优化

对于优化(减少迭代次数,加快系数收敛),书上很清楚,书上的优化分两步走:
1、不是用样本矩阵的全量而是顺序使用每个样本来更新回归系数,样本迭代完,程序结束

def stocGradAscent0(dataMatrix, classLabels):
    '''
    随机梯度下降
    :param dataMatrix:
    :param classLabels:
    :return:
    '''
    m, n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n)  # initialize to all ones,这是一个一维数组,向量
    # 遍历数据集,每次使用一个样本来更新回归系数
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i] * weights))
        error = classLabels[i] - h
        weights += alpha * error * dataMatrix[i]
    return weights

这样做显然不够好,首先结果不够好(迭代若干次后划分误差仍然很大),另外就是参数有局部波动。

2、在上一步的基础上,首先增加外围的迭代,其次步长随外围迭代不断变小,另外就是每次随机选择一个样本来更新回归系数:

def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    '''
    改进版的随机梯度下降
    :param dataMatrix:
    :param classLabels:
    :param numIter:
    :return:
    '''
    m,n = shape(dataMatrix)
    weights = ones(n)   #initialize to all ones
    # 外层迭代次数
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.0001    #apha decreases with iteration, does not
            randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant
            h = sigmoid(sum(dataMatrix[randIndex]*weights))
            error = classLabels[randIndex] - h
            weights = weights + alpha * error * dataMatrix[randIndex]
            del(dataIndex[randIndex])  # 去除用过的随机索引
    return weights

—————-本文=书上用的样本集—————–

-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
0.406704    7.067335    1
0.667394    12.741452   0
-2.460150   6.866805    1
0.569411    9.548755    0
-0.026632   10.427743   0
0.850433    6.920334    1
1.347183    13.175500   0
1.176813    3.167020    1
-1.781871   9.097953    0
-0.566606   5.749003    1
0.931635    1.589505    1
-0.024205   6.151823    1
-0.036453   2.690988    1
-0.196949   0.444165    1
1.014459    5.754399    1
1.985298    3.230619    1
-1.693453   -0.557540   1
-0.576525   11.778922   0
-0.346811   -1.678730   1
-2.124484   2.672471    1
1.217916    9.597015    0
-0.733928   9.098687    0
-3.642001   -1.618087   1
0.315985    3.523953    1
1.416614    9.619232    0
-0.386323   3.989286    1
0.556921    8.294984    1
1.224863    11.587360   0
-1.347803   -2.406051   1
1.196604    4.951851    1
0.275221    9.543647    0
0.470575    9.332488    0
-1.889567   9.542662    0
-1.527893   12.150579   0
-1.185247   11.309318   0
-0.445678   3.297303    1
1.042222    6.105155    1
-0.618787   10.320986   0
1.152083    0.548467    1
0.828534    2.676045    1
-1.237728   10.549033   0
-0.683565   -2.166125   1
0.229456    5.921938    1
-0.959885   11.555336   0
0.492911    10.993324   0
0.184992    8.721488    0
-0.355715   10.325976   0
-0.397822   8.058397    0
0.824839    13.730343   0
1.507278    5.027866    1
0.099671    6.835839    1
-0.344008   10.717485   0
1.785928    7.718645    1
-0.918801   11.560217   0
-0.364009   4.747300    1
-0.841722   4.119083    1
0.490426    1.960539    1
-0.007194   9.075792    0
0.356107    12.447863   0
0.342578    12.281162   0
-0.810823   -1.466018   1
2.530777    6.476801    1
1.296683    11.607559   0
0.475487    12.040035   0
-0.783277   11.009725   0
0.074798    11.023650   0
-1.337472   0.468339    1
-0.102781   13.763651   0
-0.147324   2.874846    1
0.518389    9.887035    0
1.015399    7.571882    0
-1.658086   -0.027255   1
1.319944    2.171228    1
2.056216    5.019981    1
-0.851633   4.375691    1
-1.510047   6.061992    0
-1.076637   -3.181888   1
1.821096    10.283990   0
3.010150    8.401766    1
-1.099458   1.688274    1
-0.834872   -1.733869   1
-0.846637   3.849075    1
1.400102    12.628781   0
1.752842    5.468166    1
0.078557    0.059736    1
0.089392    -0.715300   1
1.825662    12.693808   0
0.197445    9.744638    0
0.126117    0.922311    1
-0.679797   1.220530    1
0.677983    2.556666    1
0.761349    10.693862   0
-2.168791   0.143632    1
1.388610    9.341997    0
0.317029    14.739025   0

猜你喜欢

转载自blog.csdn.net/zhengwei223/article/details/79105478