Machine Learning Logistic Regression

机器学习 逻辑回归

1、逻辑回归与线性回归的联系与区别

2、逻辑回归的原理

3、逻辑回归损失函数推导及优化

4、正则化与模型评估指标

5、逻辑回归的优缺点

6、样本不均衡问题解决办法

7、sklearn参数

8、代码实现

1、逻辑回归与线性回归的联系与区别

线性回归解决的是连续变量问题,那么在分类任务中可以用线性回归吗?例如判断是良性肿瘤还是恶性肿瘤,判断是垃圾邮件还是正常邮件,等等……
答案是也可以,但是效果不好,见下图:
。

图显示了是否购买玩具和年龄之间的关系,可以用线性回归拟合成一条直线,将购买标注为1,不购买标注为0,拟合后取当0.5值为阈值来划分类别。
y ^ = { 1 , f ( x ) > 0.5 0 , f ( x ) < 0.5 \hat y = \begin{cases} 1,& f(x)>0.5\\ 0,& f(x)<0.5 \end{cases}
可以看到,在途中,年龄的区分点约为19岁。
但当数据点不平衡时,很容易影响到阈值,见以下图:
在这里插入图片描述
可以看到,0值样本的年龄段往高年龄端偏移后,真实的阈值依然是19岁左右,但拟合出来的曲线的阈值往后边偏移了。可以想想,负样本越多,年龄大的人越多,偏移越严重。

可是这符合实际情况吗?实际情况是60岁的老人和80岁的老人都不会购买玩具,增加几位80岁的老人,并不会影响20岁以下人群购买玩具的概率。但因为拟合曲线原本的值域为(−∞ ∞)而转换后的值域为[0,1],阈值对变量偏移很敏感。

2、逻辑回归的原理

因此理想的替代函数应当预测分类为0或1的概率,当为1的概率大于0.5时,判断为1,当为1的概率小于0.5时,判断为0。因概率的值域为 [ 0 , 1 ] [0,1] ,这样的设定比线性回归合理很多。
常用的替代函数为Sigmoid函数,即:
h ( z ) = 1 1 + e z h(z) = \frac{1}{1+e^{-z}}
其中, z = θ T x z = \theta^T x
我们可以看到,当z大于0时,函数大于0.5;当函数等于0时,函数等于0.5;函数小于0时,函数小于0.5。如果用函数表示目标分到某一类的概率,我们可以采用以下“单位阶跃函数”来判断数据的类别:
h ( z ) = { 0 z < 0 0.5 z = 0 1 z > 0 h(z) = \left\{ \begin{aligned} 0,& & z<0 \\ 0.5, & & z=0 \\ 1, & & z>0 \end{aligned} \right.
若Z大于0,则判断为正例;若小于0,判断为反例;若等于0,可任意判别。由于Sigmoid函数单调且可导,函数在(0,1)之间程Z字型,可以很好的模拟二分类情况,因此很适合我们要解决的问题。

接下来我们来推导逻辑回归函数的优化

3、逻辑回归损失函数推导及优化

P ( y = 1 x ; θ ) = h θ ( x ) P ( y = 0 x ; θ ) = 1 h θ ( x ) P(y=1|x;\theta) = h_\theta (x) \\ P(y=0|x;\theta) = 1-h_\theta (x)
可以写作一般公式,
P ( y x ; θ ) = h ( x ) y ( 1 h ( x ) ) ( 1 y ) P(y|x;\theta)= h(x)^y (1-h(x))^{(1-y)}
极大似然函数为,
L ( θ ) = i = 1 m h θ ( x ( i ) ) y ( i ) ( 1 h θ ( x ( i ) ) ( 1 y ( i ) ) L(\theta) = \prod^{m}_{i=1}h_\theta (x^{(i)})^{y^{(i)}} (1-h_\theta (x^{(i)})^{(1-y^{(i)})}
对数极大似然函数为,
l ( θ ) = l o g L ( θ ) = i = 1 m y ( i ) l o g h θ ( x ( i ) ) + ( 1 y ( i ) ) l o g ( 1 h θ ( x ( i ) ) ) l(\theta) = log L(\theta) = \sum^{m}_{i=1} y^{(i)}log h_\theta (x^{(i)}) + (1-y^{(i)})log (1-h_\theta (x^{(i)}))
损失函数为,
J ( θ ) = 1 m l ( θ ) = 1 m i = 1 m y ( i ) h θ ( x ( i ) ) + ( 1 y ( i ) ) ( 1 h θ ( x ( i ) ) ) J(\theta) = -\frac{1}{m}l(\theta) = -\frac{1}{m}\sum^{m}_{i=1} y^{(i)}h_\theta (x^{(i)}) + (1-y^{(i)})(1-h_\theta (x^{(i)}))
损失函数表示了预测值和真实值之间的差异程度,预测值和真实值越接近,则损失函数越小。

我们用梯度下降法求解,
θ : = θ α Δ θ J ( θ ) = θ + α m Δ θ l ( θ ) \theta:=\theta-\alpha\Delta_\theta J(\theta) = \theta + \frac{\alpha}{m}\Delta_\theta l(\theta)
由于 g θ ( z ) = g θ ( z ) ( 1 g θ ( z ) ) g'_\theta(z) = g_\theta (z)(1-g_\theta(z))

小练习:试证明,当$g(z) = \frac{1}{1+e^{-z}}$ $g'(z) = g(z)(1-g(z))$ 证明: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200113204137597.png) 因此可以求得, ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200113204216701.png)

可以看到,形式和线性回归很相似,这是因为他们都属于广义线性模型(GLM,可参考吴恩达-CS229 第四课)。

4、正则化与模型评估指标

正则化

我们可以在损失函数后面,加上正则化函数,即 θ \theta 的惩罚项,来抑制过拟合问题。
我们考虑L2正则,在损失函数后面加一项和 θ \theta 相关的函数,令
J ( θ ) = 1 m i = 1 m y ( i ) h θ ( x ( i ) ) + ( 1 y ( i ) ) ( 1 h θ ( x ( i ) ) ) + λ 2 m i = 1 m θ i 2 J(\theta) =\frac{1}{m}\sum^{m}_{i=1} y^{(i)}h_\theta (x^{(i)}) + (1-y^{(i)})(1-h_\theta (x^{(i)})) + \frac{\lambda}{2m}\sum^m_{i=1}\theta_i^2
Δ θ i l ( θ ) = 1 m i = 1 m ( y ( i ) h θ ( x ( i ) ) ) x ( i ) + λ m θ i \Delta_{\theta_i} l(\theta) = \frac{1}{m}\sum^m_{i=1}(y^{(i)} - h_\theta (x^{(i)}))x^{(i)} + \frac{\lambda}{m}\theta_i
用正则化后的函数替代原先的函数进行梯度法迭代获得最优化条件下的参数

逻辑回归的评价指标

由于逻辑回归模型属于分类模型,不能用线性回归的评价指标。 二元分类的评价指标基本都适用于逻辑回归。 观察以下混淆矩阵,
在这里插入图片描述
我们可以用查准率和查全率来评价预测结果:

  • 查准率 P = T P T P + F P P =\frac{TP}{TP+FP}
  • 查全率 R = T P T P + F N R =\frac{TP}{TP+FN}

我们可以用P-R曲线表示查准率和查全率之间的关系:

查准率和查全率经常相互矛盾,一般查准率高时查全率低,查全率高时查准率低。我们经常针对具体的应用场景决定更看重哪一个指标。

小练习:试举例说明,在什么场景下,更看重查准率;在什么情况下,更看重查全率,并说明原因。

在这里插入图片描述
P-R曲线越靠外,则预测效果越好,如果两条P-R曲线相交,则难说孰优孰率,可以计算曲线包住的面积。
我们可以用 F β F_\beta 表达对查准率/查全率的不同偏好,定义为:
F β = ( 1 + β 2 ) P R ( β 2 P ) + R F_\beta = \frac{(1+\beta^2) \cdot P \cdot R}{(\beta^2 \cdot P)+R}
β \beta 大于1时,查全率有更大影响; β \beta 小于1时,查准率有更大影响; β \beta 等于0时,即标准的F1度量。
但是,当正负样本分布发生变化时,P-R曲线会受到较大影响。试想负样本的数量扩大10倍,FP和TN都会成倍增加,会影响到查准率和查全率。这时候我们可以用ROC曲线来评价模型。

定义:

  • T P R = T P T P + F N TPR = \frac{TP}{TP+FN}
  • F P R = F P T N + F P FPR = \frac{FP}{TN+FP}
    绘图的过程可以将样本的预测概率排序,将分类阈值从最小向最大移动,则每个阈值会得到一组TPR和FPR的位置点,将所有的点连结,则绘制成ROC曲线,同时,计算曲线下的面积,即AUC值。AUC值越大,则说明预测的效果越好。

5、逻辑回归的优缺点

  • 优点:从上面介绍已经可以额看到,逻辑回归的思想简洁,可以很好的解决二问题。
  • 缺点:观察下图
    在这里插入图片描述
    因为预测结果呈Z字型(或反Z字型),因此当数据集中在中间区域时,对概率的变化会很敏感,可能使得预测结果缺乏区分度。
    第二,由于逻辑回归依然是线性划分,对于非线性的数据集适应性弱。
    第三,当特征空间很大,性能欠佳。
    第四,只能处理两分类问题。

6、样本不均衡问题

每200封邮件里有1封垃圾邮件,当碰到一封新邮件时,我们只需将其预测为正常邮件,可以达到99.5%的准确率。但是这样的学习器毫无价值,因为他无法判断出任意一封垃圾邮件。

我们预测时,实际上是用预测出的概率值与一个阈值进行比较,例如当y > 0.5时,判断为正例。 y 1 y \frac{y}{1-y} 表示了正例可能性和反例可能性的比值。阈值为0.5时,即 y 1 y > 1 \frac{y}{1-y}>1 时,预测为正例。

如果令 m + m_+ 为样本正例数, m m_- 为样本负例数,随机的观测几率为 m + m \frac{m_+}{m_-} 。只要分类器的预测概率高于观测几率,应判定为正例,即
y 1 y > m + m \frac{y}{1-y}>\frac{m_+}{m_-},预测为正例
这时候,需要对预测值进行调整,使得 y 1 y = y 1 y m + m \frac {y'}{1-y'}=\frac{y}{1-y} \cdot\frac{m_+}{m_-} ,那么,0.5的阈值依然是合理的分类决策。
这就是类别不平衡的一个基本策略——“再缩放”。
“再缩放”的三类做法:

  • 欠采样:去除一些反例使得正反例数目接近。
  • 过采样:增加一些正例使得正反例数目接近。
  • 阈值移动:基于原始集学习,当在预测是,将决策阈值改为符合样本正负样本比例的值。

可以想到,过采样因为增加了数据,时间开销大于欠采样法。但欠采样法由于随机丢弃反例,可能丢失一些重要信息。这时候可以将反例划分成若干个集合分别学习,从全局来看并非丢失重要信息。

7、sklearn参数

Logistics Regression参数名称

函数调用形式

LogisticRegression(penalty=‘l2’,dual=False,tol=1e-4,C=1.0,fit_intercept=True,intercept_scaling=1,class_weight=None,random_state=None,solver=‘liblinear’,max_iter=100,multi_class=‘ovr’,verbose=0,warm_start=False, n_jobs=1)

penalty

字符串型,’l1’ or ‘l2’,默认:’l2’;正则化类型。

dual

布尔型,默认:False。当样本数>特征数时,令dual=False;用于liblinear解决器中L2正则化。

tol

浮点型,默认:1e-4;迭代终止判断的误差范围。

C

浮点型,默认:1.0;其值等于正则化强度的倒数,为正的浮点数。数值越小表示正则化越强。

fit_intercept

布尔型,默认:True;指定是否应该向决策函数添加常量(即偏差或截距)。

intercept_scaling

浮点型,默认为1;仅仅当solver是”liblinear”时有用。

class_weight

默认为None;与“{class_label: weight}”形式中的类相关联的权重。如果不给,则所有的类的权重都应该是1。

random_state

整型,默认None;当“solver”==“sag”或“liblinear”时使用。在变换数据时使用的伪随机数生成器的种子。如果是整数, random_state为随机数生成器使用的种子;若为RandomState实例,则random_state为随机数生成器;如果没有,随机数生成器就是’ np.random '使用的RandomState实例。

solver

{‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’, ‘saga’},默认: ‘liblinear’;用于优化问题的算法。

对于小数据集来说,“liblinear”是个不错的选择,而“sag”和’saga’对于大型数据集会更快。

对于多类问题,只有’newton-cg’, ‘sag’, 'saga’和’lbfgs’可以处理多项损失;“liblinear”仅限于“one-versus-rest”分类。

max_iter

最大迭代次数,整型,默认是100;

multi_class

字符串型,{ovr’, ‘multinomial’},默认:‘ovr’;如果选择的选项是“ovr”,那么一个二进制问题适合于每个标签,否则损失最小化就是整个概率分布的多项式损失。对liblinear solver无效。

verbose

整型,默认是0;对于liblinear和lbfgs solver,verbose可以设为任意正数。

warm_start

布尔型,默认为False;当设置为True时,重用前一个调用的解决方案以适合初始化。否则,只擦除前一个解决方案。对liblinear解码器无效。

n_jobs

整型,默认是1;如果multi_class=‘ovr’ ,则为在类上并行时使用的CPU核数。无论是否指定了multi_class,当将’ solver ’ '设置为’liblinear’时,将忽略此参数。如果给定值为-1,则使用所有核。

原文链接:https://blog.csdn.net/qq_38683692/article/details/82533460

8、代码实现

1、先尝试调用sklearn的线性回归模型训练数据,尝试以下代码,画图查看分类的结果

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

%matplotlib inline

df_X = pd.read_csv('./logistic_x.txt', sep='\ +',header=None, engine='python') #读取X值
ys = pd.read_csv('./logistic_y.txt', sep='\ +',header=None, engine='python') #读取y值
ys = ys.astype(int)
df_X['label'] = ys[0].values #将X按照y值的结果一一打标签
ax = plt.axes()
#在二维图中描绘X点所处位置,直观查看数据点的分布情况
df_X.query('label == 0').plot.scatter(x=0, y=1, ax=ax, color='blue')
df_X.query('label == 1').plot.scatter(x=0, y=1, ax=ax, color='red')
#提取用于学习的数据
Xs = df_X[[0, 1]].values
Xs = np.hstack([np.ones((Xs.shape[0], 1)), Xs]) 
ys = df_X['label'].values


from __future__ import print_function
import numpy as np
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(fit_intercept=False) #因为前面已经将截距项的值合并到变量中,此处参数设置不需要截距项
lr.fit(Xs, ys) #拟合
score = lr.score(Xs, ys) #结果评价
print("Coefficient: %s" % lr.coef_)
print("Score: %s" % score)

ax = plt.axes()

df_X.query('label == 0').plot.scatter(x=0, y=1, ax=ax, color='blue')
df_X.query('label == 1').plot.scatter(x=0, y=1, ax=ax, color='red')

_xs = np.array([np.min(Xs[:,1]), np.max(Xs[:,1])])

#将数据以二维图形式描点,并用学习得出的参数结果作为阈值,划分数据区域
_ys = (lr.coef_[0][0] + lr.coef_[0][1] * _xs) / (- lr.coef_[0][2])
plt.plot(_xs, _ys, lw=1)

2 用梯度下降法将相同的数据分类,画图和sklearn的结果相比较

class LGR_GD():
    def __init__(self):
        self.w = None 
        self.n_iters = None
    def fit(self,X,y,alpha=0.03,loss = 1e-10): # 设定步长为0.002,判断是否收敛的条件为1e-10
        y = y.reshape(-1,1) #重塑y值的维度以便矩阵运算
        [m,d] = np.shape(X) #自变量的维度
        self.w = np.zeros((1,d)) #将参数的初始值定为0
        tol = 1e5
        self.n_iters = 0
        #============================= show me your code =======================
        while tol > loss: #设置收敛条件
            h_x = 1/(1+np.exp(-X.dot(self.w.T)))
            self.w = self.w + alpha*np.mean(X*(y-h_x),axis=0)
            tol = np.abs(np.sum(h_x))
            self.n_iters += 1 #更新迭代次数
         #============================= show me your code =======================
    def predict(self, X):
        # 用已经拟合的参数值预测新自变量
        y_pred = X.dot(self.w)
        return y_pred  

if __name__ == "__main__":
    lr_gd = LGR_GD()
    lr_gd.fit(Xs,ys)

    ax = plt.axes()

    df_X.query('label == 0').plot.scatter(x=0, y=1, ax=ax, color='blue')
    df_X.query('label == 1').plot.scatter(x=0, y=1, ax=ax, color='red')

    _xs = np.array([np.min(Xs[:,1]), np.max(Xs[:,1])])
    _ys = (lr_gd.w[0][0] + lr_gd.w[0][1] * _xs) / (- lr_gd.w[0][2])
    plt.plot(_xs, _ys, lw=1)

3 用牛顿法实现结果,画图和sklearn的结果相比较,并比较牛顿法和梯度下降法迭代收敛的次数

class LGR_NT():
    def __init__(self):
        self.w = None
        self.n_iters = None
    def fit(self,X,y,loss = 1e-10): # 判断是否收敛的条件为1e-10
        y = y.reshape(-1,1) #重塑y值的维度以便矩阵运算
        [m,d] = np.shape(X) #自变量的维度
        self.w = np.zeros((1,d)) #将参数的初始值定为0
        tol = 1e5
        n_iters =0
        Hessian = np.zeros((d,d))
        #============================= show me your code =======================
        while tol > loss:
            tol > loss:
            zs = X.dot(self.w.T)
            h_x = 1 / (1 + np.exp(-zs))
            grad = np.mean(X*(y - h_x),axis=0)
            for i in range(d):
                for j in range(d):
                    if j>=i:
                        Hessian[i][j] = np.mean(h_x*(h_x-1)*X[:,i]*X[:,j]) #更新海森矩阵中的值
                    else:
                        Hessian[i][j] = Hessian[j][i] #按海森矩阵的性质,对称点可直接得出结果
            theta = self.w - np.linalg.inv(Hessian).dot(grad)
            tol = np.sum(np.abs(theta - self.w))
            self.w = theta
            n_iters += 1
        #============================= show me your code =======================
        self.w = theta
        self.n_iters = n_iters
        
    def predict(self, X):
        # 用已经拟合的参数值预测新自变量
        y_pred = X.dot(self.w)
        return y_pred  

if __name__ == "__main__":
    lgr_nt = LGR_NT()
    lgr_nt.fit(Xs,ys)

比较梯度下降法和牛顿法收敛速度

print("梯度下降法结果参数:%s;梯度下降法迭代次数:%s" %(lgr_gd.w,lgr_gd.n_iters))
print("牛顿法结果参数:%s;牛顿法迭代次数:%s" %(lgr_nt.w,lgr_nt.n_iters))

可以,看到,牛顿法的收敛速度比梯度下降法快很多。

参考:

吴恩达 CS229课程
周志华 《机器学习》
https://blog.csdn.net/dpengwang/article/details/100159369
https://blog.csdn.net/u014106644/article/details/83660226
https://blog.csdn.net/abcjennifer/article/details/7716281

发布了11 篇原创文章 · 获赞 0 · 访问量 88

猜你喜欢

转载自blog.csdn.net/weixin_41898948/article/details/103963863