逻辑回归--理论与python实例

一、逻辑回归介绍

逻辑回归又称logistic回归分析,是一种广义的线性回归分析模型,常用于数据挖掘,疾病自动诊断,经济预测等领域。以胃癌病情分析为例,选择两组人群,一组是胃癌组,一组是非胃癌组,两组人群必定具有不同的体征与生活方式等。因此因变量就为是否胃癌,值为“是”或“否”,自变量就可以包括很多了,如年龄、性别、饮食习惯、幽门螺杆菌感染等。自变量既可以是连续的,也可以是分类的。然后通过logistic回归分析,可以得到自变量的权重,从而可以大致了解到底哪些因素是胃癌的危险因素。同时根据该权值可以根据危险因素预测一个人患癌症的可能性。(摘自百度百科)

Logistic回归常用于预测离散型目标变量,经典的二分类算法,(也可做多分类算法,这篇先讲逻辑回归二分类算法。)它的优点:稳定,应用广泛;解释性强,易于理解。缺点:容易欠拟合,分类精度可能不高。如果拿到一个分类任务,我们可以先用逻辑回归做个基础模型,做好后,再用其他算法优化。另外逻辑回归的决策边界是可以非线性的。 

二、Sigmoid函数

为了实现Logistic回归分类器,我们可以将每个特征乘上一个回归系数再相加得到一个线性回归预测值(z=w_0x_0+w_1x_1+...+w_nx_n),而这个预测值是(- \infty,+\infty)的实数,那么如何将这个预测值映射出我们想要的分类结果呢?这就连想到Sigmoid函数,公式:\sigma (z)=\frac{1}{1+e^-^z}

由上图两个都是sigmoid函数,其自变量z取值为任意实数,值域都在[0,1]之间。这不就是我们想要的吗?我们在线性回归中得到的预测值映射到sigmoid函数,得到由值到概率的转换,得到的概率大于0.5的归为一类,小于0.5的归为一类。所以,Logistic回归也可看成是一种概率估计。下图座标尺度足够大,可以看到在x=0处,Sigmoid函数看起来很想阶跃函数。(海维塞德阶跃函数:函数在跳跃点上从0瞬间跳跃到1)

三、最佳回归系数公式推导

可见逻辑回归就是由线性回归公式代入到Sigmoid函数中,将预测值转变成概率。我们回顾一下线性回归公式:(https://blog.csdn.net/weixin_41712808/article/details/85168389

①式:z=w_0x_0+w_1x_1+...+w_nx_n=\theta ^Tx

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

将①式代入②式,得③:h_\theta (x)=g(\theta ^Tx)=\frac{1}{1+e^{-\theta ^{T}x}}

对于二分类任务,目标变量是只有0和1,如判断是否患胃癌,1代表“是”,0代表“否”。我们设目标变量为y,当y=1时,概率值公式等于h_\theta (x),那么y=0时,概率值公式等于1-h_\theta (x),为了公式方便,我们可以整合如下:

\left.\begin{matrix} P(y=1|x;\theta ) =h_\theta (x) & \\ P(y=0|x;\theta ) =1-h_\theta (x) & \end{matrix}\right\} P(y|x;\theta)=(h_\theta(x))^y(1-h_\theta(x))^{1-y}  —— ④式

虽然逻辑回归结合了sigmoid函数,但和回归分析目的一样都是求出回归系数。如何求回归系数,我们首先想到的是似然函数和对数似然。(似然表示的是在给定样本X=x_0时,参数\theta是多少。举个例子:有个篮球队拿到一个球,如果给小a投球,投中概率30%,给小b投球,投中概率80%。那么选谁投中篮球概率更高呢?答案很简单,设x是投中或未投中,小a,小b就是参数,参数等于多少时,x="投中"概率最大。)

似然函数:L(\theta )=\prod_{i=1}^{m}P(y_i|x_i;\theta)=\prod_{i=1}^{m}(h_\theta(x_i) )^{y_i}(1-h_\theta(x_i))^{1-y_i}

对数似然:l(\theta)=logL(\theta)=\sum_{i=1}^{m}(y_i*logh_\theta(x_i)+(1-y_i)*log(1-h_\theta(x_i)))   —— ⑤式

似然函数的累乘\prod是比较难计算的,但如果我们在前面加一个log对数,不就可以把累乘变成累加了吗。

似然函数求得是最大值,我们可以用最优化算法——梯度上升法,当然也可以用梯度下降。使用梯度下降前,我们需要进行一个转换:J(\theta )=-\frac{1}{m}l(\theta )   ——⑥式  (\frac{1}{m}:样本个数m)  

以下是⑥式对\theta求导过程:

J(\theta )=-\frac{1}{m}l(\theta )=-\frac{1}{m}\sum_{i=1}^{m}(y_i*logh_\theta(x_i)+(1-y_i)*log(1-h_\theta(x_i)))

\frac{\delta }{\delta_{\theta _j} }J(\theta )=-\frac{1}{m}\sum_{i=1}^{m}\begin{bmatrix} y_i\frac{1}{h_\theta (x_i)}\frac{\delta}{\delta_{\theta _j}}h_\theta (x_i)-(1-y_i)\frac{1}{1-h_\theta (x_i)}\frac{\delta }{\delta_{\theta_j} }h_\theta(x_i) \end{bmatrix}

……此处省略n行化简……

最终化简得:{\color{Red} \frac{\delta }{\delta_{\theta _j}}J(\theta )=\frac{1}{m}\sum_{i=1}^{m}(h_\theta (x_i)-y_i)x_{i}^{j}}   (m:样本数,i:第i个样本,j:第j个参数,\theta:参数,\frac{\delta }{\delta _{\theta }}:对\theta求导,y:因变量,x_{i}^{j}:第i个样本得第j个特征得自变量)在做模型得时候,我们会有多个自变量,有多少个自变量就要求多少个\theta得导。

梯度下降是要对\theta不断得迭代,从而找到最优的\theta

更新公式:{\color{Red} \theta _j=\theta _j-\alpha \frac{1}{m}\sum_{i=1}^{m}(h_\theta (x_i)-y_i)x_{i}^{j}}   (右边的\theta _j是上一次迭代的\theta _j\alpha:步长,学习率,控制学习速度,最好0.01,\frac{1}{m}\sum_{i=1}^{m} (h_\theta (x_i)-y_i)x_{i}^{j}):方向)

拓展:上面适合二分类的,那如果遇到多分类则要用softmax函数,其实就是对我们求出的结果值进行归化。这涉及到神经网络,如果后续有机会可以详细写一下。

四、逻辑回归python应用

案例背景:假设你是一个大学系的管理员,你想根据两次考试的结果来决定每个申请人的录取机会。你有以前的申请人的历史数据,你可以用它作为逻辑回归的训练集。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

import os

#导入数据
path=r'data\LogiReg_data.txt'
pdData=pd.read_csv(path,header=None,names=['Exam 1', 'Exam 2', 'Admitted'])
pdData.insert(0,'ones',1) #在最前列增加一列内容为1的变量
print('pdData.shape:',pdData.shape)
pdData.head()

#作图
positive=pdData[pdData['Admitted']==1]
negative=pdData[pdData['Admitted']==0]

fit,ax=plt.subplots(figsize=(10,5))
ax.scatter(positive['Exam 1'],positive['Exam 2'], s=30, c='b', marker='o', label='Admitted')
ax.scatter(negative['Exam 1'], negative['Exam 2'], s=30, c='r', marker='x', label='Not Admitted')
ax.legend()
ax.set_xlabel('Exam 1 Score')
ax.set_ylabel('Exam 2 Score')

接下来应该要数据预处理,但我们这里主要讲逻辑回归,那我们直接进入建模步骤。要完成的模块(函数):

  • sigmoid:映射到概率的函数  \sigma (z)=\frac{1}{1+e^-^z}
  • model:返回预测结果值  (\theta _0 , \theta _1 , \theta _2)*\begin{pmatrix} 1\\ x_1\\ x_2 \end{pmatrix}=\theta _0+\theta _1x_1+\theta _2x_2
  • cost:损失函数,根据参数计算损失-\frac{1}{m}\sum_{i=1}^{m}(y_i*logh_\theta(x_i)+(1-y_i)*log(1-h_\theta(x_i)))损失函数就是上面提到的用梯度下降法转换后的对数似然。详细介绍该函数帖子:https://blog.csdn.net/shenxiaoming77/article/details/51614601
  • gradient:计算每个参数的梯度方向,就是梯度求导公式:\frac{\delta}{\delta_{\theta_i}}=-\frac{1}{m}\sum_{i=1}^{m}(y_i-h_\theta (x_i))x_{i}^{j}
  • stopCriterion:设定停止策率
  • descent:进行参数更新
  • accuracy:计算精度
#准备数据
orig_data=pdData.as_matrix()# 将数据的panda表示形式转换为一个数组,以便进一步计算
#假设theta都为0
theta=np.zeros([1,3])

#建模块
def sigmoid(z):
    return (1 / (1 + np.exp(-z)))
def model(X, theta):
    return (sigmoid(np.dot(X, theta.T)))
def cost(X, y, theta):
    left = np.multiply(-y, np.log(model(X, theta)))
    right = np.multiply(1 - y, np.log(1 - model(X, theta)))
    return (np.sum(left - right) / (len(X)))

#计算梯度即对每个theta求偏导,确定梯度下降的方向
def gradient(X, y, theta):
    grad = np.zeros(theta.shape) #定梯度
    error = (model(X, theta)- y).ravel()
    for j in range(len(theta.ravel())): #for each parmeter
        term = np.multiply(error, X[:,j])
        grad[0, j] = np.sum(term) / len(X)
    return (grad)

STOP_ITER = 0  #根据迭代次数更新theta
STOP_COST = 1  #上一次迭代目标函数和这次迭代的目标函数差异很小,损失值变化很小时停止迭代
STOP_GRAD = 2  #根据梯度,梯度变化很小时停止迭代
def stopCriterion(type, value, threshold):
    #设定三种不同的停止策略
    if type == STOP_ITER:        return (value > threshold)
    elif type == STOP_COST:      return (abs(value[-1]-value[-2]) < threshold)
    elif type == STOP_GRAD:      return (np.linalg.norm(value) < threshold)

#洗牌
def shuffleData(data):
    np.random.shuffle(data)
    cols = data.shape[1]
    X = data[:, 0:cols-1]
    y = data[:, cols-1:]
    return (X, y)

#batchSize:1:随机梯度下降样本数,stopType:停止策率;thresh:策率对应的域值,alpha:学习率,步长。
def descent(data, theta, batchSize, stopType, thresh, alpha):
    #数据初始化
    init_time = time.time()
    i = 0 # 迭代次数
    k = 0 # batch
    X, y = shuffleData(data)  #洗牌
    grad = np.zeros(theta.shape) # 计算的梯度
    costs = [cost(X, y, theta)] # 损失值

    while True:
        grad = gradient(X[k:k+batchSize], y[k:k+batchSize], theta)
        k += batchSize #取batch数量个数据
        if k >= n: 
            k = 0 
            X, y = shuffleData(data) #重新洗牌
        theta = theta - alpha*grad # 参数更新
        costs.append(cost(X, y, theta)) # 计算新的损失
        i += 1 

        if stopType == STOP_ITER:       value = i
        elif stopType == STOP_COST:     value = costs
        elif stopType == STOP_GRAD:     value = grad
        if stopCriterion(stopType, value, thresh): break
    
    return (theta, i-1, costs, grad, time.time() - init_time)

关于bathSize从总样本集中选多少个训练集:https://blog.csdn.net/wangmengmeng99/article/details/82460437

#作图
def runExpe(data, theta, batchSize, stopType, thresh, alpha):
    #import pdb; pdb.set_trace();
    theta, iter, costs, grad, dur = descent(data, theta, batchSize, stopType, thresh, alpha)
    name = "Original" if (data[:,1]>2).sum() > 1 else "Scaled"
    name += " data - learning rate: {} - ".format(alpha)
    if batchSize==n: strDescType = "Gradient"
    elif batchSize==1:  strDescType = "Stochastic"
    else: strDescType = "Mini-batch ({})".format(batchSize)
    name += strDescType + " descent - Stop: "
    if stopType == STOP_ITER: strStop = "{} iterations".format(thresh)
    elif stopType == STOP_COST: strStop = "costs change < {}".format(thresh)
    else: strStop = "gradient norm < {}".format(thresh)
    name += strStop
    print ("***{}\nTheta: {} - Iter: {} - Last cost: {:03.2f} - Duration: {:03.2f}s".format(
        name, theta, iter, costs[-1], dur))
    fig, ax = plt.subplots(figsize=(12,4))
    ax.plot(np.arange(len(costs)), costs, 'r')
    ax.set_xlabel('Iterations')
    ax.set_ylabel('Cost')
    ax.set_title(name.upper() + ' - Error vs. Iteration')
    return (theta)

#设用STOP_ITER方式迭代,次数为5000次,步长为0.000001进行迭代
n=100
runExpe(orig_data, theta, n, STOP_ITER, thresh=5000, alpha=0.000001)

#用STOP_COST梯度下降,差异值在0.000001以下,步长为0.001进行迭代
runExpe(orig_data, theta, n, STOP_COST, thresh=0.000001, alpha=0.001)

#设定阈值 0.05,差不多需要40 000次迭代
runExpe(orig_data, theta, n, STOP_GRAD, thresh=0.05, alpha=0.001)

#设定阈值,即预测估计大于0.5时认为能被录取,小于0.5时认为不能被录取
def predict(X, theta):
    return ([1 if x >= 0.5 else 0 for x in model(X, theta)])

#精度,预测值(是否被录取)估计准确概率
scaled_X = scaled_data[:, :3]
y = scaled_data[:, 3]
predictions = predict(scaled_X, theta)
correct = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(predictions, y)]
accuracy = (sum(map(int, correct)) % len(correct))
print ('accuracy = {0}%'.format(accuracy))

拓展:如果模型图(损失值)是呈下图所示:

有点爆炸,每次迭代不稳定,损失值忽上忽下,模型不会收敛,当发现这样问题,我们可先从数据层面进行优化,再从模型层面优化。数据层面比如有无数据预处理,模型层面会不会是学习率太大,我们可以调小学习率,增加迭代次数。


end……

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章