假设现在有一些数据点,我们用一天直线对这些点进行拟合,这个拟合过程就称作回归。
Logistic回归的一般步骤:
- 收集数据:采用任意方法收集数据。
- 准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则为最佳。
- 分析数据:采用任意方法对数据进行分析。
- 训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
- 测试算法:一旦训练步骤完成,分类将会很快。
- 使用算法:首先,我们需要输入一些数据,并将其转换成对应的结构化数值;
接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;
在这之后,我们就可以在输出的类编上做一些其他分析工作。
5.1 基于Logistic回归和Sigmoid函数的分类
Logistic回归:
优点:计算代价不高,易于理解和实现。
缺点:容易欠拟合,分类精度可能不高。
适用数据类型:数值型和标称型数据。
Sigmoid函数:
一种阶跃函数(step function)。在数学中,如果在实数域上的某个函数可以用半开区间上的指示函数的有限次线性组合来表示,那个这个函数就是阶跃函数。
在数学中指示函数(indicator function)是定义在某合集X上的函数,表示其中哪些元素属于某一子集A。
Sigmoid函数具体的计算公式如下:
σ
(
z
)
=
1
1
+
e
−
z
\sigma (z) = \frac{1}{1 + e^{-z}}
σ(z)=1+e−z1
5.2 基于最优化方法的最佳回归系数确定
Sigmoid函数的输入记为z,由下面的公式得出:
z
=
w
0
x
0
+
w
1
x
1
+
w
2
x
2
+
.
.
.
+
w
n
x
n
z = w_0 x_0 + w_1 x_1 + w_2 x_2 + ... + w_n x_n
z=w0x0+w1x1+w2x2+...+wnxn
如果采用向量的写法,上述公式可以写成:
z
=
w
t
x
z = w^{t} x
z=wtx
它表示将这两个数值向量对应元素相乘然后全部加起来即得到z的值。
其中的向量x是分类器的输入数据,向量w也就是我们要找到的最佳参数(系数)从而使得分类器尽可能地精确。
为了寻找该最佳参数,需要用到最优化地理论的一些知识: 梯度上升的最优化方法。
5.2.1 梯度上升法
梯度上升法基于的思想是:
要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果记梯度为
∇
\nabla
∇,则函数f(x, y)的梯度由下式表示:
∇
f
(
x
,
y
)
=
(
∂
f
(
x
,
y
)
∂
x
∂
f
(
x
,
y
)
∂
y
)
\nabla f(x, y) = \begin{pmatrix} \frac{\partial f(x, y)}{\partial x} \\ \frac{\partial f(x, y)}{\partial y} \end{pmatrix}
∇f(x,y)=(∂x∂f(x,y)∂y∂f(x,y))
这个梯度以为这要沿x的方向移动
∂
f
(
x
,
y
)
∂
x
\frac{\partial f(x, y)}{\partial x}
∂x∂f(x,y),沿y的方向移动
∂
f
(
x
,
y
)
∂
y
\frac{\partial f(x, y)}{\partial y}
∂y∂f(x,y)。
其中,函数f(x, y)必须要在带计算的点上有定义并且可微。
移动量的大小称为步长,记作
α
\alpha
α。用向量来便是的化,梯度上升算法的迭代公式如下:
w
:
=
w
+
α
∇
w
f
(
w
)
w := w + \alpha \nabla _w f(w)
w:=w+α∇wf(w)
该公式将一直被迭代执行,直至达到某个停止条件为止。
梯度下降算法:
它与这里的梯度上升算法是一样的,知识公式中的加法需要改变成剑法。因此,对应的公式可以写成:
w
:
=
w
−
α
∇
w
f
(
w
)
w := w - \alpha \nabla _w f(w)
w:=w−α∇wf(w)
梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值。
5.2.2 训练算法:使用梯度上升找到最佳参数
梯度上升法的伪代码如下:
每个回归系数初始化为1
重复R次:
计算整个数据集的梯度
使用alpha * gradient更新回归系数的向量
返回回归系数
Logistic回归梯度上升优化算法:
import numpy as np
def loadDataSet():
'''
打卡文本文件testSet.txt并逐行读取
:return:
'''
dataMat = [];
labelMat = [];
fr = open('./resource/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):
'''
激活函数
:param inX:
:return:
'''
return 1.0/(1 + np.exp(-inX))
def gradAscent(dataMatIn, classLabels):
'''
梯度上升
:param dataMatIn: 训练数据集,二维数组
:param classLabels:
:return:
'''
# 将输入转换为NumPy数组
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 k in range(maxCycles):
h = sigmoid(dataMatrix * weights)
error = (labelMat - h)
weights = weights + alpha * dataMatrix.transpose() * error
return weights
if __name__ == '__main__':
dataArr, labelMat = loadDataSet()
weights = gradAscent(dataArr, labelMat)
print(weights)
5.2.3 数据分析:画出决策边界
画出数据集和Logistic回归最佳拟合直线的函数
def weights_to_array(weights):
weights_transpose = np.asarray(weights.transpose())
return weights_transpose[0]
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
def plotBestFit(weights):
dataMat, labelMat = loadDataSet()
dataArr = np.array(dataMat)
n = np.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 = np.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()
if __name__ == '__main__':
dataArr, labelMat = loadDataSet()
weights = gradAscent(dataArr, labelMat)
weights_arr = weights_to_array(weights)
plotBestFit(weights_arr)
print(weights_arr)
5.2.4 训练算法:随机梯度上升
一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。
由于可以在新样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。
与在线学习算法相对应,一次处理所有数据被称作是批处理
随机梯度上升算法可以写成如下伪代码:
所有回归系数初始化为1
对数据集中每个样本
计算该样本的梯度
使用alpla * gradient更新回归系数值
返回回归系数值
随机梯度上升算法:
def stocGradAscent0(dataMatrix, classLabels):
m, n = np.shape(dataMatrix)
alpha = 0.01
weights = np.ones(n) # initialize to all ones
for i in range(m):
h = sigmoid(sum(dataMatrix[i] * weights))
error = classLabels[i] - h
weights = weights + alpha * np.array(dataMatrix[i]) * error
return weights
改进的随机梯度上升算法:
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
m, n = np.shape(dataMatrix)
weights = np.ones(n)
for j in range(numIter):
dataIndex = list(range(m))
for i in range(m):
alpha = 4/ (1.0 + j + i ) + 0.0001
randIndex = int(np.random.uniform(0, len(dataIndex)))
h = sigmoid(sum(dataMatrix[randIndex] * weights))
error = classLabels[randIndex] - h
weights = weights + alpha * error * np.array(dataMatrix[randIndex])
del(dataIndex[randIndex])
return weights
if __name__ == '__main__':
dataArr, labelMat = loadDataSet()
weights = stocGradAscent1(dataArr, labelMat)
plotBestFit(weights)
print(weights)
5.3 示例:从疝气病预测马的死亡率
示例:使用Logistic回归估计马疝气病的死亡率、
- 收集数据:给定数据文件。
- 准备数据:用Python解析文本并填充缺失值。
- 分析数据:可视化并观察数据。
- 训练算法:使用优化算法,找到最佳系数。
- 测试算法:为了量化回归的效果,需要观察错误率。根据错误率决定是否回退训练阶段,通过改变迭代的次数和步长等参数来得到更好的回归系数。
- 使用算法:实现一个简单的命令行程序来收集妈的症状并输出预测结果并非难事,这可以作为留给读者的一道习题。
5.3.1 准备数据:处理数据中的缺失值
处理确实值的可选方法:
- 使用可用的特征的却只来补齐缺失值;
- 使用特殊值来填补缺失值,如-1;
- 忽略由缺失值的样本;
- 使用相似样本的均值填补缺失值;
- 使用另外的机器学习算法预测缺失值。
5.3.2 测试算法: 用Logistic回归进行分类
Logistic回归分类函数:
def classifyVector(inX, weights):
'''
:param inX: 回归系数
:param weights: 特征向量
:return: 1或者0,如果Sigmoid值大于0.5返回1,否则返回0
'''
prob = sigmoid(sum(inX * weights))
if prob > 0.5:
return 1.0
else:
return 0.0
def colicTest():
'''
打开测试数据集和返回训练集,并对数据进行格式化处理的函数。
:return:
'''
frTrain = open('./resource/horseColicTraining.txt');
frTest = open('./resource/horseColicTest.txt')
trainingSet = [];
trainingLabels = []
for line in frTrain.readlines():
currLine = line.strip().split('\t')
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
trainWeights = stocGradAscent1(np.array(trainingSet), trainingLabels, 1000)
errorCount = 0;
numTestVec = 0.0
for line in frTest.readlines():
numTestVec += 1.0
currLine = line.strip().split('\t')
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
if int(classifyVector(np.array(lineArr), trainWeights)) != int(currLine[21]):
errorCount += 1
errorRate = (float(errorCount) / numTestVec)
print("the error rate of this test is: %f" % errorRate)
return errorRate
def multiTest():
'''
调用
:return: colicTest十次并求结果的评价值
'''
numTests = 10; errorSum = 0.0
for k in range(numTests):
errorSum += colicTest()
print("after %d iterations the average error rate is: %f" % (numTests, errorSum/float(numTests)))
5.4 本章小结
Logistic回归的目的时寻找一个非线性函数Sigmoid的最佳拟合参数,求解过程可以由最优化算法来完成。