Pinvon's Blog

所见, 所闻, 所思, 所想

逻辑回归(分类)

基于逻辑回归和Sigmoid函数的分类

逻辑回归

在二维情况下, 假设有一些点, 用一条直线对这些点进行拟合, 这个拟合过程就叫 回归.

逻辑回归思想: 根据已有数据, 对分类边界线建立回归公式, 进行分类. 回归的意思是要找到最好的拟合参数集(即最佳的直线). 一般是使用最优化算法来找参数.

优点: 计算代价不高, 易于理解和实现. 缺点: 容易欠拟合, 分类精度可能不高.

Sigmoid函数

利用Logistic回归进行分类, 主要是要找到一个函数, 能接受所有的输入, 然后预测类别. 这个函数就是Sigmoid函数.

假设有 \(n\) 个特征, 将这 \(n\) 个特征与对应的 \(n\) 个系数相乘并累加, 然后将累加后得到的值作为输入传递给sigmoid函数, 就可以对结果进行预测. 即: \(z=w_0x_0 + w_1x_1 + \cdots + w_nx_n\) . 再将 \(z\) 代入Sigmoid函数.

Sigmoid函数图象是个S型, 在 \(x=0\) 是, 值为0.5, \(x\) 越大, 越接近1, 越小, 越接近-1.

$$\sigma(z) = \frac{1}{1+e^{-z}}$$

基于最优化方法的最佳回归系数确定

Sigmoid函数的输入记为 \(z\) , 且 \(z=w_0x_0 + w_1x_1 + \cdots + w_nx_n\) , 也可写成 \(z=\pmb w^T \pmb x\) .

向量 \(\pmb w\) 就是要找的最佳参数.

因此, 预测问题转化为寻找最佳参数的问题.

最优化算法: 梯度上升法

梯度上升法的思想: 要找到某函数的最大值, 最好的方法就是沿着该函数的梯度方向探寻. 梯度的计算公式如下: $$\triangledown f(x, y) = \Big( \frac{\partial f(x,y)}{\partial x}, \frac{\partial f(x,y)}{\partial y} \Big)$$ 意思是要沿 \(x\) 的方向移动 \(\frac{\partial f(x,y)}{\partial x}\), 沿 \(y\) 的方向移动 \(\frac{\partial f(x,y)}{\partial y}\)

梯度上升算法中, 每到达一个点后, 都需要重新计算移动的方向. 这样算子总是能保证我们能选取到最佳的移动方向.

以爬山为例, 对梯度上升算法做个比喻, 你从山脚开始往上爬, 如果想用最快的速度到达山顶, 则每一步都要沿着最陡的方向(即斜率最大的方向)往上爬.

梯度只是移动的方向, 而不是移动量, 记每次移动的步长为 \(\alpha\) , 每走一步都要重新计算下一步移动的方向, 所以梯度算法的更新公式为 \(w := w + \alpha \triangledown_w f(w)\)

梯度上升算法与梯度下降算法原理一样, 只是一个算最大值, 一个算最小值.

训练算法: 使用梯度上升找到最佳参数

场景: 100个样本点, 每个点包含两个特征. 使用梯度上升法找到最佳回归系数.

def loadDataSet():
    dataMat = [] # 列表的列表, 每个小列表包含三个元素[回归系数, 特征1, 特征2]
    labelMat = [] # 每个元素都是分类结果
    fr = open('testSet.txt') # testSet.txt的格式: 特征1, 特征2, 分类结果
    for line in fr.readlines():
        lineArr = line.strip().split()
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) # 回归系数初始化为1.0
        labelMat.append(int(lineArr[2]))
    return dataMat, labelMat

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

def gradAscent(dataMatIn, classLabels): # dataMatlin: 2维NumPy数组, 一行对应一条数据的特征; classLabels: 类别
    dataMatrix = mat(dataMatIn) # 转换成NumPy矩阵
    labelMat = mat(classLabels).transpose() # 将行向量转换成列向量(转置)
    m, n = shape(dataMatrix) # 计算矩阵的行数和列数
    alpha = 0.001 # 步长
    maxCycles = 500 # 迭代次数
    weights = ones((n, 1)) # n个系数, 每个都初始化为1
    for k in range(maxCycles):
        # 计算真实类别与预测类别的差值
        # 再根据差值的方向调整回归系数
        # 如果差值为正, 则继续往前走一步, 如果差值为负, 则往回走一步
        h = sigmoid(dataMatrix * weights) # h是一个列向量, 元素个数为样本个数
        error = (labelMat - h)
        weights = weights + alpha * dataMatrix.transpose() * error
    return weights

梯度上升算法的返回值是一个列向量, 行数与特征数量相同, 表示每一个特征所对应的回归系数.

分析数据: 画出决策边界

上面的代码解出了一组回归系数, 但不一定是最佳系数, 如何找出最佳的回归系数呢?

def plotBestFit(wei): # wei是一维矩阵
    import matplotlib.pyplot as plt
    weights = wei.getA() # 将矩阵转换为数组
    dataMat, labelMat = loadDataSet() 
    dataArr = array(dataMat) # 将列表[系数, 特征1, 特征2]转换为数组
    n = shape(dataArr)[0] # 获得矩阵的行数
    xcord1 = []; ycord1 = []
    xcord2 = []; ycord2 = []
    for i in range(n):
        if int(labelMat[i]) == 1: # 将分类结果为1和0的分别存储
            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) # 添加编号为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) # 以-3.0为起点, 3.0为终点, 步长为0.1
    y = (-weights[0] - weights[1]*x)/weights[2]
    ax.plot(x, y)
    pl.xlabel('X1'); plt.ylabel('X2');
    plt.show()

训练算法: 随机梯度上升

使用梯度上升算法, 在每次更新回归系数时, 都要遍历整个数据集. 如果有数十亿样本和成千上万的特征, 那么该方法的计算复杂度就太高了.

随机梯度上升算法可以改善这个问题, 它每次仅用一个样本点来计算误差并更新回归系数, 这样通过多次迭代, 每次都随机选择不同的样本, 最终使回归系数趋于收敛.

def stocGradAscent0(dataMatrix, classLabels):
    m, n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n)
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i] * weights))
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMatrix[i]
    return weights

这段代码与之前的梯度上升算法主要有两点不同:

  1. 随机梯度上升算法的变量h是一个数字, 而梯度上升算法中是一个向量.
  2. 随机梯度上升算法没有进行矩阵转换, 而梯度上升算法则将矩阵转换成NumPy数组.

如果该代码报错: TypeError: 'numpy.float64' object cannot be interpreted as an integer. 则在调用该函数时, 第一个参数使用array()转一下.

随机梯度上升算法中, 一次循环只用了其中一个样本, 来对回归系数进行更新(第 \(i\) 次循环就用第 \(i\) 个样本来更新系数). 而在梯度上升算法中, 每次循环, 都使用了所有的样本来对回归系数进行更新.

在这个代码中, 样本有多少, 就循环了多少次, 所以如果样本不多, 则未必系数是较好的, 但是在样本足够多的情况下, 可以在较短的时间内(相对梯度上升算法而言)达到稳定值.

一个判断优化算法优劣的可靠方法是看它是否收敛, 也就是说参数是否达到了稳定值, 是否还会不断地变化.

但是, 在大的波动停止之后, 还有一些小的周期性波动. 产生这种现象的原因是存在一些不能正确分类的样本点(数据集并非线性可分), 在每次迭代时会引发系数的剧烈改变. 因此, 对随机梯度上升算法进行改进, 避免来回波动.

def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    m, n = shape(dataMatrix)
    weights = ones(n)
    for j in range(numIter):
        dataIndex = range(m) # 0-m
        for i in range(m):
            alpha = 4 / (1.0 + j + i) + 0.01 # 随着迭代次数的增加, 步长不断减小, 但不会减到0, 因为存在常数项
            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 会随着迭代的次数增加而不断减小, 这样可以缓解后期的波动. 然后, 随机选取样本来更新回归系数.

随机梯度上升算法可以通过更小的计算量, 来得到与梯度上升算法差不多的效果.

小结

逻辑回归的目的是寻找一个 非线性函数sigmoid的最佳拟合参数 , 求解过程可以由最优化算法来完成.

随机梯度上升算法与梯度上升算法的效果相当, 但占用更少的计算资源. 此外, 随机梯度上升是一个在线算法, 它可以在新数据到来时就完成参数更新, 而不需要重新读取整个数据集来进行批处理运算.

机器学习的一个重要问题是如何处理缺失数据, 这个问题没有标准答案, 取决于实际中的需要.

Comments

使用 Disqus 评论
comments powered by Disqus