《机器学习实战》学习笔记(四)

第五章 Logistic 回归

引言

利用(logistic)回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。

Logistic 回归优缺点

优点:计算代价不高,易于理解和实现。
缺点:容易欠拟合,分类精度可能不高。 .
适用数据类型:数值型和标称型数据。

Logistic 回归的一般过程

  1. 收集数据:采用任意方法收集数据。
  2. 准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳。
  3. 分析数据:采用任意方法对数据进行分析。
  4. 训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
  5. 测试算法:一旦训练步驟完成,分类将会很快。
  6. 使用算法:首先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别,在这之后,我们就可以在输出的类别上做一些其他分析工作。

5.1 基 于Logistic回归和Sigmoid函数的分类

目标函数的功能: 能接受所有的输人然后预测出类别

在两个类的情况下,输出不是0就是1,类似信号系统中的阶跃函数,
阶跃函数:
ε ( t ) = { 1 , t > 0 0 , t < 0 \varepsilon(t) = \begin{cases} 1, &t> 0 \\ 0, &t<0 \end{cases} ε(t)={1,0,t>0t<0

阶跃函数图像:
在这里插入图片描述

由于0到1之间会产生一个跳跃,跳跃时不方便处理,所以引进了Sigmoid 函数。
Sigmoid 函数表达式:
σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1+e^{-z}} σ(z)=1+ez1

Sigmoid函数在不同坐标尺度下的两条曲线图:
在这里插入图片描述两种坐标尺度下的Sigmoid函数图。上图的横坐标为-5 到5,这时的曲线变化较为平滑;下图横坐标的尺度足够大,可以看到,在X= 0点处Sigmoid函数看起来很像阶跃函数。

5.2 基于最优化方法的最佳回归系数确定

将Sigmoid函数输入记为z且z满足:
z = w 0 x 0 + w 1 x 1 + w 2 x 2 + . . . + w n x n z= w_0x_0 +w_1x_1 +w_2x_2 +...+w_nx_n z=w0x0+w1x1+w2x2+...+wnxn

使用向量表达为: z = w T X z = w^TX z=wTX
X为输入数据,w为需要找到的最佳参数(系数)从而使分类器尽可能的精确。

为了寻找该最佳参数,引入了最优化理论以及一些对应方法。

梯度上升法

梯度上升法基于的思想是:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。
函数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)=(xf(x,y) yf(x,y))

实际就是函数 f f f的偏导数。
函数f(x,y)必须要在待计算的点上有定义并且可微。
在这里插入图片描述
梯度上升算法到达每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一点P1。在P1点,梯度再次被重新计算,并沿新的梯度方向移动到P2。如此循环迭代,直到满足停止条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向。

梯度算法的迭代公式如下:
w = w + α ∇ w f ( ( w ) w= w + \alpha \nabla_w f((w) w=w+αwf((w)

α \alpha α称为步长,代表移动量的大小。

该公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或算
法达到某个可以允许的误差范围。

训练算法:使用梯度上升找到最佳参数

数据集如下图:
在这里插入图片描述
梯度上升法的伪代码如下:

每个回归系数初始化为1
重复R次:
	计算整个数据集的梯度
	使用alpha x gradient更新回归系数的向量
	返回回归系数
加载数据

将文件中的数据加载到程序中

#加载数据
def loadDataSet():
    dataMat = []                                                        
    labelMat = []      
    #打开文件                                                   
    fr = open('./Ch05/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]))          
    #关闭文件                     
    fr.close()                                                            
    return dataMat, labelMat   

dataMat, labelMat = loadDataSet()  
print(dataMat,end="\n")
print(labelMat,end="\t")         
                                             

输出结果如下:
在这里插入图片描述

数据在载入时,人为的增加了一个数据1.0,书中讲的是为了方便计算,没想明白去掉哪里会产生影响。(类似于偏置电压的作用吗?)

训练算法

由两部分构成,一部分是Sigmoid函数,一部分就是梯度上升优化算法。

#Sigmoid函数
def sigmoid(inX):
    #np.exp()函数是求e^x的值的函数
    return 1.0 / (1 + np.exp(-inX))

def gradAscent(dataMatIn, classLabels):
    #转换成numpy的mat矩阵
    dataMatrix = np.mat(dataMatIn)  
    #转换成numpy的mat,并进行转置                                      
    labelMat = np.mat(classLabels).transpose()     
    #返回dataMatrix的大小。m为行数,n为列数。                      
    m, n = np.shape(dataMatrix)  
    #移动步长,也就是学习速率,控制更新的幅度。                                         
    alpha = 0.001    
    #最大迭代次数                                                    
    maxCycles = 500                                                        
    weights = np.ones((n,1))
    for k in range(maxCycles):
        #梯度上升矢量化公式,这里是矩阵相乘,[100*3]*[3*1]weights就是需要找的参数,
        h = sigmoid(dataMatrix * weights)                                
        #使用标签值与经过Sigmoid函数转换的值进行比较
        error = labelMat - h
        #根据误差值对参数进行修改
        #alpha是步进值,数据矩阵转置后是[3*100],error是[100*1]
        weights = weights + alpha * dataMatrix.transpose() * error
        #将矩阵转换为数组,返回权重数组
    return weights.getA()       


dataMat, labelMat = loadDataSet()  
# print(dataMat,end="\n")
# print(labelMat,end="\t")         
weights = gradAscent(dataMat, labelMat)
print(weights)                                         

输出参数如下:

[[ 4.12414349]
 [ 0.48007329]
 [-0.6168482 ]]

关键是下面这一行代码:

#alpha是步进值,数据矩阵转置后是[3*100],error是[100*1]
 weights = weights + alpha * dataMatrix.transpose() * error

为什么将原有的数据乘以步进值,乘以误差值,再加上现在的参数就是下一次的参数?

分析数据:画出决策边界

根据之前得到的回归系数,该系数确定了不同类别数据之间的分隔线,画出该分隔线。
实现代码如下:

def plotBestFit(weights):
    #加载数据集
    dataMat, labelMat = loadDataSet()  
    #转换成numpy的array数组                                  
    dataArr = np.array(dataMat)                                            
    n = np.shape(dataMat)[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()
    #添加subplot
	ax = fig.add_subplot(111)    
	#绘制正样本,使用散点图                                        
	ax.scatter(xcord1, ycord1, s = 20, c = 'red', marker = 's',alpha=.5)
	ax.scatter(xcord2, ycord2, s = 20, c = 'green',alpha=.5)           
    
	#设置x的范围,(-3,3)的范围是根据数据值得到的
	x = np.arange(-3.0, 3.0, 0.1)
	#我们是参考阶跃函数延伸出Sigmoid函数的,所以是以0为分界线来化分数据的。
	#所以Sigmoid函数输入Z = 0,设定0= w0*x0 + w1*x1 + w2*x2 , 然后解出x2和x1的关系式(即				分隔线的方程,注意x0 = 1)。
	y = (-weights[0] - weights[1] * x) / weights[2]
    ax.plot(x, y)
    #绘制title
    plt.title('BestFit')   
    #绘制label                                             
    plt.xlabel('X1'); plt.ylabel('X2')                                    
    plt.show()       


dataMat, labelMat = loadDataSet()  
weights = gradAscent(dataMat, labelMat)
plotBestFit(weights)

输出结果如下:
在这里插入图片描述
可以看到这条线基本将两类数据划分开了。但是,有少量点交错掺杂,使得分类结果有部分不准确。

这条分界线来源,我们是参考阶跃函数延伸出Sigmoid函数的,所以是以0为分界线来化分数据的。 所以Sigmoid函数输入Z = 0,设定$ 0= w_0x_0 +w_1x_1 +w_2x_2 $ 然后解出x2和x1的关系式(即分隔线的方程,注意x0 = 1)。

	y = (-weights[0] - weights[1] * x) / weights[2]
训练算法:随机梯度上升

梯度上升算法在每次更新回归系数时都需要遍历整个数据集, 当样本过大时,该方法的计算复杂度就太高了。

随机梯度上升算法,就是进行的改进,一次仅用一个样本点来更新回归系数。由于可以在新样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。

与 “ 在线学习”相对应,一次处理所有数据被称作是“批处理” 。

随机梯度上升算法可以写成如下的伪代码:

所有回归系数初始化为1
=对数据集中每个样本
	计算该样本的梯度
	使用alpha x gradient^ .新回归系数值
返回回归系数值

实现代码如下:
代码参考了该博客——>机器学习笔记
但是要增加一句

dataMatrix=np.array(dataMatrix)

修改了类型后,后面在计算回归参数时才不会报错。

#随机梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    #返回dataMatrix的大小。m为行数,n为列数
    m,n = np.shape(dataMatrix)  
    
    #防止后面计算系数时报类型错误
    dataMatrix=np.array(dataMatrix)
     
    #参数初始化                                             
    weights = np.ones(n)                                                       
    for j in range(numIter):                                            
        dataIndex = list(range(m))
        for i in range(m):        
            #降低alpha的大小,每次减小。   
            alpha = 4/(1.0+j+i)+0.01          
            #随机选取样本                                  
            randIndex = int(random.uniform(0,len(dataIndex)))   
            #选择随机选取的一个样本,计算h
            h = sigmoid(sum(dataMatrix[randIndex]*weights)) 
            #计算误差                  
            error = classLabels[randIndex] - h
            #更新回归系数                              
            weights = weights +  alpha * error * dataMatrix[randIndex]
            #删除已经使用的样本     
            del(dataIndex[randIndex])                                         
    return weights    
        
dataMat, labelMat = loadDataSet()                                                    
weights = stocGradAscent1(dataMat, labelMat)```
print(weights,end= '\n')

输出结果如下:

[14.60832331  1.22689838 -2.19665258]

利用该输出参数画出拟合曲线如下:

这里直接调用用poltBestFit()函数即可。

plotBestFit(weights)

在这里插入图片描述
上图可以看到绿色的部分处于分割线的下方,即为分类错误的部分,较之前的结果,总体来说要差。时间因素,因为数据量不大,所以并不是十分明显。

回归系数与迭代次数的关系

将每次循环的到的回归系数记录下来,并记录对应的次数,将得到的数据打印出来。
代码参考——>机器学习笔记
需要修改代码中的函数参数FontProperties,修改为fontproperties。

实现代码如下:

#绘制回归系数与迭代次数的关系
def plotWeights(weights_array1,weights_array2):
    #设置汉字格式
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
    #将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8)
    #当nrow=3,nclos=2时,代表fig画布被分为六个区域,axs[0][0]表示第一行第一列
    fig, axs = plt.subplots(nrows=3, ncols=2,sharex=False, sharey=False, figsize=(20,10))
    x1 = np.arange(0, len(weights_array1), 1)
    #绘制w0与迭代次数的关系
    axs[0][0].plot(x1,weights_array1[:,0])
    axs0_title_text = axs[0][0].set_title(u'梯度上升算法:回归系数与迭代次数关系',fontproperties=font)
    axs0_ylabel_text = axs[0][0].set_ylabel(u'W0',fontproperties=font)
    plt.setp(axs0_title_text, size=20, weight='bold', color='black') 
    plt.setp(axs0_ylabel_text, size=20, weight='bold', color='black')
    #绘制w1与迭代次数的关系
    axs[1][0].plot(x1,weights_array1[:,1])
    axs1_ylabel_text = axs[1][0].set_ylabel(u'W1',fontproperties=font)
    plt.setp(axs1_ylabel_text, size=20, weight='bold', color='black')
    #绘制w2与迭代次数的关系
    axs[2][0].plot(x1,weights_array1[:,2])
    axs2_xlabel_text = axs[2][0].set_xlabel(u'迭代次数',fontproperties=font)
    axs2_ylabel_text = axs[2][0].set_ylabel(u'W1',fontproperties=font)
    plt.setp(axs2_xlabel_text, size=20, weight='bold', color='black') 
    plt.setp(axs2_ylabel_text, size=20, weight='bold', color='black')


    x2 = np.arange(0, len(weights_array2), 1)
    #绘制w0与迭代次数的关系
    axs[0][1].plot(x2,weights_array2[:,0])
    axs0_title_text = axs[0][1].set_title(u'改进的随机梯度上升算法:回归系数与迭代次数关系',fontproperties=font)
    axs0_ylabel_text = axs[0][1].set_ylabel(u'W0',fontproperties=font)
    plt.setp(axs0_title_text, size=20, weight='bold', color='black') 
    plt.setp(axs0_ylabel_text, size=20, weight='bold', color='black')
    #绘制w1与迭代次数的关系
    axs[1][1].plot(x2,weights_array2[:,1])
    axs1_ylabel_text = axs[1][1].set_ylabel(u'W1',fontproperties=font)
    plt.setp(axs1_ylabel_text, size=20, weight='bold', color='black')
    #绘制w2与迭代次数的关系
    axs[2][1].plot(x2,weights_array2[:,2])
    axs2_xlabel_text = axs[2][1].set_xlabel(u'迭代次数',fontproperties=font)
    axs2_ylabel_text = axs[2][1].set_ylabel(u'W1',fontproperties=font)
    plt.setp(axs2_xlabel_text, size=20, weight='bold', color='black') 
    plt.setp(axs2_ylabel_text, size=20, weight='bold', color='black')

    plt.show()       

dataMat, labelMat = loadDataSet()          
weights1,weights_array1 = gradAscent(dataMat, labelMat)
weights2,weights_array2 = stocGradAscent1(dataMat, labelMat)
plotWeights(weights_array1,weights_array2)

此外需要在另外两个梯度上升算法中增加记录回归参数的向量,并且要返回该记录值。

如果直接使用append()函数将回归参数存到数组中,则需要使用reshape()函数将数组重新化分维度。

输出结果如下:
在这里插入图片描述
梯度上升算法这里的步长alpha为0.001,出来参数走势是上图
修改alpha为0,01得到效果如下图:
在这里插入图片描述

明显看到梯度上升算法的回归参数发生明显变化,步长长时毛刺抖动明显比小步长多,而且走势也出现较大差别。三个参数在大步长时已经出现趋于稳定,而小步长时仍处在变换中。没有稳定迹象。

在小步长下,增大梯度上升法的计算次数,直到出现稳定,对比一下区别。
小步长趋于稳定是=时。结果如下
我是将循环次数增大到50000后得到下图
在这里插入图片描述
梯度上升算法整个循环周期,回归参数并没有出现大的波动。对比改进的随机梯度算法,参数稳定周期差了几个量级。
对比小步长短循环次数与较多循环次数,回归参数的走势基本相同。

5.3 示例:从疝气病症预测病马的死亡率

准备数据:处理被据中的缺失值

对于特征数据缺失一般采用如下方法:

  1. 使用可用特征的均值来填补缺失值;
  2. 使用特殊值来填补缺失值,如-1;
  3. 忽略有缺失值的样本;
  4. 使用相似样本的均值添补缺失值;
  5. 使用另外的机器学习算法预测缺失值。

如果在测试数据集中类别标签已经缺失,那么我们的简单做法是将该条数据丢弃。

使用这个博客中的已经处理好的数据集——>机器学习笔记

测试算法:用Logistic回归进行分类

实现代码如下:

#分类函数
def classifyVector(inX, weights):
    prob = sigmoid(sum(inX*weights))
    if prob > 0.5: return 1.0
    else: return 0.0

#对马进行预测
def colicTest():
    #打开训练集
    frTrain = open('./Ch05/horseColicTraining.txt')      
    #打开测试集                                 
    frTest = open('./Ch05/horseColicTest.txt')                                                
    trainingSet = []; trainingLabels = []
    for line in frTrain.readlines():
        #分割数据
        currLine = line.strip().split('\t')
        lineArr = []

        for i in range(len(currLine)-1):
            #将每一组数据转换位浮点型存储
            lineArr.append(float(currLine[i]))
        #将转换的数据存到测试数组中
        trainingSet.append(lineArr)
        #将标签存到标签数组中
        trainingLabels.append(float(currLine[-1]))
    
    #使用改进的随机上升梯度训练,得到回归参数
    trainWeights = stocGradAscent1(np.array(trainingSet), trainingLabels, 500)        
    errorCount = 0; numTestVec = 0.0
    #测试数据
    for line in frTest.readlines():
        #统计册数数据个数
        numTestVec += 1.0
        #划分测试数据
        currLine = line.strip().split('\t')
        lineArr =[]

        for i in range(len(currLine)-1):
            lineArr.append(float(currLine[i]))
        
        #判断分类的结果与数据是否相同。
        if int(classifyVector(np.array(lineArr), trainWeights)) != int(currLine[-1]):
            errorCount += 1
    errorRate = (float(errorCount)/numTestVec) * 100                                 #错误率计算
    print("测试集错误率为: %.2f%%" % errorRate)

colicTest()

classifyVector函数为分类函数,使用已经计算好的回归参数和测试数据,得到Sigmoid返回值,当值大于0.5就返回1,小于0.5就返回0。

这里在调用之前的随机梯度算法函数时,要讲统计的回归参数相关的内容注释掉,并且要删除返回值。
此外使用程序原本的Sigmoid函数得到的准确率非常感人(试集错误率为: 56.72%)修改使用scipy.special import expit 得到的准确率能高20%左右(测试集错误率为: 32.84%)

这里有问题,测试集错误率为: 41.79% 、47.76%、25.37%这个准确率范围波动有点大?这样的测试结果能用吗?
使用书中的Sigmoid函数,得到的测试正确率同样会变化。

调整步长与循环次数后试一下:
出现测试集错误率为: 26.87%。
这个数值是之前在这个条件下测试时没有出现过得数值。但是由于他的册数准确率还是在变化,不稳定。所以不能确定准确率一定是提高了。但是要怎么让输出结果保持稳定呢?

5.4小结

本章引入了logistic回归,目的就是为了找到一个非线性函数SIgmoid的最佳拟合参数,求解过程可以由最优化算法来完成。在最优化算法中,最常用的就是梯度上升算法,而梯度上升算法又可以简化为随机梯度上升算法。此外引入了一些处理缺失数据的方法。

最后,调整随机梯度上升算法的步长与循环周期,能够影响分类结果,但是该分类存在分类结果不稳定的问题。如何调整参数与如何使分类结果稳定都是问题.,是我使用不正确吗?

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值