逻辑回归(分类)
Table of Contents
基于逻辑回归和Sigmoid函数的分类
逻辑回归
在二维情况下, 假设有一些点, 用一条直线对这些点进行拟合, 这个拟合过程就叫 回归
.
逻辑回归思想: 根据已有数据, 对分类边界线建立回归公式, 进行分类. 回归的意思是要找到最好的拟合参数集(即最佳的直线). 一般是使用最优化算法来找参数.
优点: 计算代价不高, 易于理解和实现. 缺点: 容易欠拟合, 分类精度可能不高.
Sigmoid函数
利用Logistic回归进行分类, 主要是要找到一个函数, 能接受所有的输入, 然后预测类别. 这个函数就是Sigmoid函数.
假设有 n 个特征, 将这 n 个特征与对应的 n 个系数相乘并累加, 然后将累加后得到的值作为输入传递给sigmoid函数, 就可以对结果进行预测. 即: z=w0x0+w1x1+⋯+wnxn . 再将 z 代入Sigmoid函数.
Sigmoid函数图象是个S型, 在 x=0 是, 值为0.5, x 越大, 越接近1, 越小, 越接近-1.
σ(z)=11+e−z
基于最优化方法的最佳回归系数确定
Sigmoid函数的输入记为 z , 且 z=w0x0+w1x1+⋯+wnxn , 也可写成 z=wwTxx .
向量 ww 就是要找的最佳参数.
因此, 预测问题转化为寻找最佳参数的问题.
最优化算法: 梯度上升法
梯度上升法的思想: 要找到某函数的最大值, 最好的方法就是沿着该函数的梯度方向探寻. 梯度的计算公式如下: ▽f(x,y)=(∂f(x,y)∂x,∂f(x,y)∂y)
梯度上升算法中, 每到达一个点后, 都需要重新计算移动的方向. 这样算子总是能保证我们能选取到最佳的移动方向.
以爬山为例, 对梯度上升算法做个比喻, 你从山脚开始往上爬, 如果想用最快的速度到达山顶, 则每一步都要沿着最陡的方向(即斜率最大的方向)往上爬.
梯度只是移动的方向, 而不是移动量, 记每次移动的步长为 α , 每走一步都要重新计算下一步移动的方向, 所以梯度算法的更新公式为 w:=w+α▽wf(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
这段代码与之前的梯度上升算法主要有两点不同:
- 随机梯度上升算法的变量h是一个数字, 而梯度上升算法中是一个向量.
- 随机梯度上升算法没有进行矩阵转换, 而梯度上升算法则将矩阵转换成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的最佳拟合参数
, 求解过程可以由最优化算法来完成.
随机梯度上升算法与梯度上升算法的效果相当, 但占用更少的计算资源. 此外, 随机梯度上升是一个在线算法, 它可以在新数据到来时就完成参数更新, 而不需要重新读取整个数据集来进行批处理运算.
机器学习的一个重要问题是如何处理缺失数据, 这个问题没有标准答案, 取决于实际中的需要.