机器学习——Logistic回归

目录

一、Logistic基本概念

1.1Logistic回归

1.2Logistic分布

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

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

2.1极大似然估计 

2.2 梯度上升法

2.3梯度下降法

三、利用Logistic模型进行分类测试

3.1数据准备

3.2编写代码查看数据集的分布情况

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

3.4分析数据:画出决策边界

3.5训练算法:随机梯度上升

3.6改进的随机梯度上升算法

四、从疝气病症预测病马的死亡率

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

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

五、总结

5.1Logistic回归的一般过程

5.2优缺点 


 

一、Logistic基本概念

1.1Logistic回归

Logistic回归(logistic regression)是统计学习中的经典分类方法,属于对数线性模型,所以也被称为对数几率回归。虽然名字有回归,但是实际上却是一种经典的分类方法,其主要思想是:根据现有的数据对分类边界线(Decision Boundary)建立回归公式,以此进行分类。

1.2Logistic分布

Logistic 分布是一种连续型的概率分布,其分布函数密度函数分别为:

F(x) = P(X\leqslant x) = \frac{1}{1+e^{-(x-\mu )/\gamma }}

f(x) = F'(X\leqslant x) = \frac{e^{-(x-\mu )/\gamma }}{\gamma (1+e^{-(x-\mu )/\gamma })^{2}}

其中,μ 表示位置参数,γ 为形状参数。我们可以看下逻辑斯蒂分布在不同的\亩\伽玛的情况下,其概率密度函数p\left( {x;\mu ,\lambda } \right)的图形:

 

 逻辑斯蒂分布在不同的\亩\伽玛的情况下,其概率分布函数P \ left({x;  \ mu,\ lambda} \ right)的图形:

 Logistic 分布是由其位置和尺度参数定义的连续分布。Logistic 分布的形状与正态分布的形状相似,但是 Logistic 分布的尾部更长,所以我们可以使用 Logistic 分布来建模比正态分布具有更长尾部和更高波峰的数据分布在深度学习中常用到的 Sigmoid 函数就是 Logistic 的分布函数在\mu =0,\gamma =1 的特殊形式。 

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

我们想要的函数应该是,能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出0或1。例如海维塞德阶跃函数,或直接称为单位阶跃函数,图像如下:

该函数的问题在于不连续、不可微,该函数在跳跃点上从0瞬间跳跃到1,有时很难处理,而Sigmoid函数也有这样的性质,且数学上更易处理。Sigmoid函数具体的计算公式如下:

\sigma (z) = \frac{1}{1+e^{-z}}

下图给出了Sigmoid函数在不同坐标尺度下的两条曲线图。当x为0时,Sigmoid函数值为0.5。 随着x的增大,对应的Sigmoid值将逼近于1;而随着x的减小,Sigmoid值将逼近于0。如果横坐标 刻度足够大,Sigmoid函数看起来很像一个阶跃函数。

 

因此,为了实现Logistic回归分类,我们可以在每个特征上都乘以一个回归系数,然后把 所有的结果值相加,将这个总和代入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类,小于0.5即被归入0类。所以,Logistic回归也可以被看成是一种概率估计

Sigmoid函数的输入记为z,
z = w^{T}x + b
其中向量x是分类器的输入数据,向量w就是我们要找到的最佳系数,b为常数。代入Sigmoid函数中得到:

y = \frac{1}{1+e^{-(w^{T}x+b)}} 
该预测函数的值表示y=1的概率,所以有y=1和y=0的概率分别为

p(y =1|x) = \frac{e^{w^{T}x+b}}{1+e^{w^{T}x+b}}

p(y=0|x) = \frac{1}{1+e^{w^{T}x+b}}

通过,我们获得z值,再通过Sigmoid函数把z值映射到0~1之间,获得数值之后就可进行分类。比如定义,大于0.5的分类为1,反之,即分类为0。所以我们要解决的问题就是获得最佳回归系数,即求解w和b得值。 

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

2.1极大似然估计 

假设:

p(y=1|x) = \pi (x)

p(y=0|x) = 1-\pi (x)

可以得到似然函数为:

\prod_{i=1}^{N}[\pi (x_{i})^{y_{i}}][1-\pi (x_{i})]^{1-y_{i}}

对似然函数取对数可得:

L(w) = \sum_{i=1}^{N}(y_{i}logP(y_{i}=1|x_{i})+(1-y)log(1-P(y_{i}=1|x_{i}))) = \sum_{i=1}^{N}(y_{i}log\frac{P(y_{i=1|x})}{1-P(y_{i}=1|x_{i})}+log(1-P(y_{i}=1|x_{i})))=\sum_{i=1}^{N}(y_{i}(w^{T}x_{i})-log(1+e^{w^{T}x_{i}}))

2.2 梯度上升法

  基本思想:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为▽,则函数f(x,y)的梯度由下式表示:

\bigtriangledown f(x,y) =\binom{\frac{\partial f(x,y)}{\partial x}}{\frac{\partial f(x,y)}{\partial y}}

  这个梯度意味着要沿x的方向移动:

        \frac{\partial f(x,y)}{\partial x}
  沿y的方向移动:

        \frac{\partial f(x,y)}{y} 

         如图,梯度上升算法到达每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一点P1。在P1点,梯度再次被重新计算,并沿新的梯度方向移动到P2。如此循环迭代,直到满足停止条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向。

用向量来表示的话,梯度算法的迭代公式如下:

w:=w+\alpha \bigtriangledown _{w}f(w),其中\alpha表示步长。

2.3梯度下降法

按梯度上升的反方向迭代公式即可

w:=w-\alpha \bigtriangledown _{w}f(w)

梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值

三、利用Logistic模型进行分类测试

3.1数据准备

        有100个样本点,每个数据有两个特征维度,将第一列看做x1的值,第二列看做x2的值,第三列作为分类的标签

3.2编写代码查看数据集的分布情况

# -*- coding:UTF-8 -*-
import matplotlib.pyplot as plt
import numpy as np
 
"""
函数说明:加载数据
Returns:
    dataMat - 数据列表
    labelMat - 标签列表
"""
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]))                                #添加标签
    fr.close()                                                            #关闭文件
    return dataMat, labelMat                                            #返回
 
"""
函数说明:绘制数据集
"""
def plotDataSet():
    dataMat, labelMat = loadDataSet()                                    #加载数据集
    dataArr = np.array(dataMat)                                            #转换成numpy的array数组
    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])    #1为正样本
        else:
            xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2])    #0为负样本
    fig = plt.figure()
    ax = fig.add_subplot(111)                                            #添加subplot
    ax.scatter(xcord1, ycord1, s = 20, c = 'black', marker = 's',alpha=.5)#绘制正样本
    ax.scatter(xcord2, ycord2, s = 20, c = 'red',alpha=.5)            #绘制负样本
    plt.title('DataSet')                                                #绘制title
    plt.xlabel('x1'); plt.ylabel('x2')                                    #绘制label
    plt.show()                                                            #显示
 
if __name__ == '__main__':
    plotDataSet()

 如图所示:假设Sigmoid函数的输入记为z,那么z=w_{0}x_{0}+w_{1}x_{1}+w_{2}x_{2},即可将数据分割开。其中,x0为全是1的向量,x1为数据集的第一列数据,x2为数据集的第二列数据。因此,我们需要求出这个方程未知的参数w0,w1,w2,也就是我们需要求的回归系数(最优参数)。

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

"""
函数说明:sigmoid函数
Parameters:
    inX - 数据
Returns:
    sigmoid函数
"""
def sigmoid(inX):
    return 1.0 / (1 + np.exp(-inX))
 
 
"""
函数说明:梯度上升算法
Parameters:
    dataMatIn - 数据集
    classLabels - 数据标签
Returns:
    weights.getA() - 求得的权重数组(最优参数)
"""
def gradAscent(dataMatIn, classLabels):
    dataMatrix = np.mat(dataMatIn)                                        #转换成numpy的mat
    labelMat = np.mat(classLabels).transpose()                            #转换成numpy的mat,并进行转置
    m, n = np.shape(dataMatrix)                                            #返回dataMatrix的大小。m为行数,n为列数。
    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.getA()                                                #将矩阵转换为数组,返回权重数组
 

 现在,我们已经求出了未知的参数w0=4.12414349,w1=0.48007329,w2=-0.6168482,接下去通过求解出的参数,我们就可以确定不同类别数据之间的分隔线,画出决策边界。 

3.4分析数据:画出决策边界

# 绘制数据集和Logistic回归最佳拟合直线
def plotBestFit(weights):
    dataMat, labelMat = loadDataSet()                      # 加载数据集,标签
    dataArr = np.array(dataMat)                               # 转换成umPy的数组
    n = np.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 = np.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()


if __name__ == '__main__':
    weights = gradAscent(dataMat, labelMat)
    plotBestFit(weights)

         从得出的结果图,我们可以看出,这个分类结果很不错了,从图上看出错的点不多。 

3.5训练算法:随机梯度上升

# 随机梯度上升算法
def stocGradAscent0(dataMatrix, classLabels):              # dataMatIn数据集、classLabels数据标签
    m, n = np.shape(dataMatrix)                               # 获取数据集矩阵的大小,m为行数,n为列数
    alpha = 0.01                                           # 目标移动的步长
    weights = np.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


# 运行测试代码
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent0(np.array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))

        可以看到,随机梯度上升算法与梯度上升算法在代码上很相似,但也有一些区别:第一,后 者的变量h和误差error都是向量,而前者则全是数值;第二,前者没有矩阵的转换过程,所有 变量的数据类型都是NumPy数组。

        随机梯度上升算法在上述数据集上的执行结果,最佳拟合直线并非最佳分类线,可以看出拟合曲线出现了很大的偏差。 

3.6改进的随机梯度上升算法

# 改进的随机梯度上升算法
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

# 测试
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent1(array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))

         改进的第一个方面在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)))处,这里通过随机选取样本来更新回归系数,每一次都是迭代未用过的样本点。这种方法将减少周期性的波动,计算量减少了,而且从上述的运行结果可以看出,回归效果挺好。

  

四、从疝气病症预测病马的死亡率

        使用 Logistic 回归来预测患有疝病的马的存活问题。疝病是描述马胃肠痛的术语。然而,这种病不一定源自马的胃肠问题,其他问题也可能引发马疝病。这个数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别。 

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

原始数据集 

 上述数据还存在一个问题,数据集中有30%的值是缺失的。数据中的缺失值是一个非常棘手的问题,下面给出了一些可选的做法:

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

数据预处理:

  1. 所有的缺失值必须要用一个实数值来替换,因为我们使用的Numpy数据类型不允许包含缺失值。这里选择实数0来替换所有的缺失值,恰好能适用于Logistic回归。这样做的直觉是我们需要的是一个在更新时不会影响系数的值。另外,由于sigmoid(0)=5,即它对结果的预测不具有任何倾向性,因此上述做法也不会对误差造成任何影响。
  2. 如果测试集中一条数据的类别标签已经缺失,那么我们将该类别数据丢弃,因为类别标签与特征不同,很难确定采用某个合适的值来替换。

处理好的数据

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

# -*- coding:UTF-8 -*-
import numpy as np
import random
 
def gradAscent(dataMatIn, classLabels):
    dataMatrix = np.mat(dataMatIn)                                        #转换成numpy的mat
    labelMat = np.mat(classLabels).transpose()                            #转换成numpy的mat,并进行转置
    m, n = np.shape(dataMatrix)                                            #返回dataMatrix的大小。m为行数,n为列数。
    alpha = 0.01                                                        #移动步长,也就是学习速率,控制更新的幅度。
    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.getA()                                                #将矩阵转换为数组,并返回
 
def sigmoid(inX):
    return 1.0 / (1 + np.exp(-inX))
 
def colicTest():
    frTrain = open('horseColicTraining.txt')                                        #打开训练集
    frTest = open('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 = gradAscent(np.array(trainingSet), trainingLabels)        #使用改进的随即上升梯度训练
    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[:,0]))!= int(currLine[-1]):
            errorCount += 1
    errorRate = (float(errorCount)/numTestVec) * 100                                 #错误率计算
    print("测试集错误率为: %.2f%%" % errorRate)
 
 
def classifyVector(inX, weights):
    prob = sigmoid(sum(inX*weights))
    if prob > 0.5: return 1.0
    else: return 0.0
 
if __name__ == '__main__':
    colicTest()

可以看到由于数据集中存在 30%的值是缺失的,因此测试集的错误率会比较高,这是很难避免的。

五、总结

5.1Logistic回归的一般过程

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

5.2优缺点 

  • 优点:计算代价不高,具有可解析性,易于实现。不仅可以预测出类别,而且可以得到近似概率预测,对许多需要利用概率辅助决策的任务很有用。
  • 缺点:容易欠拟合,分类精度可能不高。
  • 适用的数据类型:数值型和标称型数据。 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
居民出行选择行为可以被视为一个分类问题,其中特征包括年龄、性别、收入、出行目的、出行时间、出行距离等。在这里,我们可以使用逻辑回归模型进行预测。 我们可以先收集一些相关数据,然后将其分为训练集和测试集。接着,我们可以使用 Python 中的 Scikit-learn 库来构建逻辑回归模型。具体步骤如下: 1. 首先,我们需要导入所需的库: ```python import pandas as pd from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score ``` 2. 然后,我们需要加载数据并进行预处理: ```python data = pd.read_csv('travel_behavior.csv') data = data.dropna() # 删除缺失值 data = pd.get_dummies(data, columns=['purpose', 'time']) # 将分类变量转换为哑变量 X = data.drop(['choice'], axis=1) y = data['choice'] ``` 这里,我们使用了 Pandas 库加载数据,并删除了任何包含缺失值的行。然后,我们使用 `get_dummies()` 函数将分类变量转换为哑变量。最后,我们将特征(`X`)和标签(`y`)分开。 3. 接着,我们将数据分为训练集和测试集: ```python X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) ``` 这里,我们使用了 `train_test_split()` 函数来将数据分为训练集和测试集。我们选择了 20% 的数据作为测试集,而其余的数据则用于训练模型。 4. 然后,我们可以使用逻辑回归模型进行训练: ```python model = LogisticRegression() model.fit(X_train, y_train) ``` 这里,我们使用了 Scikit-learn 库中的 `LogisticRegression()` 类来构建逻辑回归模型,并使用 `fit()` 方法将模型拟合到训练数据上。 5. 最后,我们可以使用测试集来评估模型的准确性: ```python y_pred = model.predict(X_test) accuracy = accuracy_score(y_test, y_pred) print('Accuracy:', accuracy) ``` 这里,我们使用 `predict()` 方法来预测测试集中的标签,并使用 `accuracy_score()` 函数来计算模型的准确性。 通过以上步骤,我们就可以构建一个简单的逻辑回归模型,用于预测居民出行选择行为。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值