Logistic回归-从氙气病症预测病马的死亡率

Logistic回归-从氙气病症预测病马的死亡率

机器学习课程作业,网上找了很多代码基本都是复制粘贴,原理也没有写的很清楚,这里根据个人理解整理一下。

原始数据链接:Horse Colic - UCI Machine Learning Repository

一、问题介绍

疝气病是描述马胃肠痛的术语,然而,这种病并不一定源自马的胃肠问题,其他问题也可能引发疝气病。数据一共包括368个样本和21个特征。该数据集中包含了医院检测马疝气病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别。另外,除了部分指标主观和难以测量之外,该数据还存在一个问题,数据集中有30%的值是缺失的。

二、数据处理

将样本分为两组,其中300个作为训练用,记为horseColicTraining.txt,另外68个作为测试用,记为horseColicTest.txt。对数据集中缺失的数据,以0代替。处理后数据中每行为一个样本,前21列为数据,最后一列为0或1,代表分类的类别。

三、理论分析

该问题是一个二分类问题,对于测试集中每一组数据,我们需要输出0或1的预测值,因此采用logistic回归算法。

在logistic回归中,需要寻找参数 w ∈ R n w \in R^{n} wRn b ∈ R b \in R bR,对于给定特征 x ∈ R n x \in R^{n} xRn,计算 σ ( x ) = 1 1 + e − w T x + b \sigma(x) = \frac{1}{1 + e^{- w^{T}x + b}} σ(x)=1+ewTx+b1,其中 σ \sigma σ为Sigmoid函数。若 σ ( x ) > 0.5 \sigma(x) > 0.5 σ(x)>0.5则输出1,否则输出0。

为了方便起见,我们可以记新的 x = ( x 1 ) x = \binom{x}{1} x=(1x) w = ( w b ) w = \binom{w}{b} w=(bw),从而 σ ( x ) = 1 1 + e − w T x \sigma(x) = \frac{1}{1 + e^{- w^{T}x}} σ(x)=1+ewTx1

为了寻找合适的参数,可以采用极大似然估计法:

设训练数据集为 { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x k , y k ) } \left\{ \left( {x^{1},y_{1}} \right),\left( {x^{2},y_{2}} \right),\ldots,\left( x^{k},y_{k} \right) \right\} {(x1,y1),(x2,y2),,(xk,yk)}。其中每个 x i ∈ R n x^{i} \in R^{n} xiRn y i ∈ { 0 , 1 } y_{i} \in \left\{ 0,1 \right\} yi{0,1}

由logistic回归模型,对于给定的 x x x P ( Y = 1 | x ) = e − w T x 1 + e − w T x P\left( {Y = 1} \middle| x \right) = \frac{e^{- w^{T}x}}{1 + e^{- w^{T}x}} P(Y=1x)=1+ewTxewTx P ( Y = 0 | x ) = 1 1 + e − w T x P\left( {Y = 0} \middle| x \right) = \frac{1}{1 + e^{- w^{T}x}} P(Y=0x)=1+ewTx1

f ( x ) = P ( Y = 1 | x ) f(x) = P\left( {Y = 1} \middle| x \right) f(x)=P(Y=1x)

则似然函数为

L ( w ) = ∏ i = 1 n P ( Y = y i | x i ) = ∏ i = 1 n f ( x i ) y i ( 1 − f ( x i ) ) ( 1 − y i ) L(w) = {\prod\limits_{i = 1}^{n}{P\left( Y = y_{i} \middle| x^{i} \right)}} = {\prod\limits_{i = 1}^{n}{{f\left( x^{i} \right)}^{y_{i}}\left( 1 - f\left( x^{i} \right) \right)^{(1 - y_{i})}}} L(w)=i=1nP(Y=yi xi)=i=1nf(xi)yi(1f(xi))(1yi)

我们的目标是使似然函数最大,可以对似然函数取对数,利用 y i ∈ { 0 , 1 } y_{i} \in \left\{ 0,1 \right\} yi{0,1}

l n L ( w ) = ∑ i = 1 n ( y i ln ⁡ f ( x i ) + ( 1 − y i ) l n ( 1 − f ( x i ) ) lnL(w) = {\sum\limits_{i = 1}^{n}\left( y_{i}{\ln{f\left( x^{i} \right)}} + \left( {1 - y_{i}} \right)ln\left( 1 - f\left( x^{i} \right) \right) \right.} lnL(w)=i=1n(yilnf(xi)+(1yi)ln(1f(xi))

于是只需要求解使 l n L ( w ) lnL(w) lnL(w)最大,这里可以采用梯度上升法(也可以利用梯度下降法求 − l n L ( w ) - lnL(w) lnL(w)的最小值,原理相同)。

下面计算梯度。记 z = w T x z = w^{T}x z=wTx,注意到Sigmoid函数的性质:

σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1 + e^{- z}} σ(z)=1+ez1 σ ( z ) ′ = σ ( z ) ( 1 − σ ( z ) ) {\sigma(z)}^{'} = \sigma(z)\left( 1 - \sigma(z) \right) σ(z)=σ(z)(1σ(z))

∂ l n σ ( z ) ∂ w i = ∂ l n σ ( z ) ∂ z ∂ z ∂ w i = 1 σ ( z ) d σ ( z ) d z x i = 1 σ ( z ) σ ( z ) ( 1 − σ ( z ) ) x i = ( 1 − σ ( z ) ) x i \frac{\partial ln\sigma(z)}{\partial w_{i}} = \frac{\partial ln\sigma(z)}{\partial z}\frac{\partial z}{\partial w_{i}} = \frac{1}{\sigma(z)}\frac{d\sigma(z)}{dz}x_{i} = \frac{1}{\sigma(z)}\sigma(z)\left( {1 - \sigma(z)} \right)x_{i} = \left( {1 - \sigma(z)} \right)x_{i} wil(z)=zl(z)wiz=σ(z)1dzdσ(z)xi=σ(z)1σ(z)(1σ(z))xi=(1σ(z))xi

类似可得 ∂ l n ( 1 − σ ( z ) ) ∂ w i = − σ ( z ) x i \frac{\partial ln\left( 1 - \sigma(z) \right)}{\partial w_{i}} = - \sigma(z)x_{i} wiln(1σ(z))=σ(z)xi

所以

∂ l n L ( w ) ∂ w j = ∑ i = 1 n ( y i ( 1 − f ( x i ) ) x j i − ( 1 − y i ) f ( x i ) x j i ) = ∑ i = 1 n ( y i − f ( x i ) x j i \frac{\partial lnL(w)}{\partial w_{j}} = {\sum\limits_{i = 1}^{n}\left( y_{i}\left( {1 - f\left( x^{i} \right)} \right)x_{j}^{i} - \left( {1 - y_{i}} \right)f\left( x^{i} \right)x_{j}^{i} \right)} = {\sum\limits_{i = 1}^{n}\left( y_{i} - f\left( x^{i} \right)x_{j}^{i} \right.} wjlnL(w)=i=1n(yi(1f(xi))xji(1yi)f(xi)xji)=i=1n(yif(xi)xji

(注:这里的 x j i x_{j}^{i} xji指第i个训练数据的第j个分量)

于是梯度上升算法可表示为 w j ← w j + α ∑ i = 1 n ( y i − f ( x i ) x j i \left. w_{j}\leftarrow w_{j} + \alpha{\sum\limits_{i = 1}^{n}\left( y_{i} - f\left( x^{i} \right)x_{j}^{i} \right.} \right. wjwj+αi=1n(yif(xi)xji,其中 α \alpha α为学习率。

具更体地,我们可以把训练数据中的 x x x写成矩阵形式:

x = ( ( x 1 ) , … , ( x k ) ) T = ( x 1 1 ⋯ x n 1 ⋮ ⋱ ⋮ x 1 k ⋯ x n k ) x = \left( \left( x^{1} \right),\ldots,\left( x^{k} \right) \right)^{T} = \begin{pmatrix} x_{1}^{1} & \cdots & x_{n}^{1} \\ \vdots & \ddots & \vdots \\ x_{1}^{k} & \cdots & x_{n}^{k} \end{pmatrix} x=((x1),,(xk))T= x11x1kxn1xnk

( y i − f ( x i ) \left( y_{i} - f\left( x^{i} \right) \right. (yif(xi)记为 e i e_{i} ei e = ( e 1 , … , e k ) T e = \left( e_{1},\ldots,e_{k} \right)^{T} e=(e1,,ek)T

于是 ∑ i = 1 n ( y i − f ( x i ) x j i = ∑ i = 1 n e i x j i {\sum\limits_{i = 1}^{n}\left( y_{i} - f\left( x^{i} \right)x_{j}^{i} \right.} = {\sum\limits_{i = 1}^{n}e_{i}}x_{j}^{i} i=1n(yif(xi)xji=i=1neixji

w = ( w 1 , … , w n ) w = \left( {w_{1},\ldots,w_{n}} \right) w=(w1,,wn),则梯度上升每次迭代可表示为 w ← w + α x T e \left. w\leftarrow w + \alpha x^{T}e \right. ww+αxTe

这便是代码中梯度上升的实现方式。

四、代码实现

Sigmoid()函数:每次处理一个numpy数组

def sigmoid(arr): # sigmoid阶跃函数  
 m, n = np.shape(arr) #m行1列  
 res = np.empty((m, 1))  
 for i in range(m):  
 inx = arr[i, 0]  
 if inx>=0: #对sigmoid函数的优化,避免了出现极大的数据溢出  
 res[i, 0] = (1.0/(1+np.exp(-inx)))  
 else:  
 res[i, 0] = (np.exp(inx)/(1+np.exp(inx)))  
 return res

gradAscent()函数:梯度上升算法实现

# dataMatIn 是一个2维NumPy数组,每列分别代表每个不同的特征,每行则代表每个训练样本。  
# classLabels 是类别标签,它是一个 1*300 的向量。为了便于矩阵计算,需要转置  
def gradAscent(dataMatIn, classLabels):  
 dataMatrix = np.mat(dataMatIn) # m*n矩阵,行数m=样本数,列数n=特征数  
 labelMat = np.mat(classLabels).transpose() # 行数m=样本数,列数=1  
 m,n = np.shape(dataMatrix)  
 alpha = 0.1 # alpha代表学习率  
 maxCycles = 5000 # 迭代次数  
 weights = np.ones((n, 1)) #n*1矩阵,代表回归系数,初始化为全1  
 for k in range(maxCycles):  
 # m*n 的矩阵 * n*1 的单位矩阵 = m*1的矩阵  
 h = sigmoid(dataMatrix*weights) # 得到m*1矩阵,其中每行代表对每个样本计算sigmoid函数的值  
 error = (labelMat - h) # 均为m*1矩阵 每行为这个样本的误差  
 weights = weights + alpha * dataMatrix.transpose() * error # 矩阵乘法,最后得到回归系数  
 return weights

classifyVector()函数:分类

def classifyVector(inX, weights): # 分类函数,根据回归系数和特征向量来计算 Sigmoid的值  
 prob = sigmoid(sum(inX * weights))  
 if prob > 0.5:  
 return 1.0  
 else:  
 return 0.0

colicTest()函数:数据处理,统计,模型训练和测试

# 打开测试集和训练集,并对数据进行格式化处理  
def colicTest():  
 frTrain = open('horseColicTraining.txt')  
 frTest = open('horseColicTest.txt')  
 trainingSet = []  
 trainingLabels = []  
 # 解析训练数据集中的数据特征和Labels  
 # trainingSet 中存储训练数据集的特征(前22列),trainingLabels 存储训练数据集的样本对应的分类标签(0或1)  
 for line in frTrain.readlines():  
 currLine = ["1"] + line.strip().split('\t')  
 lineArr = []  
 for i in range(22):  
 lineArr.append(float(currLine[i]))  
 trainingSet.append(lineArr)  
 trainingLabels.append(float(currLine[22]))  
 # 使用梯度上升算法 求得在此数据集上的最佳回归系数 trainWeights  
 trainWeights = gradAscent(np.array(trainingSet), trainingLabels)  
 errorCount = 0  
 numTestVec = 0.0  
 # 读取 测试数据集 进行测试,计算分类错误的样本条数和最终的错误率  
 for line in frTest.readlines():  
 numTestVec += 1.0  
 currLine = ["1"] + line.strip().split('\t')  
 lineArr = []  
 for i in range(22):  
 lineArr.append(float(currLine[i]))  
 if int(classifyVector(np.array(lineArr), trainWeights)) != int(currLine[22]):  
 errorCount += 1  
 errorRate = (float(errorCount) / numTestVec)  
 print("the error rate of this test is: %f" % errorRate)  
 return errorRate

五、测试结果

在测试中,选择学习率 α = 0.1 \alpha = 0.1 α=0.1,迭代次数=500,进行训练。

在这里插入图片描述

测试结果显示,错误率为25.37%。考虑到训练数据有30%缺失,这个结果可以接受。

附录(完整代码)

import numpy as np


def sigmoid(arr):  # sigmoid阶跃函数
    m, n = np.shape(arr)  #m行1列
    res = np.empty((m, 1))
    for i in range(m):
        inx = arr[i, 0]
        if inx>=0:      #对sigmoid函数的优化,避免了出现极大的数据溢出
            res[i, 0] = (1.0/(1+np.exp(-inx)))
        else:
            res[i, 0] = (np.exp(inx)/(1+np.exp(inx)))
    return res


# dataMatIn 是一个2维NumPy数组,每列分别代表每个不同的特征,每行则代表每个训练样本。
# classLabels 是类别标签,它是一个 1*300 的向量。为了便于矩阵计算,需要转置
def gradAscent(dataMatIn, classLabels):
    dataMatrix = np.mat(dataMatIn)  # m*n矩阵,行数m=样本数,列数n=特征数+1(22)
    labelMat = np.mat(classLabels).transpose()  # 行数m=样本数,列数=1
    m,n = np.shape(dataMatrix)
    alpha = 0.1  # alpha代表学习率
    maxCycles = 5000  # 迭代次数
    weights = np.ones((n, 1))  #n*1矩阵,代表回归系数,初始化为全1
    for k in range(maxCycles):
        # m*n 的矩阵 * n*1 的单位矩阵 = m*1的矩阵
        h = sigmoid(dataMatrix*weights)     # 得到m*1矩阵,其中每行代表对每个样本计算sigmoid函数的值
        # labelMat是实际值
        error = (labelMat - h)              # 均为m*1矩阵 每行为这个样本的误差
        weights = weights + alpha * dataMatrix.transpose() * error  # 矩阵乘法,最后得到回归系数
    return weights



def classifyVector(inX, weights):  # 分类函数,根据回归系数和特征向量来计算 Sigmoid的值
    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0


# 打开测试集和训练集,并对数据进行格式化处理
def colicTest():
    frTrain = open('horseColicTraining.txt')
    frTest = open('horseColicTest.txt')
    trainingSet = []
    trainingLabels = []
    # 解析训练数据集中的数据特征和Labels
    # trainingSet 中存储训练数据集的特征(前22列),trainingLabels 存储训练数据集的样本对应的分类标签(0或1)
    for line in frTrain.readlines():
        currLine = ["1"] + line.strip().split('\t')
        lineArr = []
        for i in range(22):
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[22]))
    # 使用梯度上升算法 求得在此数据集上的最佳回归系数 trainWeights
    trainWeights = gradAscent(np.array(trainingSet), trainingLabels)
    errorCount = 0
    numTestVec = 0.0
    # 读取 测试数据集 进行测试,计算分类错误的样本条数和最终的错误率
    for line in frTest.readlines():
        numTestVec += 1.0
        currLine = ["1"] + line.strip().split('\t')
        lineArr = []
        for i in range(22):
            lineArr.append(float(currLine[i]))
        if int(classifyVector(np.array(lineArr), trainWeights)) != int(currLine[22]):
            errorCount += 1
    errorRate = (float(errorCount) / numTestVec)
    print("the error rate of this test is: %f" % errorRate)
    return errorRate


# 调用 colicTest() 1次并求结果的平均值(确定性算法无需重复)
def multiTest():
    numTests = 1
    errorSum = 0.0
    for k in range(numTests):
        errorSum += colicTest()
    print("after %d iterations the average error rate is: %f" % (numTests, errorSum / float(numTests)))


multiTest()
均值(确定性算法无需重复)
def multiTest():
    numTests = 1
    errorSum = 0.0
    for k in range(numTests):
        errorSum += colicTest()
    print("after %d iterations the average error rate is: %f" % (numTests, errorSum / float(numTests)))


multiTest()
  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值