逻辑回归

逻辑回归
逻辑回归是一种经典的二分类算法,它的决策边界可以是非线性的(包含高阶项)
Sigmoid函数
公式:g(z)=\frac{1}{1+{{e}^{-z}}},其中自变量取值为任意实数
解释:将任意的输入映射到了[0,1]区间
比如在线性回归中可以得到一个预测值(\hat{y}),再将该值映射到Sigmoid函数,就完成了由值到概率的转换,也就是分类任务
假设预测函数{{h}_{\mathbf{\theta }}}(x)=g({{\mathbf{\theta }}^{\mathbf{T}}}\mathbf{X})=\frac{1}{1+{{e}^{-{{\mathbf{\theta }}^{\mathbf{T}}}\mathbf{X}}}}
分类任务:\begin{align} & p(y=1|\mathbf{X};\mathbf{\theta })={{h}_{\mathbf{\theta }}}(x) \\ & p(y=0|\mathbf{X};\mathbf{\theta })=1-{{h}_{\mathbf{\theta }}}(x) \\ \end{align}
相当于0-1分布,整合上述式子得p(y|\mathbf{X};\mathbf{\theta })={{({{h}_{\mathbf{\theta }}}(x))}^{y}}{{(1-{{h}_{\mathbf{\theta }}}(x))}^{1-y}}
似然函数:
L(\mathbf{\theta })=\prod\limits_{i=1}^{m}{p(y|\mathbf{X};\mathbf{\theta })}=\prod\limits_{i=1}^{m}{{{({{h}_{\mathbf{\theta }}}({{x}_{i}}))}^{{{y}_{i}}}}{{(1-{{h}_{\mathbf{\theta }}}({{x}_{i}}))}^{1-{{y}_{i}}}}}
取对数似然函数:
\ln L(\mathbf{\theta })=\sum\limits_{i=1}^{m}{{{y}_{i}}\ln ({{h}_{\theta }}({{x}_{i}}))\text{+(1-}}{{y}_{i}})\ln (1-{{h}_{\theta }}({{x}_{i}}))
现在要求上述对数似然函数的最大值,如果要用梯度下降法,需要将目标函数转化为求最小值【梯度的方向是值越来越大的方向,求最小值,应该朝着梯度反方向走】,因此引入
J(\mathbf{\theta })=-\frac{1}{m}\ln L(\mathbf{\theta })=-\frac{1}{m}\sum\limits_{i=1}^{m}{{{y}_{i}}\ln ({{h}_{\theta }}({{x}_{i}}))\text{+(1-}}{{y}_{i}})\ln (1-{{h}_{\theta }}({{x}_{i}}))
对上式求偏导
\begin{align} & \frac{\partial J(\mathbf{\theta })}{\partial {{\theta }_{j}}}=-\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{y}_{i}}\frac{1}{{{h}_{\mathbf{\theta }}}({{x}_{i}})}\frac{\partial {{h}_{\mathbf{\theta }}}({{x}_{i}})}{\partial {{\theta }_{j}}}-(1-{{y}_{i}})\frac{1}{1-{{h}_{\mathbf{\theta }}}({{x}_{i}})}\frac{\partial {{h}_{\mathbf{\theta }}}({{x}_{i}})}{\partial {{\theta }_{j}}} \right]} \\ & \text{ =}-\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{y}_{i}}\frac{1}{{{h}_{\mathbf{\theta }}}({{x}_{i}})}-(1-{{y}_{i}})\frac{1}{1-{{h}_{\mathbf{\theta }}}({{x}_{i}})} \right]}\frac{\partial {{h}_{\mathbf{\theta }}}({{x}_{i}})}{\partial {{\theta }_{j}}} \\ & \text{ =}-\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{y}_{i}}\frac{1}{g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})}-(1-{{y}_{i}})\frac{1}{1-g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})} \right]}\frac{\partial g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})}{\partial {{\theta }_{j}}} \\ & \text{ =}-\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{y}_{i}}\frac{1}{g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})}-(1-{{y}_{i}})\frac{1}{1-g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})} \right]}\frac{{{e}^{-{{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{\mathbf{i}}}}}}{1+{{e}^{-{{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{\mathbf{i}}}}}}x_{i}^{j} \\ & \text{ =}-\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{y}_{i}}\frac{1}{g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})}-(1-{{y}_{i}})\frac{1}{1-g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})} \right]}g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}})(1-g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}}))x_{i}^{j} \\ & \text{ =}-\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{y}_{i}}(1-g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}}))-(1-{{y}_{i}})g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}}) \right]}x_{i}^{j} \\ & \text{ =}-\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{y}_{i}}-g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}}) \right]}x_{i}^{j} \\ & \text{ =}\frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{h}_{\mathbf{\theta }}}({{x}_{i}})-{{y}_{i}} \right]}x_{i}^{j} \\ \end{align}
确定了梯度方向之后,利用上式更新参数,得到
\theta _{j}^{'}={{\theta }_{j}}-\alpha \frac{1}{m}\sum\limits_{i=1}^{m}{\left[ {{h}_{\mathbf{\theta }}}({{x}_{i}})-{{y}_{i}} \right]}x_{i}^{j}
按照上式继续迭代
下面是一个关于实现逻辑回归的小例子
目标:建立一个逻辑回归模型来预测一个大学生是否被大学录取。
特征:两次考试成绩
导入数据

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
pdData=pd.read_csv('C:/Users/lenovo/Desktop/LogiReg_data.txt',header=None,names=['Exam 1','Exam 2','Admitted'])#header=None不让数据自己指定列名
pdData.insert(0,'ones',1) #为了考虑常数项
pdData.head()

数据如以下所示


image.png

通常在做数据处理之前,需要做一些数据预处理,比如数据标准化等,这边先不做,看看不做标准化会对结果什么影响。
逻辑回归的关键是计算损失值和梯度方向
为了计算这两个函数,需要引入sigmoid函数【将值映射到概率】

#定义sigmoid函数
def sigmoid_2(z):
    return 1/(1+np.exp(-z))
#预测函数模型
def model(X,theta):
    return sigmoid_2(np.dot(X,theta.T))

切片,分出X和y

orig_data=pdData.as_matrix()# 转换成array类型
cols=orig_data.shape[1]
X=orig_data[:,:cols-1]#前三列,切片012
y=orig_data[:,cols-1:]#和y=orig_data[:,cols-1]结果额维度不一样
theta=np.zeros([1,3]) #theta初始化是0

注:这里要注意一个关于数组切片的问题


image.png

上面画圈的两个语句一个返回一维数组,一个返回三维数组

损失函数

将对数似然函数取相反数
-\ln L(\mathbf{\theta })=-\sum\limits_{i=1}^{n}{{{y}_{i}}\ln ({{h}_{\theta }}({{x}_{i}}))-\text{(1-}}{{y}_{i}})\ln (1-{{h}_{\theta }}({{x}_{i}}))
求平均损失
J(\mathbf{\theta })=\frac{1}{n}\ln L(\mathbf{\theta })=\frac{1}{n}\sum\limits_{i=1}^{n}{\text{-}{{y}_{i}}\ln ({{h}_{\theta }}({{x}_{i}}))\text{-(1-}}{{y}_{i}})\ln (1-{{h}_{\theta }}({{x}_{i}}))
目的是求平均损失的最小值

def cost(X,y,theta):#对应于J(theta)
    left=np.multiply(-y,np.log(model(X,theta)))#np.multiply对应位置相乘
    right=np.multiply(1-y,np.log(1-model(X,theta)))
    return np.sum(left-right)/len(X)

计算梯度

\frac{\partial J(\mathbf{\theta })}{\partial {{\theta }_{j}}}\text{=}-\frac{1}{n}\sum\limits_{i=1}^{n}{\left[ {{y}_{i}}-g({{\mathbf{\theta }}^{\mathbf{T}}}{{\mathbf{x}}_{i}}) \right]}x_{i}^{j}\text{=}\frac{1}{n}\sum\limits_{i=1}^{n}{\left[ {{h}_{\mathbf{\theta }}}({{x}_{i}})-{{y}_{i}} \right]}x_{i}^{j}

def gradient(X,y,theta):
    grad=np.zeros(theta.shape)
    error=(model(X,theta)-y).ravel()
   # print(error.shape)
    for j in range(len(theta.ravel())):#求每个参数的偏导
        term=np.multiply(error,X[:,j])
        grad[0,j]=np.sum(term)/len(X)
    return grad

参数更新

首先设定三种停止策略【迭代次数、损失值差距、梯度】

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 #梯度较小

为了避免原数据存在某种规律【比如先收集女生成绩再收集男生成绩】,将原数据顺序打乱

import numpy.random
#洗牌,将数据顺序打乱
def shuffleData(data):
    np.random.shuffle(data)  #shuffle() 方法将序列的所有元素随机排序
    cols=data.shape[1]
    X=data[:,:cols-1]
    y=data[:,cols-1:]
    return X,y

然后进行参数更新【引入time库是为了比较不同策略的运行时间】

import time
def descent(data,theta,batchSize,stopType,thresh,alpha):
    #batchSize等于总样本数,即批量梯度下降;batchSize等于1,即随机梯度下降;batchSize取1~n之间的数据,即小批量梯度下降
    
    
    #梯度下降求解
    #初始化
    init_time=time.time()  #比较不同梯度下降方法的运行速度
    i=0 #迭代次数,第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 #取样本数据
        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):#如果if语句为真,就跳出整个循环
            break
        #print(grad,np.linalg.norm(grad))
    return theta,i-1,costs,grad,time.time()-init_time

注:np.linalg.norm([4,3])表示\sqrt{{{3}^{2}}+{{4}^{2}}}

def runExpe(data,theta,batchSize,stopType,thresh,alpha):
    theta,iter,costs,grad,dur=descent(data,theta,batchSize,stopType,thresh,alpha)
#     name='Original' if (data[:,2]>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='cost change < {}'.format(thresh)
#     else: strStop ='gradient norm < {}'.format(thresh)
#     name += strStop
    fig,ax=plt.subplots(figsize=(12,4))
    ax.plot(np.arange(len(costs)),costs,c='r')
    ax.set_xlabel('Iteration')
    ax.set_ylabel('Cost')
    #ax.set_title(name.upper())
    print('iter: {}, last cost: {:03.2f}, duration: {:03.2f}s'.format(iter,costs[-1],dur))
    return theta

不同的停止策略

以批量梯度下降为例

  • 停止条件为迭代次数
n=100
runExpe(orig_data,theta,n,STOP_ITER,thresh=5000,alpha=0.000001)

返回


image.png

看似损失值已经稳定在最低点0.63

  • 停止条件为损失值
    设定阈值为0.000001,需要迭代110000次左右
runExpe(orig_data,theta,n,STOP_COST,thresh=0.000001,alpha=0.001)

返回


image.png

损失值最低为0.38,似乎还可以进一步收敛

  • 停止条件为梯度大小
    设定阈值0.05,需要迭代40000次左右
runExpe(orig_data,theta,n,STOP_GRAD,thresh=0.05,alpha=0.001)

返回


image.png

损失值最小为0.49,似乎还可以进一步收敛
综上,基于批量梯度下降方法,上述三种停止条件得到的损失函数值为0.63、0.38和0.49,迭代次数分别为5000次、110000次和40000次,迭代次数越多,损失值越小

对比不同的梯度下降方法

停止策略为迭代次数

  • 随机梯度下降
runExpe(orig_data,theta,1,STOP_ITER,thresh=5000,alpha=0.001)

返回


image.png

波动非常大,迭代过程不稳定,这也是随机梯度下降的主要缺点
尝试降低学习率为0.000001,增加迭代次数为15000

runExpe(orig_data,theta,1,STOP_ITER,thresh=15000,alpha=0.000001)

返回


image.png

效果要好一些,损失值似乎稳定在0.63,根据上面的结果可知,0.63不算是一个特别合理的值

  • 小批量梯度下降
#取样本为16
runExpe(orig_data,theta,16,STOP_ITER,thresh=15000,alpha=0.001)

返回


image.png

上下波动,迭代过程不稳定
尝试调低学习率为0.000001

runExpe(orig_data,theta,16,STOP_ITER,thresh=15000,alpha=0.001)

返回


image.png

降低学习率之后没有效果,迭代过程依旧不稳定
因此,可能不是模型本身的问题,而是数据本身的问题,尝试着对数据做一些变换,此处对数据进行标准化,用标准化后的数据求解

#标准化
from sklearn import preprocessing as pp
scaled_data=orig_data.copy()
scaled_data[:,1:3]=pp.scale(scaled_data[:,1:3])
#用标准化后的数据求解
runExpe(scaled_data,theta,16,STOP_ITER,thresh=15000,alpha=0.001)

返回


image.png

损失值收敛到0.28,比0.63好很多
再尝试一下梯度为停止条件的情况

runExpe(scaled_data,theta,16,STOP_GRAD,thresh=0.004,alpha=0.001)

返回


image.png

迭代次数由15000增加到60000多,损失值由0.28降低到0.22,又改善了一步

计算模型分类结果的精确率

#设定阈值为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]
theta = runExpe(scaled_data,theta,16,STOP_GRAD,thresh=0.004,alpha=0.001)
predictions = predict(scaled_X,theta)
correct = [1 if a==b else 0 for (a,b) in zip(predictions,y)]   #真实值与预测值相等,同为1或者同为0
accuracy = sum(correct)/len(correct)

返回accuracy等于0.89
通过这个例子,算是对逻辑回归的基本原理有一个比较清晰的认识了。

  • sigmoid函数:将值映射到概率的函数
  • model:返回预测结果值
  • cost:根据参数计算损失
  • gradient:计算每个参数的梯度方向
  • descent:进行每个参数的更新
  • accuracy:计算精度
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341