逻辑回归实现糖尿病预测(GD、SGD、K折)

本实验使用逻辑回归,根据数据集中包含的某些诊断测量值来预测对象是否患有糖尿病。将数据预处理后,先使用两种方法实现逻辑回归:梯度下降和调用sklearn库中的函数LogisticRegression。作为延伸,还使用了随机梯度下降和K折交叉验证。

基础理论

考虑二分类问题,给定数据集:
D = ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x N , y N ) , x i ⊆ R n , y i ∈ 0 , 1 , i = 1 , 2 , ⋯   , N D=(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N),x_i\subseteq R^n,y_i\in0,1,i=1,2,\cdots,N D=(x1,y1),(x2,y2),,(xN,yN),xiRn,yi0,1,i=1,2,,N在线性可分情况下,决策边界可以表示为: w T x + b = 0 w^Tx+b = 0 wTx+b=0,对于样本点若有 w T x + b > 0 w^Tx+b >0 wTx+b>0,那么可以判定其类别为 1,否则类别为0

逻辑回归表达式

y = f ( z ) = 1 1 + e − z , z = w T x + b y = \mathrm{f}(\mathbf{z})=\dfrac{1}{1+\mathrm{e}^{-\mathbf{z}}}, \\ \mathbf{z}=\mathbf{w}^{\mathrm{T}}\mathbf{x}+\mathrm{b} y=f(z)=1+ez1,z=wTx+b其中 f ( ⋅ ) f(\cdot) f() 为sigmoid函数。

使用了sigmoid函数映射后,分界面在 z = 0 z = 0 z=0 处的间隔将变大,有利于优化 w w w

同时,模型计算的值 y y y 可以理解为将样本划分为1类的概率,若其大于0.5,视为划分为1类,否则划分为0类。
P ( y = 1 ∣ x , w ) = 1 1 + e − w T x + b P ( y = 0 ∣ x , w ) = e − w T x + b 1 + e − w T x + b P(y=1|x,w) =\frac{1}{1+e^{-w^{T}x+b}}\\ P(y=0|x,w) =\frac{e^{-w^{T}x+b}}{1+e^{-w^{T}x+b}} P(y=1∣x,w)=1+ewTx+b1P(y=0∣x,w)=1+ewTx+bewTx+b

模型优化过程

p = P ( y = 1 ∣ x , w ) \mathbf{p} = P(y=1|x,w) p=P(y=1∣x,w), 则 P ( y = 0 ∣ x , w ) = 1 − p P(y=0|x,w)=1-\mathbf{p} P(y=0∣x,w)=1p。给定样本 x i x_i xi,其分类为 y i y_i yi的概率为:
P ( y i ∣ x i ) = p y i ( 1 − p ) 1 − y i P\left(\mathrm{y_i}|\mathbf{x_i}\right)=\mathrm{p}^{\mathrm{y_i}}\left(1-\mathrm{p}\right)^{1-\mathrm{y_i}} P(yixi)=pyi(1p)1yi优化模型参数 w w w满足所有样本的情况,则要:
max ⁡ P = ∏ i = 1 n P ( y i ∣ x i ) = ∏ i = 1 n p y i ( 1 − p ) ( 1 − y i ) \max\mathrm{P}=\prod\limits_{\mathrm{i}=1}^{\mathrm{n}}P\left(\mathrm{y}_{\mathrm{i}}|\mathrm{x}_{\mathrm{i}}\right)=\prod\limits_{\mathrm{i}=1}^{\mathrm{n}}\mathrm{p}^{\mathrm{y_i}}\left(1-\mathrm{p}\right)^{\left(1-\mathrm{y}_{\mathrm{i}}\right)} maxP=i=1nP(yixi)=i=1npyi(1p)(1yi)两边取对数后:
h ( w ) = ln ⁡ P = ∑ i = 1 n ( y ⁡ i ln ⁡ p ⁡ + ( 1 − y ⁡ i ) ln ⁡ ( 1 − p ⁡ ) ) h(w) = \ln \mathrm{P}=\sum_{i=1}^{n} (\operatorname{y}_{i}\ln\operatorname{p}+(1-\operatorname{y}_{i})\ln(1-\operatorname{p})) h(w)=lnP=i=1n(yilnp+(1yi)ln(1p))使用梯度下降算法优化参数。

计算梯度:
∇ h ( w ) = ∇ ( ∑ i = 1 n ( y i ln ⁡ p + ( 1 − y i ) ln ⁡ ( 1 − p ) ) ) = ∑ i = 1 n ( y i p + ( 1 − y i ) − p ′ 1 − p ) \begin{aligned} \nabla h\left(\mathbf{w}\right)&=\nabla(\sum_{i=1}^n (\mathbf{y_i}\ln\mathbf{p}+(1-\mathbf{y_i})\ln(1-\mathbf{p})))\\ &=\sum_{i=1}^n (\mathbf{y_i}\mathbf{p}+\left(1-\mathbf{y_i}\right)\frac{-\mathbf{p}'}{1-\mathbf{p}}) \end{aligned} h(w)=(i=1n(yilnp+(1yi)ln(1p)))=i=1n(yip+(1yi)1pp)因为sigmoid函数 f ( ⋅ ) f(\cdot) f()的梯度为 f ( ⋅ ) ∗ ( 1 − f ( ⋅ ) ) f(\cdot)*(1-f(\cdot)) f()(1f()),故 p = 1 1 + e − w T x + b p = \frac{1}{1+e^{-w^{T}x+b}} p=1+ewTx+b1的梯度为 p ′ = p ( 1 − p ) x \mathbf{p}^{\prime}=\mathbf{p}(1-\mathbf{p})\mathbf{x} p=p(1p)x

代入上式得:
∇ h ( w ) = ∑ i = 1 n ( y i ( 1 − p ) x i + ( 1 − y i ) − p x i ) = ∑ i = 1 n ( y i − p ) x i \begin{aligned} \nabla\mathrm{h}\left(\mathbf{w}\right)&=\sum\limits_{i=1}^n (\mathbf{y_i}(1-\mathbf{p})\mathbf{x_i}+(1-\mathbf{y_i})-\mathbf{p}\mathbf{x_i}) \\ &=\sum\limits_{\mathrm{i}=1}^\mathrm{n}\big(\mathrm{y_\mathrm{i}-p}\big)\mathbf{x_i} \end{aligned} h(w)=i=1n(yi(1p)xi+(1yi)pxi)=i=1n(yip)xi其中 p = 1 1 + e − w T x + b \mathbf{p}=\frac{1}{1+e^{-w^{T}x+b}} p=1+ewTx+b1.

优化参数:
w t + 1 = w t − α ∇ h ( w t ) \mathbf{w}_{\mathrm{t}+1}=\mathbf{w_t}-\alpha\mathbf{\nabla}\mathbf{h}\left(\mathbf{w_t}\right) wt+1=wtα∇h(wt)

数据集说明

此数据集来自kaggle,意在根据数据集中包含的某些诊断测量值来预测对象是否患有糖尿病。数据集包含多个医学预测变量(‘Pregnancies’,'Glucose’等)和一个目标变量(‘Outcome’)。

变量:
0列为Pregnancies(怀孕次数);
1列为Glucose(口服葡萄糖耐量试验中2小时后的血浆葡萄糖浓度);
2列为BloodPressure(舒张压,单位:mmHg);
3列为SkinThickness(三头肌皮褶厚度,单位:mm)
4列为Insulin(餐后血清胰岛素,单位:mm) 
5列为BMI,体重指数(体重(kg)/身高(m)^2)
6列为DiabetesPedigreeFunction(糖尿病家系作用)
7列为Age(年龄)
目标:
8列为Outcome(是否患有糖尿病,0或1)

库的导入

train_test_split函数用于将数据集分为训练集和测试集。StandardScaler函数用于对数据进行标准化处理。print_coefs函数打印某种逻辑回归方法的超参数、偏置项与准确率。

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

def print_coefs(name,w,b,acc):
    print("\n"+name+":")
    print("变量参数 w =",w.reshape(-1))
    print("偏置项 b = %.8f"%b)
    print("准确率:",acc)

数据预处理

  1. 处理缺省值

    由于数据集中的数据存在异常0值,需要对这些缺省值进行处理。对于缺省值,可以使用均值或者中位数进行填充。本实验使用中位数进行填充。

  2. 划分训练集与测试集

    这里使用了sklearn库中的train_test_split函数将数据集分为训练集和测试集,并设定训练集占总数据集的80%,测试集占20%。该函数的random_state参数用于设置随机数种子,以得到对数据集随机的划分。

  3. 数据标准化

    使用sklearn库中的StandardScaler函数对数据进行标准化处理,使得数据集中的每个特征服从标准正态分布。这里,先用fit_transform方法处理训练集,再用transform方法处理测试集。这时,在归一化测试集时,使用的是训练集的统计量,使用训练集和测试集更相似,使算法在两者上的表现尽可能相同。

def Preprocess(data,seed=5932):
    medians=data.median()
    names = data.columns[1:-1]
    data[names] = data[names].replace(0, np.nan)    # 将除Pregnancies和Outcome两列外所有列中的缺省值标记为NaN
    data = data.fillna(medians)                     # 使用各列的中位数填充缺省值

    X,y=data.iloc[:,:-1],data.iloc[:,-1]            # 将数据划分为X和y
    # 使用train_test_split函数将数据划分为训练集和测试集,设定训练数据占80%
    X_train,X_test,y_train,y_test=train_test_split(X,y,train_size=0.8,random_state=seed)
    
    ss = StandardScaler()
    X_train = pd.DataFrame(ss.fit_transform(X_train),columns=X.columns)
    X_test = pd.DataFrame(ss.transform(X_test),columns=X.columns)   #分别对训练和测试数据的特征进行标准化处理
    return X_train,X_test,y_train,y_test

梯度下降(GD)实现逻辑回归

根据逻辑回归的原理,
w t + 1 = w t − α ∇ h ( w ) = w t − α X T ( 1 1 + exp ⁡ ( − X w ) − y ) \mathbf{w}_{\mathrm{t}+1}=\mathbf{w}_\mathrm{t}-\alpha\mathbf{\nabla}\mathbf{h}\left(\mathbf{w}\right)=\mathbf{w}_\mathrm{t}-\alpha\mathbf{X}^{\mathrm{T}}\left(\dfrac{1}{1+\exp\left(-\mathbf{X}\mathbf{w}\right)}-\mathbf{y}\right) wt+1=wtα∇h(w)=wtαXT(1+exp(Xw)1y)其中, X \mathbf{X} X w \mathbf{w} w均经过增广。

于是,使用numpy中的矩阵处理,增广X_trainX_test,并将y_trainy_test写成列向量的形式。初始化 w \mathbf{w} w为全1列向量,学习率 α \alpha α为0.001,迭代次数为1000次,得到分类参数 w \mathbf{w} w。将 w \mathbf{w} w代入sigmoid函数,对测试集中的每个样本计算得到y的预测值。sigmoid(X_test@w)大于等于0.5时,预测为1,否则预测为0。将预测值与真实值进行比较,得到梯度下降法的准确率。

def sigmoid(x):
    return 1/(1+np.exp(-x))
    
def GradientDescent(X_train,X_test,y_train,y_test,alpha=0.001):
    X_train=np.hstack((X_train.values,np.ones((X_train.shape[0],1))))
    X_test=np.hstack((X_test.values,np.ones((X_test.shape[0],1))))          # 将X_train和X_test增加一列1
    y_train,y_test=y_train.values.reshape(-1,1),y_test.values.reshape(-1,1) # 将y_train和y_test转换为列向量
    w=np.ones([X_train.shape[1],1])                     # 初始化参数w
    for i in range(1000):
        w-=alpha*X_train.T@(sigmoid(X_train@w)-y_train) # 梯度下降
    y_predict=(sigmoid(X_test@w)>=0.5)                  # 预测结果二值化
    acc=np.mean(y_predict==y_test)                      # 计算准确率,即预测正确的样本数占总样本数的比例
    print_coefs("Gradient Descent",w[:-1],w[-1],acc)

调用库函数 LogisticRegression 实现逻辑回归

使用sklearn库中的LogisticRegression函数实现逻辑回归。其中,model.coef_对应于未增广的 w \mathbf{w} wmodel.intercept_对应于偏置项。

def Sklearn(X_train,X_test,y_train,y_test):
    from sklearn.linear_model import LogisticRegression
    model=LogisticRegression()          # 初始化逻辑回归模型
    model.fit(X_train,y_train)          # fit方法使用训练集进行训练,得到模型超参数
    y_predict=model.predict(X_test)     # predict方法使用测试集进行预测,得到预测的y
    acc=np.mean(y_predict==y_test)
    print_coefs("Sklearn",model.coef_,model.intercept_,acc)

随机梯度下降(SGD)实现逻辑回归

与梯度下降法相比,随机梯度下降法每次迭代只使用一个样本,因此速度更快。迭代公式为
w t + 1 = w t − α x t ( 1 1 + exp ⁡ ( − x t w ) − y t ) \mathbf{w}_{\mathrm{t}+1}=\mathbf{w}_\mathrm{t}-\alpha\mathbf{x}_\mathrm{t}\left(\dfrac{1}{1+\exp\left(-\mathbf{x}_\mathrm{t}\mathbf{w}\right)}-y_\mathrm{t}\right) wt+1=wtαxt(1+exp(xtw)1yt)其中 t \mathrm{t} t的取值为 1 , 2 , . . . , n 1,2,...,n 1,2,...,n n n n为训练集的样本数,在这里为X_train.shape[0]

随机梯度下降虽然迭代速度较快,但是可能会陷入局部最优解,准确率通常较梯度下降法低。

def SGD(X_train,X_test,y_train,y_test,alpha=0.001):
    X_train=np.hstack((X_train.values,np.ones((X_train.shape[0],1))))
    X_test=np.hstack((X_test.values,np.ones((X_test.shape[0],1))))
    y_train,y_test=y_train.values.reshape(-1,1),y_test.values.reshape(-1,1)
    w=np.ones([X_train.shape[1],1])
    for j in range(X_train.shape[0]):
        w-=alpha*(sigmoid(X_train[j]@w)-y_train[j])*X_train[j].reshape(-1,1)    # 随机梯度下降
    y_predict=(sigmoid(X_test@w)>=0.5)
    acc=np.mean(y_predict==y_test)
    print_coefs("Stochastic Gradient Descent",w[:-1],w[-1],acc)

K折划分实现逻辑回归

K折交叉验证将数据集划分为K个大小相似的互斥子集,每次使用K-1个子集的并集作为训练集,剩下的一个子集作为测试集,重复K次,可以更加充分地利用数据集。计算出多个模型的准确率后,从中找出一个泛化性能最好、超参数组合最佳的模型,使用这组超参数对整个训练集进行训练,得到最终的模型。

这里使用sklearn库中的KFold函数将数据集划分为K个子集,KFold函数的n_splits参数用于设置划分的份数(这里设为5),random_state参数用于设置随机数种子,以得到对数据集随机的划分。对每一种划分,使用LogisticRegression函数实现逻辑回归,得到每种模型的准确率,并取准确率最高的模型为最优模型,使用最优模型的超参数对完整训练集进行训练。

def KFoldSklearn(X_train,X_test,y_train,y_test,k=5,seed=5932):
    from sklearn.linear_model import LogisticRegression
    from sklearn.model_selection import KFold
    kf = KFold(n_splits=k,shuffle=True,random_state=seed)   # 初始化KFold对象,设定K=5
    max_acc=0                                               # max_acc用于记录几个模型中最高的准确率
    for train_index,test_index in kf.split(X_train):        # split方法用于产生每次划分的训练集的索引和测试集的索引
        model=LogisticRegression()
        model.fit(X_train.iloc[train_index],y_train.iloc[train_index])                  # iloc方法用于按索引进行切片
        acc=np.mean(model.predict(X_train.iloc[test_index])==y_train.iloc[test_index])  # 计算每个模型的准确率
        if max_acc<acc:
            max_acc=acc                                     # 找寻最高的准确率
            best_model=model                                # 记录最高准确率对应的模型,即最优模型
    y_predict=best_model.predict(X_test)                    # 使用最优模型的参数对整个测试集进行预测
    acc=np.mean(y_predict==y_test)                          # 在整个测试集上计算准确率
    print_coefs("KFold",best_model.coef_,best_model.intercept_,acc)

主函数

主函数中,使用库函数read_csv读取数据文件diabetes.csv,Preprocess函数将数据进行预处理与划分,得到训练集和测试集。GradientDescentSGDSklearnKFoldSklearn函数分别使用梯度下降、随机梯度下降、调用库函数LogisticRegression和K折划分实现逻辑回归,并打印出各自的超参数、偏置项和准确率。

if __name__=='__main__':
    data=pd.read_csv(r"diabetes.csv")
    datasets=Preprocess(data)
    GradientDescent(*datasets)      # 梯度下降
    SGD(*datasets)                  # 随机梯度下降
    Sklearn(*datasets)              # 调用库函数
    KFoldSklearn(*datasets)         # K折交叉验证
Gradient Descent:
变量参数 w = [ 0.32284836  1.06512242 -0.12022423 -0.06247633 -0.13979151  0.7101504  0.31527763  0.19169785]
偏置项 b = -0.72258697
准确率: 0.8636363636363636

Stochastic Gradient Descent:
变量参数 w = [ 0.94821771  1.00063203  0.93099904  0.93718738  0.96261441  0.96416734  0.98095596  0.93800946]
偏置项 b = 0.88192861
准确率: 0.6428571428571429

Sklearn:
变量参数 w = [ 0.31805227  1.0466346  -0.11284161 -0.05621546 -0.13238095  0.69474348  0.31040481  0.19211869]
偏置项 b = -0.71923251
准确率: 0.8636363636363636

KFold:
变量参数 w = [ 0.2517363   1.03985257 -0.07014447 -0.02436512 -0.1031414   0.65096068  0.27245426  0.28586422]
偏置项 b = -0.61968836
准确率: 0.8701298701298701

实验结果分析

可以看到,在当前随机数种子设定下,梯度下降和调用库函数进行逻辑回归的准确率达到86.36%。使用五折划分得到的准确率略有提升,为87.01%。相比之下,随机梯度下降得到的准确率较低,仅为64.28%。随机梯度下降每次迭代只使用一个样本,速度更快,但是准确率较低,更适合大体量数据集的训练。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用随机梯度下降(Stochastic Gradient Descent,SGD)求解逻辑回归是一种常见的机器学习方法。逻辑回归是一种二分类算法,用于预测一个样本属于两个类别中的哪一个。 下面是使用SGD求解逻辑回归的步骤: 1. 准备数据集:首先,你需要准备一个带有标签的数据集,其中包含输入特征和对应的类别标签。 2. 特征工程:对数据进行预处理和特征选择,例如标准化、归一化、特征缩放等。 3. 定义模型:逻辑回归模型可以用以下公式表示: z = w1*x1 + w2*x2 + ... + wn*xn + b y = sigmoid(z) 其中,w1, w2, ..., wn 是特征的权重,b 是偏置项,sigmoid 是激活函数。 4. 定义损失函数:逻辑回归通常使用交叉熵损失函数来衡量预测结果与真实标签之间的差异。 5. 初始化参数:随机初始化权重和偏置项。 6. 迭代更新参数:使用SGD算法来更新模型参数。在每次迭代中,从训练集中随机选择一个样本,计算梯度并更新参数。 7. 重复步骤6,直到达到指定的迭代次数或收敛条件。 8. 预测:使用训练好的模型对新样本进行预测。将输入特征带入模型,计算输出概率,并根据阈值将概率转换为类别标签。 这就是使用SGD求解逻辑回归的基本步骤。当然,还有其他优化算法可以用于逻辑回归的求解,如批量梯度下降(Batch Gradient Descent)和小批量梯度下降(Mini-batch Gradient Descent)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值