机器学习之Logistic回归

之前说了利用knn,决策树,朴素贝叶斯来实现分类的机器学习算法,各有优缺点;这次学习了新的用于分类的机器学习算法——Logistic回归的分类算法

Logistic算法最重要的就是为现有的数据集建立回归公式,得到回归直线,举一个例子:

machine-learning-4.1

上图就是一个典型的Logistic分类的应用,图中蓝色矩形色块和红色圆形色块是两个不同的类别,图中的直线就是一条回归直线。可以看到这条直线将蓝色矩形色块和红色圆形色块分割开来了,如果这个看作是一个分类问题的话,那么位于直线上方的点归类到蓝色矩形色块中,位于直线下方的点可以归类到红色圆形色块中,而这条回归直线,就是分类标准,而得到这条直线的过程,就是所要说的Logistic回归了。

Logistic回归

回归,也可以称作最佳拟合,就拿上面的例子说,希望找到一条最好的直线能够最大化的区分蓝色矩形色块与红色圆形色块,那么这条回归直线的参数就是所需要求的,并且希望得到一组最优参数,使得得到的直线是最优拟合的直线。

sigmoid函数

sigmoid(z) = 1 / (1 + exp(-z)) {z = w0 x + w1 x + w2 x + … + wn x},这是sigmoid函数的表达式,z是自变量,z的表示式就是花括号里面的式子;sigmoid函数常用于二分类当中,这是由于sigmoid函数图像决定的,sigmoid函数的值域是(0, 1),0处的取值是0.5,所以对于二分类来说,如果sigmoid函数的值小于0.5,就归类于第一个分类,否则就归类于第二个分类。

machine-learning-4.2

在sigmoid函数的z变量的表示式中,看到了这个式子:z = w0 x + w1 x + w2 x + … + wn x;这里的x,就是向分类器中输入的事物的特征值,而w0,w1,w2,…,wn,则是参数,这些个参数,就是需要求的最优参数了。但是这个w参数,有什么意义呢?重新看这个z自变量的表达式:x是事物的特征值,既然是要做分类,那么肯定是需要参考事物的特征来实现分类的,但是事物的特征有很多,到底该用哪些特征来作为分类的依据呢?w参数就是做这个的,每种特征对应一个特征的权重wi,wi的大小决定了这个特征是否是用于分类的重要特征,所以,又可以知道,w参数的个数与事物的特征数目有关。接下来,就是如何去求解这么多个特征的权重参数wi了。

梯度下降(上升)法

如何求解多个参数的最优值,普遍的做法是采用梯度下降(上升)法来求解。梯度下降(上升)法是用于求解凹(凸)函数的最大(小)值的方法,到这里可能会有疑惑,梯度是什么?梯度实际上就是高等数学里面的对函数的每个自变量求偏导数,然后这些偏导数和起来组成的向量就是梯度,用更专业一点的话说:表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。 所以,只需要沿着梯度的方向,就能够快速接近函数的最大(小)值。

machine-learning-4.3.png

所以,Logistic回归可以说是求一组特征权重的过程,特征权重的好与坏直接影响到最终分类的好坏,并且,Logistic回归最核心的部分,就是如何求的特征权重的取值,现在来看看代码是如何实现Logistic回归算法的。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import numpy as np


def loadDataSet():
dataMat = []
labelMat = []
fr = open('testSet.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


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


def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn)
labelMat = np.mat(classLabels).transpose()
m, n = np.shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = np.ones((n, 1))
# 实现梯度上升算法
for i in range(maxCycles):
h = sigmoid(dataMatrix * weights)
error = labelMat - h
# 更新权重wi
weights = weights + alpha * dataMatrix.transpose() * error
return weights


if __name__ == '__main__':
dataMatIn, classLabels = loadDataSet()
print(gradAscent(dataMatIn, classLabels))

样本示例

machine-learning-4.4

结果

1
2
3
4
/usr/bin/python /Volumes/Extended/code/PythonProject/maching-learning/Logistic/Logistic.py
[[ 4.12414349]
[ 0.48007329]
[-0.6168482 ]]

最终的回归线与样本展示

最后我们求的权重参数是三个维度,每一种特征对应一个特征权重。在函数gradascent中实现了梯度上升的算法。我们在循环中看到,每次都是运算整个样本量进行更新权重参数wi,并且是经过500轮的计算,虽然样本测试得出结果的速度非常快,但是对于实际操作中,样本容量动不动就是千万甚至亿级的样本量,这样的计算量就显得非常庞大了。这个时候,就要改进下梯度上升(下降)算法了——采用随机梯度上升(下降)算法。

改进的梯度上升(下降)算法

1
2
3
4
5
6
7
8
9
10
11
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn)
labelMat = np.mat(classLabels).transpose()
m, n = np.shape(dataMatrix)
alpha = 0.001
weights = np.ones(n)
for i in range(m):
h = sigmoid(np.sum(dataMatrix[i] * weights))
error = labelMat[i] - h
weights = weights + alpha * dataMatrix[i] * error
return weights

我们对样本每一次取等量的一部分进行更新特征取值,这样就减少了每一次更新特征权重所需的计算量大小

优化后的随机梯度上升计算结果

1
2
/usr/bin/python /Volumes/Extended/code/PythonProject/maching-learning/Logistic/Logistic.py
[ 1.01702007 0.85914348 -0.36579921]

可以看出,随机梯度上升计算的结果跟梯度上升计算的结果相差很大,原因是随机梯度上升(下降)算法虽然相比梯度上升(下降)算法减少了更多的计算量,但是却无法到达函数的最大(小)值点,只能在附近动荡,因此,我们还需要在稍稍优化下随机梯度上升(下降)算法

1
2
3
4
5
6
7
8
9
10
11
12
13
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
m, n = shape(dataMatrix)
weights = ones(n)
for j in range(numIter):
dataIndex = range(m)
for i in range(m):
alpha = 4 / (1.0 + j + i) + 0.0001
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

计算结果

1
2
/usr/bin/python /Volumes/Extended/code/PythonProject/maching-learning/Logistic/Logistic.py
[13.19641216 1.05126524 -1.73315744]

结果可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()

machine-learning-4.5.png

source code

https://github.com/chuntaojun/machine-learning-pr/tree/master/Logistic