diff --git a/docs/5.Logistic回归.md b/docs/5.Logistic回归.md index 98c7eafb..5e14a9cd 100644 --- a/docs/5.Logistic回归.md +++ b/docs/5.Logistic回归.md @@ -1,55 +1,271 @@ # 第5章 Logistic回归 -![朴素贝叶斯_首页](/images/5.Logistic/LogisticRegression_headPage_xy.png "Logistic回归首页") +![朴素贝叶斯_首页](../images/5.Logistic/LogisticRegression_headPage_xy.png "Logistic回归首页") -## Sigmoid函数和Logistic回归分类器 +## Logistic 回归 概述 -> 回归简介 +`Logistic 回归虽然名字叫回归,但是它是用来做分类的。其主要思想是: 根据现有数据对分类边界线建立回归公式,以此进行分类。` + +## 须知概念 + +### Sigmoid 函数 + +我们想要的函数应该是: 能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出 0 或 1.或许你之前接触过具有这种性质的函数,该函数称为 `海维塞得阶跃函数(Heaviside step function)`,或者直接称为 `单位阶跃函数`。然而,海维塞得阶跃函数的问题在于: 该函数在跳跃点上从 0 瞬间跳跃到 1,这个瞬间跳跃过程有时很难处理。幸好,另一个函数也有类似的性质(可以输出 0 或者 1 的性质),且数学上更易处理,这就是 Sigmoid 函数。 Sigmoid 函数具体的计算公式如下: + +![Sigmoid 函数计算公式](../images/5.Logistic/LR_1.png) + +下图给出了 Sigmoid 函数在不同坐标尺度下的两条曲线图。当 x 为 0 时,Sigmoid 函数值为 0.5 。随着 x 的增大,对应的 Sigmoid 值将逼近于 1 ; 而随着 x 的减小, Sigmoid 值将逼近于 0 。如果横坐标刻度足够大, Sigmoid 函数看起来很像一个阶跃函数。 + +![Sigmoid 函数在不同坐标下的图片](../images/5.Logistic/LR_3.png) + +因此,为了实现 Logistic 回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有结果值相加,将这个总和代入 Sigmoid 函数中,进而得到一个范围在 0~1 之间的数值。任何大于 0.5 的数据被分入 1 类,小于 0.5 即被归入 0 类。所以, Logistic 回归也可以被看成是一种概率估计。 + +### 基于最优化方法的回归系数确定 + +Sigmoid 函数的输入记为 z ,由下面公式得到: + +![Sigmoid 函数计算公式](../images/5.Logistic/LR_2.png) + +如果采用向量的写法,上述公式可以写成 ![Sigmoid 函数计算公式向量形式](../images/5.Logistic/LR_4.png) ,它表示将这两个数值向量对应元素相乘然后全部加起来即得到 z 值。其中的向量 x 是分类器的输入数据,向量 w 也就是我们要找到的最佳参数(系数),从而使得分类器尽可能地精确。为了寻找该最佳参数,需要用到最优化理论的一些知识。我们这里使用的是——梯度上升法。 + +### 梯度上升法 + +梯度上升法基于的思想是: 要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为 ▽ ,则函数 f(x, y) 的梯度由下式表示: + +![梯度上升计算公式](../images/5.Logistic/LR_5.png) + +这个梯度意味着要沿 x 的方向移动 ![f(x, y)对x求偏导](../images/5.Logistic/LR_6.png) ,沿 y 的方向移动 ![f(x, y)对y求偏导](../images/5.Logistic/LR_7.png) 。其中,函数f(x, y) 必须要在待计算的点上有定义并且可微。下图是一个具体的例子。 + +![梯度上升](../images/5.Logistic/LR_8.png) + +上图展示的,梯度上升算法到达每个点后都会重新估计移动的方向。从 P0 开始,计算完该点的梯度,函数就根据梯度移动到下一点 P1。在 P1 点,梯度再次被重新计算,并沿着新的梯度方向移动到 P2 。如此循环迭代,直到满足停止条件。迭代过程中,梯度算子总是保证我们能选取到最佳的移动方向。 + +上图中的梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记作 α 。用向量来表示的话,梯度上升算法的迭代公式如下: + +![梯度上升迭代公式](../images/5.Logistic/LR_9.png) + +该公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或者算法达到某个可以允许的误差范围。 + +**Note:** 我们常听到的是梯度下降算法,它与这里的梯度上升算法是一样的,只是公式中的加法需要变成减法。因此,对应的公式可以写成 + +![梯度下降迭代公式](../images/5.Logistic/LR_10.png) + +梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值。 + + +## Logistic 回归 原理 + +### Logistic 回归 工作原理 ``` - 假设现在有一些数据点,我们用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合过程就称作回归。 - Logistic 回归主要的用途是用来做分类,利用 Logistic 回归 进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。 - 我们这里所说的“回归”一词源于最佳拟合,表示要找到最佳拟合参数集。训练分类器时的做法就是寻找最佳拟合参数,使用的是最优化算法。 +每个回归系数初始化为 1 +重复 R 次: + 计算整个数据集的梯度 + 使用 alpha x gradient 更新回归系数的向量 +返回回归系数 ``` -> Logistic回归特点 +### Logistic 回归 开发流程 ``` - 优点:计算代价不高,易于理解和实现。 - 缺点:容易欠拟合,分类精度可能不高。 - 适用数据类型:数值型和标称型数据。 +收集数据: 采用任意方法收集数据 +准备数据: 由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳。 +分析数据: 采用任意方法对数据进行分析。 +训练算法: 大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。 +测试算法: 一旦训练步骤完成,分类将会很快。 +使用算法: 首先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。 ``` -> Sigmoid函数简介 +### Logistic 回归 算法特点 ``` - 我们想要的函数应该是,能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出 0 和 1 。这类函数称为海维塞德阶跃函数,或者直接称之为 单位阶跃函数。 - 但是,海维塞德阶跃函数的问题在于:该函数在跳跃点上从 0 瞬间跳跃到 1,这个瞬间跳跃过程有时候很难处理。幸好,另外的一个函数也有这样的性质(这里的性质指的是可以输出0和1的性质), - 且数学上更易处理,这就是我们下边要介绍的 Sigmoid 函数。 - - Sigmoid函数具体的计算公式如下: - f(z) = 1 / (1 + e ^(-z)) - 图5-1 给出了Sigmoid函数在不同坐标尺度下的两条曲线图。当x为0时,Sigmoid函数值为0.5。随着x的增大,对应的Sigmoid值将逼近1;而随着x的减小,Sigmoid值将逼近0。 - 如果横坐标刻度足够大(图5-1下图),Sigmoid函数看起来很像一个阶跃函数。 - 因此,为了实现Logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和带入Sigmoid函数中,进而得到一个范围在0~1之间的数值。 - 任何大于0.5的数据被分入1类,小于0.5即被归入0类。所以,Logistic回归也可以被看成是一种概率估计。 +优点: 计算代价不高,易于理解和实现。 +缺点: 容易欠拟合,分类精度可能不高。 +适用数据类型: 数值型和标称型数据。 ``` -![Logistic回归Sigmoid函数](/images/5.Logistic/Logistic回归Sigmoid函数.png "Logistic回归Sigmoid函数") -## 最优化理论初步 +## Logistic 回归 项目案例 + +### 项目案例1: 使用 Logistic 回归在简单数据集上的分类 + +#### 项目概述 + +在一个简单的数据集上,采用梯度上升法找到 Logistic 回归分类器在此数据集上的最佳回归系数 + +#### 开发流程 ``` - Sigmoid函数的输入记为z,由下面的公式得出 - z = w0x0 + w1x1 + w2x2 + ... +wnxn - 如果采用向量的写法,上述公式可以写成 z = wTx ,它表示将这两个数值向量对应元素相乘然后全部加起来即得到z值。其中的向量 x 是分类器的输入数据, - 向量 w 也就是我们要找到的最佳参数(系数),从而使得分类器尽可能地精确。为了寻找最佳参数,需要用到最优化理论的一些知识。 -``` +收集数据: 可以使用任何方法 +准备数据: 由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳 +分析数据: 画出决策边界 +训练算法: 使用梯度上升找到最佳参数 +测试算法: 使用 Logistic 回归进行分类 +使用算法: 对简单数据集中数据进行分类 +``` -> 梯度上升法 +> 收集数据: 可以使用任何方法 + +我们采用存储在 TestSet.txt 文本文件中的数据,存储格式如下: ``` - 梯度上升法基于的思想是:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为▽,则函数f(x,y)的梯度由下式表示: +-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 +``` + +绘制在图中,如下图所示: + +![简单数据集绘制在图上](../images/5.Logistic/LR_11.png) + +> 准备数据: 由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳 + +> 分析数据: 画出决策边界 + +画出数据集和 Logistic 回归最佳拟合直线的函数 + +```python +def plotBestFit(dataArr, labelMat, weights): + ''' + Desc: + 将我们得到的数据可视化展示出来 + Args: + dataArr:样本数据的特征 + labelMat:样本数据的类别标签,即目标变量 + weights:回归系数 + Returns: + None + ''' + + 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的由来,卧槽,是不是没看懂? + 首先理论上是这个样子的。 + dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) + w0*x0+w1*x1+w2*x2=f(x) + x0最开始就设置为1叻, x2就是我们画图的y值,而f(x)被我们磨合误差给算到w0,w1,w2身上去了 + 所以: w0+w1*x+w2*y=0 => y = (-w0-w1*x)/w2 + """ + y = (-weights[0]-weights[1]*x)/weights[2] + ax.plot(x, y) + plt.xlabel('X'); plt.ylabel('Y') + plt.show() +``` + +> 训练算法: 使用梯度上升找到最佳参数 + +Logistic 回归梯度上升优化算法 + +```python +# 正常的处理方案 +# 两个参数:第一个参数==> dataMatIn 是一个2维NumPy数组,每列分别代表每个不同的特征,每行则代表每个训练样本。 +# 第二个参数==> classLabels 是类别标签,它是一个 1*100 的行向量。为了便于矩阵计算,需要将该行向量转换为列向量,做法是将原向量转置,再将它赋值给labelMat。 +def gradAscent(dataMatIn, classLabels): + # 转化为矩阵[[1,1,2],[1,1,2]....] + dataMatrix = mat(dataMatIn) # 转换为 NumPy 矩阵 + # 转化为矩阵[[0,1,0,1,0,1.....]],并转制[[0],[1],[0].....] + # transpose() 行列转置函数 + # 将行向量转化为列向量 => 矩阵的转置 + labelMat = mat(classLabels).transpose() # 首先将数组转换为 NumPy 矩阵,然后再将行向量转置为列向量 + # m->数据量,样本数 n->特征数 + m,n = shape(dataMatrix) + # print m, n, '__'*10, shape(dataMatrix.transpose()), '__'*100 + # alpha代表向目标移动的步长 + alpha = 0.001 + # 迭代次数 + maxCycles = 500 + # 生成一个长度和特征数相同的矩阵,此处n为3 -> [[1],[1],[1]] + # weights 代表回归系数, 此处的 ones((n,1)) 创建一个长度和特征数相同的矩阵,其中的数全部都是 1 + weights = ones((n,1)) + for k in range(maxCycles): #heavy on matrix operations + # m*3 的矩阵 * 3*1 的单位矩阵 = m*1的矩阵 + # 那么乘上单位矩阵的意义,就代表:通过公式得到的理论值 + # 参考地址: 矩阵乘法的本质是什么? https://www.zhihu.com/question/21351965/answer/31050145 + # print 'dataMatrix====', dataMatrix + # print 'weights====', weights + # n*3 * 3*1 = n*1 + h = sigmoid(dataMatrix*weights) # 矩阵乘法 + # print 'hhhhhhh====', h + # labelMat是实际值 + error = (labelMat - h) # 向量相减 + # 0.001* (3*m)*(m*1) 表示在每一个列上的一个误差情况,最后得出 x1,x2,xn的系数的偏移量 + weights = weights + alpha * dataMatrix.transpose() * error # 矩阵乘法,最后得到回归系数 + return array(weights) +``` + +> 测试算法: 使用 Logistic 回归进行分类 + +```python +def testLR(): + # 1.收集并准备数据 + dataMat, labelMat = loadDataSet("input/5.Logistic/TestSet.txt") + + # print dataMat, '---\n', labelMat + # 2.训练模型, f(x)=a1*x1+b2*x2+..+nn*xn中 (a1,b2, .., nn).T的矩阵值 + # 因为数组没有是复制n份, array的乘法就是乘法 + dataArr = array(dataMat) + # print dataArr + weights = gradAscent(dataArr, labelMat) + # weights = stocGradAscent0(dataArr, labelMat) + # weights = stocGradAscent1(dataArr, labelMat) + # print '*'*30, weights + + # 数据可视化 + plotBestFit(dataArr, labelMat, weights) +``` + +> 使用算法: 对简单数据集中数据进行分类 + +#### 注意 + +梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理 100 个左右的数据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为 `随机梯度上升算法`。由于可以在新样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。与 “在线学习” 相对应,一次处理所有数据被称作是 “批处理”。 + +随机梯度上升算法可以写成如下的伪代码: + +``` +所有回归系数初始化为 1 +对数据集中每个样本 + 计算该样本的梯度 + 使用 alpha x gradient 更新回归系数值 +返回回归系数值 +``` + +以下是随机梯度上升算法的实现代码: + +```python +# 随机梯度上升 +# 梯度上升优化算法在每次更新数据集时都需要遍历整个数据集,计算复杂都较高 +# 随机梯度上升一次只用一个样本点来更新回归系数 +def stocGradAscent0(dataMatrix, classLabels): + m,n = shape(dataMatrix) + alpha = 0.01 + # n*1的矩阵 + # 函数ones创建一个全1的数组 + weights = ones(n) # 初始化长度为n的数组,元素全部为 1 + for i in range(m): + # sum(dataMatrix[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn,此处求出的 h 是一个具体的数值,而不是一个矩阵 + h = sigmoid(sum(dataMatrix[i]*weights)) + # print 'dataMatrix[i]===', dataMatrix[i] + # 计算真实类别与预测类别之间的差值,然后按照该差值调整回归系数 + error = classLabels[i] - h + # 0.01*(1*1)*(1*n) + print weights, "*"*10 , dataMatrix[i], "*"*10 , error + weights = weights + alpha * error * dataMatrix[i] + return weights ``` ![logistic回归梯度上升法](/images/5.Logistic/梯度上升算法.png "梯度上升法") diff --git a/images/5.Logistic/LR_1.png b/images/5.Logistic/LR_1.png new file mode 100644 index 00000000..60e83726 Binary files /dev/null and b/images/5.Logistic/LR_1.png differ diff --git a/images/5.Logistic/LR_10.png b/images/5.Logistic/LR_10.png new file mode 100644 index 00000000..177fa8f5 Binary files /dev/null and b/images/5.Logistic/LR_10.png differ diff --git a/images/5.Logistic/LR_11.png b/images/5.Logistic/LR_11.png new file mode 100644 index 00000000..4740784f Binary files /dev/null and b/images/5.Logistic/LR_11.png differ diff --git a/images/5.Logistic/LR_2.png b/images/5.Logistic/LR_2.png new file mode 100644 index 00000000..2a5447d1 Binary files /dev/null and b/images/5.Logistic/LR_2.png differ diff --git a/images/5.Logistic/LR_3.png b/images/5.Logistic/LR_3.png new file mode 100644 index 00000000..04100348 Binary files /dev/null and b/images/5.Logistic/LR_3.png differ diff --git a/images/5.Logistic/LR_4.png b/images/5.Logistic/LR_4.png new file mode 100644 index 00000000..38d7f823 Binary files /dev/null and b/images/5.Logistic/LR_4.png differ diff --git a/images/5.Logistic/LR_5.png b/images/5.Logistic/LR_5.png new file mode 100644 index 00000000..61d3b042 Binary files /dev/null and b/images/5.Logistic/LR_5.png differ diff --git a/images/5.Logistic/LR_6.png b/images/5.Logistic/LR_6.png new file mode 100644 index 00000000..9f29f469 Binary files /dev/null and b/images/5.Logistic/LR_6.png differ diff --git a/images/5.Logistic/LR_7.png b/images/5.Logistic/LR_7.png new file mode 100644 index 00000000..ec843cb6 Binary files /dev/null and b/images/5.Logistic/LR_7.png differ diff --git a/images/5.Logistic/LR_8.png b/images/5.Logistic/LR_8.png new file mode 100644 index 00000000..53100103 Binary files /dev/null and b/images/5.Logistic/LR_8.png differ diff --git a/images/5.Logistic/LR_9.png b/images/5.Logistic/LR_9.png new file mode 100644 index 00000000..1d48fcb6 Binary files /dev/null and b/images/5.Logistic/LR_9.png differ