5.1 基于Logistic回归和Sigmoid函数的分类
假设有一些数据点,这些点能在坐标轴上展示,我们利用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合过程就称作为回归。
其方程为:f ( x )= w1x1+w2x2+...+wdxd + b
Logistic回归分类的主要思想:根据现有数据对分类边界线建立回归公式,以此进行分类。
Logistic回归的一般过程
(1)收集数据:采用任意方式收集数据。
(2)数据准备:由于需要进行距离计算。因此要求数据类型为数值型。另外,结构化数据格式则最佳。
(3)分析数据:采用任意方法对数据进行分析。
(4)训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
(5)测试算法:一旦训练步骤完成,分类将会很快。
(6)使用算法:首先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。
Logistic回归:
优点:计算代价不高,易于理解和实现。
缺点:容易欠拟合,分类精度可能不高。
使用数据类型:数值型和标称型数据。
我们想要的函数是:能够接受所有的输入然后预测出类别。而海维塞德阶跃函数(单位阶跃函数)在跳跃过程中的瞬间跳跃有时很难处理。但与之类似的sigmoid函数也有类似的性质,且数学上更易处理。Sigmoid函数具体的计算公式如下:
Sigmoid函数:
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
下图给出了Sigmoid函数在不同坐标尺度下的两条曲线图。当x为0时,Sigmoid函数值为0.5。随着x的增大,对应的Sigmoid值将逼近于1;而随着x的减小,Sigmoid值将逼近于0。如果横坐标刻度足够大(如下图2),Sigmoid函数看起来很像一个阶跃函数。
因此,为了实现Logistic回归分类器,可在每个特征上乘以一个回归系数,然后把所有的结果值相加,将这个总和代入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类,小于0.5即被归入0类。所以,Logistic回归可被看成是一种概率估计。
两种坐标尺度下的Sigmoid函数图,上图的横坐标为-5到5,这时的曲线变化较为平滑;下图横坐标的尺度足够大,可以看到,在x=0点处Sigmoid函数看起来很像阶跃函数。
5.2 基于最优化方法的最佳回归系数确定
Sigmoid函数的输入记为z,由公式得出:
5.2.1梯度上升法
梯度上升法基于的思想是:要想找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。
如果梯度记为∇,则函数f(x,y)的梯度由 下式表示:
这个梯度意味着要沿x的方向移动 ,沿y的方向移动 。其中,函数f(x,y) 必须要在待计算的点上有定义并且可微。
如上图:梯度上升算法达到每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一个点P1。在P1点,梯度再次被重新计算,并沿新的梯度方向移动到P2。如此循环迭代,直到满足停止条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向。
梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记做α。用向量来表示的话,梯度上升算法的迭代公式如下:
该公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或算 法达到某个可以允许的误差范围。
梯度下降算法
梯度下降算法与梯度上升算法是一样的,只是公式中的加法需要变成减法。因此,对应的公式可以写成:
梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值。
5.2.2 数据集准备
X1 | X2 | 标签 |
-0.01761 | 14.05306 | 0 |
-1.39563 | 4.662541 | 1 |
-0.75216 | 6.53862 | 0 |
-1.32237 | 7.152853 | 0 |
0.423363 | 11.05468 | 0 |
0.406704 | 7.067335 | 1 |
0.667394 | 12.74145 | 0 |
-2.46015 | 6.866805 | 1 |
0.569411 | 9.548755 | 0 |
-0.02663 | 10.42774 | 0 |
0.850433 | 6.920334 | 1 |
1.347183 | 13.1755 | 0 |
1.176813 | 3.16702 | 1 |
-1.78187 | 9.097953 | 0 |
-0.56661 | 5.749003 | 1 |
0.931635 | 1.589505 | 1 |
100个样本点,每个点包含两个数值型特征:X1和X2。(数据有点多,稍后给出完整的)
5.2.3 训练算法:使用梯度上升找到最佳参数
通过以上的数据集使用梯度上升法找到最佳回归系数,即拟合出Logistic回归模型的最佳参数。
#加载数据
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
#Sigmoid函数
def sigmoid(inX):
return 1.0/(1+exp(-inX))
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn) #convert to NumPy matrix
labelMat = mat(classLabels).transpose() #convert to NumPy matrix
m,n = shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = ones((n,1))
for k in range(maxCycles): #heavy on matrix operations
h = sigmoid(dataMatrix*weights) #matrix mult
error = (labelMat - h) #vector subtraction
weights = weights + alpha * dataMatrix.transpose()* error #matrix mult
return weights
提示符下:
5.2.4 分析数据:画出决策边界
上面已经解出了一组回归系数,它确定了不同类别数据之间的分隔线。现在我们可以尝试去画出这个分隔线。
# 绘制数据集和Logistic回归最佳拟合直线
def plotBestFit(weights):
import matplotlib.pyplot as plt
dataMat, labelMat = loadDataSet() # 加载数据集、标签
dataArr = array(dataMat) # 转换成NumPy
n = shape(dataArr)[0] # 获取数据总数
xcord1 = []; ycord1 = [] # 存放正样本
xcord2 = []; ycord2 = [] # 存放负样本
for i in range(n): # 依据数据集的标签来对数据进行分类
if int(labelMat[i]) == 1: #标签为1,表示正样本
xcord1.append(dataArr[i, 1]); ycord1.append(dataArr[i, 2])
else: # 标签不为1,表示负样本
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) # x区间
y = (-weights[0] - weights[1] * x) / weights[2] # 最佳拟合直线
ax.plot(x, y)
plt.title('BestFit') # 标题
plt.xlabel('X1'); plt.ylabel('X2') # x,y轴的标签
plt.show()
这个分类结果很不错了,从图上看出错分点的点不多。但是,尽管例子简单且数据集很小, 这个方法却需要300次乘法大量的计算。
5.2.5 训练算法:使用梯度上升找到最佳参数
# 梯度上升算法
def gradAscent(dataMatIn, classLabels): # dataMatIn数据集、classLabels数据标签
dataMatrix = mat(dataMatIn) # 转换为NumPy矩阵
labelMat = mat(classLabels).transpose() # 转换为NumPy矩阵,并且矩阵转置
m, n = shape(dataMatrix) #数据集矩阵大小,m为行数,n为列数
alpha = 0.001 #移动步长
maxCycles = 500 #迭代次数
weights = ones((n, 1)) #权重初始化为1
for k in range(maxCycles): #重复矩阵运算
h = sigmoid(dataMatrix * weights) #矩阵相乘,计算sigmoid函数
error = (labelMat - h) #计算误差
weights = weights + alpha * dataMatrix.transpose() * error
return weights
5.2.6 训练算法:随机梯度上升算法
梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个左右的数 据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种 改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。由于可以在新 样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。与“在线学习”相对应,一次处理所有数据被称作是“批处理”。
# 随机梯度上升算法
def stocGradAscent0(dataMatrix, classLabels): #dataMatIn数据集、classLabels数据标签
m, n = shape(dataMatrix) #数据集矩阵大小 m为行数,n为列数
alpha = 0.01 # 移动步长
weights = ones(n) #初始化为1
for i in range(m): # 重复矩阵运算
h = sigmoid(sum(dataMatrix[i] * weights)) #矩阵相乘 计算sigmoid函数
error = classLabels[i] - h #误差
weights = weights + alpha * error * dataMatrix[i]
return weights
可以看到,随机梯度上升算法与梯度上升算法在代码上很相似,但也有一些区别:第一,后 者的变量h和误差error都是向量,而前者则全是数值;第二,前者没有矩阵的转换过程,所有 变量的数据类型都是NumPy数组。
上图展示了随机梯度上升算法在200次迭代过程中回归系数的变化情况。图中的X2只经过了50次迭代就达到了稳定值,但X1和X0则需要更多次的迭代。此外,在大的波动停止后,还有一些小的周期性波动。产生这种现象的原因是存在一些不能正确分类的样本点,在每次迭代时会引发系数的剧烈改变。
5.2.7 改进的随机梯度上升算法
# 改进的随机梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150): # dataMatIn数据集、classLabels数据标签、numIter迭代次数
m, n = shape(dataMatrix) #数据集矩阵的大小,m为行数,n为列数
weights = ones(n) #初始化为1
for j in range(numIter):
dataIndex = list(range(m)) #创建数据下标列表
for i in range(m):
alpha = 4 / (1.0 + j + i) + 0.0001 #apha目标移动的步长,每次迭代调整
randIndex = int(random.uniform(0, len(dataIndex))) #随机选取更新样本
h = sigmoid(sum(dataMatrix[randIndex] * weights))#矩阵相乘,计算sigmoid函数
error = classLabels[randIndex] - h #误差
weights = weights + alpha * error * dataMatrix[randIndex]#相乘 更新权重
del (dataIndex[randIndex]) # 删除已使用过的样本
return weights
第一个改进的地方在alpha = 4 / (1.0 + j + i) + 0.0001,alpha在每次迭代的时候都会调整,另外,虽然alpha会随着迭代次数不断减小,但永远不会减小到0。必须这样做的原因是为了保证在多次迭代之后新数据仍然具有一定的影响。 如果要处理的问题是动态变化的,那么可以适当加大上述常数项,来确保新的值获得更大的回归系数。另一点值得注意的是,在降低alpha的函数中,alpha每次减少1/(j+i) ,其中j是迭代次数, i是样本点的下标。
第二个改进的地方在randIndex = int(random.uniform(0, len(dataIndex)))处,这里通过随机选取样本来更新回归系数,每一次都是迭代未用过的样本点。这种方法将减少周期性的波动,计算量减少了,而且从上述的运行结果可以看出,回归效果挺好。
使用样本随机选择和alpha动态减少机制的随机梯度上升算法stocGradAscent1() 所生成的系数收敛示意图。该方法比采用固定alpha的方法收敛速度更快。
5.3 实例:从疝气病症状预测病马的死亡率
使用 Logistic 回归来预测患有疝病的马的存活问题。
实例:从疝气病症状预测病马的死亡率
(1) 收集数据:给定数据文件。(2) 准备数据:用Python解析文本文件并填充缺失值。
(3) 分析数据:可视化并观察数据。
(4) 训练算法:使用优化算法,找到最佳的系数。
(5) 测试算法:为了量化回归的效果,需要观察错误率。根据错误率决定是否回退到训练 阶段,通过改变迭代的次数和步长等参数来得到更好的回归系数。
(6) 使用算法:实现一个简单的命令行程序来收集马的症状并输出预测结果并非难事,这 可以做为留给读者的一道习题。