第七课.Logistic回归算法

Logistic 回归,又名逻辑回归,它从线性回归发展而来,是一种广义的线性回归模型;该模型预测输出的是样本类别的条件概率分布,因而可以取概率值最大的类别作为分类结果,实质上是一个分类模型。

算法原理

sigmoid函数

首先设想一个应用场景下的分类问题:某电商网站有着大量的商品,当用户看到这些商品时,有两个选择,一是点击,二是不点击。如果点击的类型是购买,则用户的这一行为将给商家带来直接的收益。由于电商网站使用的模型要预测用户是否点击这个行为,取值只有两个。目前学过的分类算法只有 KNN,但由于 KNN 严重依赖训练数据,一旦出现近邻的噪声样本,就会对最终的预测结果产生严重的影响。另一方面,KNN 用在电商这个大数据量的场景下显然是不合适的。一是计算量大、效率低:每预测一个样本,都要和所有已知的训练样本计算距离,样本的特征空间大多是成千上万的,这样就造成了计算的困难。二是电商场景下的样本类别不平衡问题会导致正样本的召回率偏低,毕竟用户的不点击行为是占多数的,而 KNN 是使用多数表决的分类规则。

回想人类自身的判断与决策过程:当有50%以上的把握时,我们就可以判定事物的属性或者决定是否要做某一件事情,这个50%以上的把握可以理解为事件发生的概率值大于0.5 。如果将线性回归的输出值 z z z映射为[0,1]之间的概率值,那么就可以通过概率值来进行分类决策了。具体到前面商品点击的例子中,可以通过预测用户点击商品的概率值,来判断该用户是否会点击购买当前的商品。

现在的问题是找到一个可以将线性回归的预测值 z z z 映射到 [0,1] 之间任意取值的函数。恰好数学中就有sigmoid函数:

fig1
这个函数实际上是Logistic 分布的分布函数,它的形状为一条S形曲线,因而人们习惯把它叫做Sigmoid函数。观察图像可知,该函数关于点(0,0.5)中心对称,且靠近对称中心时的增长速度较快。sigmoid函数公式为:
σ ( z ) = 1 1 + e − z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+ez1
其对 z z z的导数为:
σ ′ ( z ) = σ ( z ) [ 1 − σ ( z ) ] \sigma'(z)=\sigma(z)[1-\sigma(z)] σ(z)=σ(z)[1σ(z)]
对 sigmoid 函数求导得到的是随机变量 z z z 的概率密度函数:
fig2
图中函数曲线与 x x x 轴围成的面积为1,表示随机变量 z z z 取所有可能值的概率之和为1。物体质量的密度衡量了物体质量在空间中不同位置的分布情况;概率密度则衡量了概率值1在随机变量 z z z 的不同取值区间内的分布情况。

逻辑回归模型

将线性回归的预测输出代入 Sigmoid 函数就得到了逻辑回归的预测函数:
f ( x i ) = σ ( w ⋅ x i ) , i ∈ { 1 , 2 , . . . , m } f(x_{i})=\sigma(w\cdot x_{i}),i\in \left \{ 1,2,...,m\right\} f(xi)=σ(wxi),i{1,2,...,m}
该函数预测输出的是样本 i i i 属于正例的概率值。具体地,可以理解成用户发生点击行为的概率,如果概率大于 0.5,则判断用户会点击购买当前的商品,否则不点击。

概率决策的阈值一般取 0.5,也可以根据实际情况进行调整。某医院拥有患者大量的病理特征数据,如果使用逻辑回归来预测病人得癌症的概率,进而确定病人是否患了癌症。在判断病人是否患癌症这个场景中,要从患者的角度考虑误诊风险。将阈值设置为 0.5 以上很可能导致原本患癌症的病人错过最佳的治疗时机,造成不可挽回的生命损失。正确的做法是在 0.5 的基础上调低阈值。


"回归"二字的由来
在解释这个问题之前,先了解几个概念:一个是随机事件发生的几率,另一个是对数几率。随机事件发生的几率指的是该事件发生的概率与不发生概率的比值,用来衡量该事件发生的相对可能性大小。事件 i i i 发生的几率:
f ( x i ) 1 − f ( x i ) = e w ⋅ x i , i ∈ { 1 , 2 , . . . , m } \frac{f(x_{i})}{1-f(x_{i})}=e^{w \cdot x_{i}},i\in \left \{ 1,2,...,m\right\} 1f(xi)f(xi)=ewxi,i{1,2,...,m}
对数几率,顾名思义就是上面的结果取对数:
I n ( f ( x i ) 1 − f ( x i ) ) = I n ( e w ⋅ x i ) = w ⋅ x i , i ∈ { 1 , 2 , . . . , m } In(\frac{f(x_{i})}{1-f(x_{i})})=In(e^{w \cdot x_{i}})=w \cdot x_{i},i\in \left \{ 1,2,...,m\right\} In(1f(xi)f(xi))=In(ewxi)=wxi,i{1,2,...,m}
由此发现,对数几率正是线性回归模型对样本 i i i 的预测输出值,换句话说,logistic 回归是使用线性回归来预测事件发生的对数几率,所以 logistic 回归又叫对数几率回归。


前面已经推导出随机事件发生与不发生的概率表达式。进一步地,可以将二分类的逻辑回归模型表示成如下形式的条件概率分布:
P ( y i = 1 ∣ x i ) = σ ( w ⋅ x i ) , i ∈ { 1 , 2 , . . . , m } P(y_{i}=1|x_{i})=\sigma(w\cdot x_{i}),i\in \left \{ 1,2,...,m\right\} P(yi=1xi)=σ(wxi),i{1,2,...,m}
P ( y i = 0 ∣ x i ) = 1 − σ ( w ⋅ x i ) , i ∈ { 1 , 2 , . . . , m } P(y_{i}=0|x_{i})=1-\sigma(w\cdot x_{i}),i\in \left \{ 1,2,...,m\right\} P(yi=0xi)=1σ(wxi),i{1,2,...,m}
等式左边是条件概率的表示方法,简单来说就是:给定样本特征 x i x_{i} xi 的条件下,样本标记 y i y_{i} yi 取值为 1 或 0 的概率。由此可知,二项逻辑回归是服从伯努利分布(又称两点分布、零一分布)的概率模型。逻辑回归不是直接预测样本的类别,而是对分类的可能性进行建模,这样可以得到类别概率的近似值,更有利于分类决策。

机器学习模型可以分为概率模型非概率模型,前面的线性回归和K近邻都属于非概率模型,用预测函数 y = f ( x ) y=f(x) y=f(x)来表示;而概率模型一般用条件概率分布 P ( y ∣ x ) P(y|x) P(yx) 来表示,逻辑回归既可看作是概率模型,又可看作是非概率模型。


在后面的学习中,朴素贝叶斯和决策树属于概率模型;K均值和感知机与支持向量机属于非概率模型。在监督学习中,概率模型是生成模型,非概率模型是判别模型。


极大似然估计

逻辑回归的模型参数是通过极大化对数似然函数来估计得到的。似然函数(likelihood function)是一种关于概率模型参数的函数,逻辑回归的似然函数为:
∏ i = 1 m P ( y i ∣ x i ) \prod_{i=1}^{m}P(y_{i}|x_{i}) i=1mP(yixi)
其中 m m m 是样本个数, P ( y i ∣ x i ) P(y_{i}|x_{i}) P(yixi) 是在给定样本特征 x i x_{i} xi 的条件下, 模型将样本 i i i 预测为真实标记值 y i y_{i} yi 的概率,将每个样本预测正确的概率连乘起来就得到了似然函数。由于 x i x_{i} xi y i y_{i} yi 是已知的,故似然函数的表达式是关于模型参数的函数。

二项逻辑回归的似然函数可以表示成如下样本概率连乘的形式:
∏ i = 1 m [ P ( y i = 1 ∣ x i ) ] y i [ P ( y i = 0 ∣ x i ) ] 1 − y i \prod_{i=1}^{m}[P(y_{i}=1|x_{i})]^{y_{i}}[P(y_{i}=0|x_{i})]^{1-y_{i}} i=1m[P(yi=1xi)]yi[P(yi=0xi)]1yi
二项逻辑回归的似然函数也可以写成样本预测函数连乘的形式:
∏ i = 1 m [ σ ( w ⋅ x i ) ] y i [ 1 − σ ( w ⋅ x i ) ] 1 − y i \prod_{i=1}^{m}[\sigma(w\cdot x_{i})]^{y_{i}}[1-\sigma(w\cdot x_{i})]^{1-y_{i}} i=1m[σ(wxi)]yi[1σ(wxi)]1yi
由于每个样本的概率值位于 [0,1] 之间,连乘之后将会变成一个很小的数,可能会引起浮点数下溢。当样本数 m m m 很大时,似然函数的值会趋向于 0,非常不利于之后的计算。所以,将原似然函数取对数,使其从积的形式转变为和的形式,得到的对数似然函数 L ( w ) L(w) L(w) 如下:
L ( w ) = ∑ i = 1 m [ y i l o g ( σ ( w ⋅ x i ) ) + ( 1 − y i ) l o g ( 1 − σ ( w ⋅ x i ) ) ] L(w)=\sum_{i=1}^{m}[y_{i}log(\sigma(w\cdot x_{i}))+(1-y_{i})log(1-\sigma(w\cdot x_{i}))] L(w)=i=1m[yilog(σ(wxi))+(1yi)log(1σ(wxi))]
由对数似然函数 L ( w ) L(w) L(w) 的表达式可知,模型将训练样本预测正确的概率越大,对数似然函数的值就越大。所以只要极大化 L ( w ) L(w) L(w),对应的 w w w 值就是要求解的模型参数了。

分类模型的损失函数一般使用如下形式的对数损失(又称交叉熵损失):
L ( y i , P ( y i ∣ x i ) ) = − l o g P ( y i ∣ x i ) , i ∈ { 1 , 2 , . . . , m } L(y_{i},P(y_{i}|x_{i}))=-logP(y_{i}|x_{i}),i\in \left \{ 1,2,...,m\right\} L(yi,P(yixi))=logP(yixi),i{1,2,...,m}
由公式可知,分类模型在样本 i i i 上的预测损失为:模型将样本 i i i 预测为真实分类 y i y_{i} yi 的概率取负对数。所以,对数损失函数值越小,模型在该样本上的表现就越好。进一步地,分类模型在整个训练集上的对数损失可以表示成如下形式:
− ∑ i = 1 m l o g P ( y i ∣ x i ) -\sum_{i=1}^{m}logP(y_{i}|x_{i}) i=1mlogP(yixi)
二分类模型在整个训练集上的对数损失如下:
− ∑ i = 1 m [ y i l o g ( σ ( w ⋅ x i ) ) + ( 1 − y i ) l o g ( 1 − σ ( w ⋅ x i ) ) ] -\sum_{i=1}^{m}[y_{i}log(\sigma(w\cdot x_{i}))+(1-y_{i})log(1-\sigma(w\cdot x_{i}))] i=1m[yilog(σ(wxi))+(1yi)log(1σ(wxi))]
二项逻辑回归的对数似然函数 L ( w ) L(w) L(w) 和它在训练集上的对数损失互为相反数。也就是说,极大化对数似然函数等价于极小化训练集上的对数损失。极小化对数损失使用梯度下降法,而极大化似然函数要使用梯度上升法:沿梯度方向更新模型参数。


梯度上升
根据对数似然函数:
L ( w ) = ∑ i = 1 m [ y i l o g ( σ ( w ⋅ x i ) ) + ( 1 − y i ) l o g ( 1 − σ ( w ⋅ x i ) ) ] L(w)=\sum_{i=1}^{m}[y_{i}log(\sigma(w\cdot x_{i}))+(1-y_{i})log(1-\sigma(w\cdot x_{i}))] L(w)=i=1m[yilog(σ(wxi))+(1yi)log(1σ(wxi))]
然后根据链式法则(复合函数求导法则)求解对数似然函数的梯度:
L ′ ( w ) = ∑ i = 1 m [ y i − σ ( w ⋅ x i ) ] x i L'(w)=\sum_{i=1}^{m}[y_{i}-\sigma(w\cdot x_{i})]x_{i} L(w)=i=1m[yiσ(wxi)]xi
最后沿梯度方向更新模型参数向量:
w = w + α L ′ ( w ) w=w+\alpha L'(w) w=w+αL(w)


numpy实现逻辑回归

算法改写矩阵形式

使用 numpy 实现逻辑回归算法,然后利用该算法在乳腺癌数据集上进行癌症诊断。

首先回顾算法原理,将其改写至矩阵形式,输入数据 x x x形状为 ( m , n ) (m,n) (m,n) w w w的形状为 ( n , 1 ) (n,1) (n,1),则预测为正例的概率为:
f ( x ) = σ ( x w ) f(x)=\sigma(xw) f(x)=σ(xw)
将损失函数 J ( w ) J(w) J(w)写成矩阵形式:
J ( w ) = − 1 m [ y T l o g ( f ( x ) ) + ( 1 − y ) T l o g ( 1 − f ( x ) ) ] J(w)=-\frac{1}{m}[y^{T}log(f(x))+(1-y)^{T}log(1-f(x))] J(w)=m1[yTlog(f(x))+(1y)Tlog(1f(x))]
其中,类别标签 y y y形状为 ( m , 1 ) (m,1) (m,1)

将参数 w w w形状转换为 ( 1 , n ) (1,n) (1,n)如果是极大化似然函数,参数更新需要沿着似然函数的梯度方向,前面计算过似然函数对于参数的梯度为:
L ′ ( w ) = ∑ i = 1 m [ y i − σ ( w ⋅ x i ) ] x i L'(w)=\sum_{i=1}^{m}[y_{i}-\sigma(w\cdot x_{i})]x_{i} L(w)=i=1m[yiσ(wxi)]xi
因此矩阵表达为:
w = w + α L ′ ( w ) = w + α [ y − f ( x ) ] T x w=w+\alpha L'(w)=w+\alpha[y-f(x)]^{T}x w=w+αL(w)=w+α[yf(x)]Tx

数据加载

数据加载和预处理:

from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split

# 获取乳腺癌数据集
cancer = load_breast_cancer()
# 获取数据集特征
X = cancer.data
# 获取数据集标记
y = cancer.target
# 特征归一化到 [0,1] 范围内:提升模型收敛速度
X = MinMaxScaler().fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2, random_state=2020)

模型实现

补充一个numpy的操作:

  • np.column_stack(tuple)
    将1维数组作为一列拼接到2维数组得到一个新的2维数组,或是拼接到1维数组成为2维数组:
a = np.array((1,2,3))
b = np.array((2,3,4))
np.column_stack((a,b))

"""
array([[1, 2],
       [2, 3],
       [3, 4]])
"""

通过以上分析,模型实现为:

import numpy as np
import matplotlib.pyplot as plt

class LogisticRegression:
    '''逻辑回归算法实现'''
    def __init__(self, alpha=0.1, epoch=5000, fit_bias=True, threshold=0.5):
        '''
        alpha: 学习率,控制参数更新的幅度
        epoch: 在整个训练集上训练迭代(参数更新)的次数
        fit_bias: 是否训练偏置项参数
        threshold:判定为正类的概率阈值
        '''
        self.alpha = alpha
        self.epoch = epoch
        # cost_record 记录每一次迭代后的经验风险
        self.cost_record = []
        self.fit_bias = fit_bias
        self.threshold = threshold

    # 概率预测函数
    def predict_proba(self, X_test):
        '''
        X_test: m x n 的 numpy 二维数组
        '''
        # 模型有偏置项参数时:为每个测试样本增加特征 x_0 = 1
        if self.fit_bias:
            x_0 = np.ones(X_test.shape[0])
            X_test = np.column_stack((x_0, X_test))

        # 根据预测公式返回结果
        z = np.dot(X_test, self.w)
        return 1 / (1 + np.exp(-z))

    # 类别预测函数
    def predict(self, X_test):
        '''
        X_test: m x n 的 numpy 二维数组
        '''
        probs = self.predict_proba(X_test)
        results = map(lambda x: int(x > self.threshold), probs)
        return np.array(list(results))

    # 模型训练:使用梯度下降法更新参数
    def fit(self, X_train, y_train):
        '''
        X_train: m x n 的 numpy 二维数组
        y_train:有 m 个元素的 numpy 一维数组
        '''
        # 训练偏置项参数时:为每个训练样本增加特征 x_0 = 1
        if self.fit_bias:
            x_0 = np.ones(X_train.shape[0])
            X_train = np.column_stack((x_0, X_train))

        # 训练样本数量
        m = X_train.shape[0]
        # 样本特征维数
        n = X_train.shape[1]
        # 初始模型参数
        self.w = np.ones(n)

        # 模型参数迭代
        for i in range(self.epoch):
            # 计算训练样本预测值
            z = np.dot(X_train, self.w)
            y_pred = 1 / (1 + np.exp(-z))
            # 计算训练集经验风险
            cost = -(np.dot(y_train, np.log(y_pred)) + np.dot(np.ones(m) - y_train, np.log(np.ones(m) - y_pred))) / m
            # 记录训练集经验风险
            self.cost_record.append(cost)
            # 参数更新:梯度上升法
            self.w += self.alpha / m * np.dot(y_train - y_pred, X_train)

        # 保存模型
        self.save_model()

    # 显示经验风险的收敛趋势图
    def polt_cost(self):
        plt.plot(np.arange(self.epoch), self.cost_record)
        plt.xlabel("epoch")
        plt.ylabel("cost")
        plt.show()

    # 保存模型参数
    def save_model(self):
        np.savetxt("model.txt", self.w)

    # 加载模型参数
    def load_model(self):
        self.w = np.loadtxt('model.txt')

模型的训练和预测:

# 实例化一个对象
model = LogisticRegression()
# 在训练集上训练
model.fit(X_train,y_train)
# 在测试集上预测
y_pred = model.predict(X_test)

查看模型收敛趋势图:

model.polt_cost()

fig3

实现对比

与sklearn实现的逻辑回归对比:

from sklearn import linear_model 

# 实例化一个对象
model_1 = LogisticRegression(epoch=60000) 
model_2 = linear_model.LogisticRegression() 
# 在训练集上训练
model_1.fit(X_train,y_train)
model_2.fit(X_train,y_train)
# 在测试集上预测
y_pred_1 = model_1.predict(X_test)
y_pred_2 = model_2.predict(X_test)

利用 sklearn.metrics 模块中的一些评估函数来对两个模型进行评估:

import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
from sklearn.metrics import roc_auc_score

metrics = dict()

acc_1 = accuracy_score(y_test,y_pred_1)
acc_2 = accuracy_score(y_test,y_pred_2)
metrics['准确率'] = [acc_1,acc_2]

pre_1 = precision_score(y_test,y_pred_1)
pre_2 = precision_score(y_test,y_pred_2)
metrics['精确率'] = [pre_1,pre_2]

rec_1 = recall_score(y_test,y_pred_1)
rec_2 = recall_score(y_test,y_pred_2)
metrics['召回率'] = [rec_1,rec_2]

f1_1 = f1_score(y_test,y_pred_1)
f1_2 = f1_score(y_test,y_pred_2)
metrics['F1值'] = [f1_1,f1_2]

auc_1 = roc_auc_score(y_test, model_1.predict_proba(X_test))
# model_2.predict_proba(X_test)的形状为(m,2),第0列是分类为负例的概率,第1列是分类为正例的概率
auc_2 = roc_auc_score(y_test, model_2.predict_proba(X_test)[:,1])
metrics['AUC'] = [auc_1,auc_2]

df = pd.DataFrame(metrics,index=['model_1','model_2'])

结果df为:
fig4

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值