Scikit-Learn逻辑回归

Scikit-Learn逻辑回归

    • 1、逻辑回归概述
      • 1.1、逻辑回归
      • 1.2、逻辑回归的优缺点
      • 1.3、逻辑回归与线性回归
      • 2、逻辑回归的原理
        • 2.1、逻辑回归的概念与原理
        • 2.2、逻辑回归的损失函数
        • 2.3、梯度下降法求解逻辑回归的最优解
        • 3、Scikit-Learn逻辑回归
          • 3.1、决策边界
          • 3.2、Scikit-Learn逻辑回归API

            1、逻辑回归概述

            逻辑回归(Logistic Regression)主要解决二分类问题,用来表示某个事件发生的可能性。逻辑回归在机器学习知识结构中的位置如下:

            1.1、逻辑回归

            逻辑回归的思想最早可以追溯到19世纪,由英国统计学家Francis Galton在研究豌豆遗传问题时首次提出。然而,真正将逻辑回归应用于机器学习的是加拿大统计学家Hugh Everett,他在1970年代提出了广义线性模型(GLM),其中包括逻辑回归

            逻辑回归这个算法的名称有一定的误导性。虽然它的名称中有“回归”,但它在机器学习中不是回归算法,而是分类算法。因为采用了与回归类似的思想来解决分类问题,所以它才会被称为逻辑回归

            在逻辑回归中,我们不是直接预测输出值,而是预测输出值属于某一特定类别的概率。例如,一封邮件是垃圾邮件的概率(是与不是),广告被点击的概率(点与不点)等

            逻辑回归通过Sigmoid函数将样本的特征与样本发生的概率联系起来,在拟合样本数据发生概率的时候,其实是在解决一个回归问题,当概率计算出来后,再根据概率进行分类处理。逻辑回归是在解决样本与样本发生的概率之间的回归问题

            逻辑回归的函数表达式(Logistic函数或Sigmoid函数)为

            g ( z ) = 1 1 + e − z \rm g(z)=\frac{1}{1+e^{-z}} g(z)=1+e−z1​

            Sigmoid函数有时也用 σ ( z ) \sigma(z) σ(z)表示,其对应的图像为

            import numpy as np
            import matplotlib.pyplot as plt
            z = np.arange(-5, 5, 0.01)
            y = 1/(1+np.exp(-z))
            plt.plot(z, y)
            plt.show()
            

            逻辑回归基于概率来进行分类。对于给定输入特征X,逻辑回归模型会计算输出标签y=1(正类)的条件概率:

            P ( y = 1 ∣ X ) = 1 1 + e − ( θ T X + b ) P(y=1|X)=\frac{1}{1+e^{-(\theta^T X+b)}} P(y=1∣X)=1+e−(θTX+b)1​

            其中, ω \omega ω是特征权重,b是偏置项

            逻辑回归将线性回归的结果 y y y= θ T X \theta^T X θTX+ b b b带入到Sigmoid函数的自变量,并将其映射到0和1之间,使其可以解释为概率

            逻辑回归与线性回归的关键区别在于,后者训练出来的模型的预测值域为( − ∞ -∞ −∞,+ ∞ ∞ ∞),换句话说,也就是对值域没有限制;而对于表示概率的值而言,其值域都是在(0,1)之间

            更多关于逻辑回归的介绍见文章:传送门

            1.2、逻辑回归的优缺点

            优点:

            • 模型简单,分类计算量小,存储资源低,训练和预测速度快
            • 预测结果是观测样本的概率,易于理解,增加了解释性
            • 对逻辑回归而言,多重共线性并不是问题,它可以结合L2正则化来解决该问题

              缺点:

              • 只能处理二分类问题,且逻辑回归假设数据是线性可分的,对于非线性特征,需要进行转换
              • 容易欠拟合,一般准确度不太高

                1.3、逻辑回归与线性回归

                逻辑回归与线性回归的区别详见文章:传送门

                2、逻辑回归的原理

                2.1、逻辑回归的概念与原理

                逻辑回归的概念与原理推导详见文章:传送门

                2.2、逻辑回归的损失函数

                在逻辑回归(详见:传送门)一文中,我们已经给出并推导了逻辑回归的损失函数

                J ( θ ) = − 1 m ∑ i = 1 m [ y i ln ⁡ y ^ i + ( 1 − y i ) ln ⁡ ( 1 − y ^ i ) ] J(\theta)=-\frac{1}{m}\sum_{i=1}^m[y_i\ln \hat y_i+(1-y_i)\ln(1-\hat y_i)] J(θ)=−m1​i=1∑m​[yi​lny^​i​+(1−yi​)ln(1−y^​i​)]

                其中 y y y为真实标签值, y ^ \hat y y^​= 1 1 + e − θ T X \frac{1}{1+e^{-\theta^T X}} 1+e−θTX1​为预测值。它是多个样本的平均损失。对于单个样本,其损失函数拆分后表示为

                c o s t = { − ln ⁡ ( p )    y = 1 − ln ⁡ ( 1 − p )    y = 0 cost=\begin{cases} -\ln(p) \, \, & y=1 \\ -\ln(1-p) \, \, & y=0 \end{cases} cost={−ln(p)−ln(1−p)​y=1y=0​

                图像如下

                当 p p p趋于0时, − ln ⁡ ( p ) -\ln(p) −ln(p)趋于正无穷,损失惩罚很大;当 p p p趋于1时, − ln ⁡ ( p ) -\ln(p) −ln(p)趋于0,说明没有损失

                当 p p p趋于1时, − ln ⁡ ( 1 − p ) -\ln(1-p) −ln(1−p)趋于正无穷,损失惩罚很大;当 p p p趋于0时, − ln ⁡ ( 1 − p ) -\ln(1-p) −ln(1−p)趋于0,说明没有损失

                现在,我们只需要找到一‍‍组 θ \theta θ值,使得上面‍‍的 J ( θ ) J(\theta) J(θ)最小即可

                2.3、梯度下降法求解逻辑回归的最优解

                2.3.1、莺尾花数据集简介

                莺尾花数据集于1988年公开,是最早用于评估分类方法的数据集之一。数据集包含150个样本,分为3类,其都属于鸢尾的三个亚属(山鸢尾Setosa,变色鸢尾Versicolour,维吉尼亚鸢尾Virginica),每类50个实例,每个实例包含4个属性。其中每个实例都是一个鸢尾植物,每类都引用一种鸢尾植物,一类与另一类线性可分,可通过花萼长度、花萼宽度、花瓣长度、花瓣宽度4个属性预测鸢尾花卉属于三个种类中的哪一类,目标变量即为莺尾花的类别

                左:Virginica,右上:Versicolour,右下:Setosa

                数据集的属性信息(4特征+1标签)如下:

                属性/标签说明
                sepal length花萼长度(cm)
                sepal width花萼宽度(cm)
                petal length花瓣长度(cm)
                petal width花瓣宽度(cm)
                class鸢尾植物的三个亚属类别:Setosa(0)、Versicolor(1)、Virginica(2)

                数据集的信息如下:

                import numpy as np
                import pandas as pd
                from sklearn import datasets
                # 加载数据集
                iris = datasets.load_iris()
                # 查看数据的所有属性和方法
                print(dir(iris))    # ['DESCR', 'data', 'data_module', 'feature_names', 'filename', 'frame', 'target', 'target_names']
                # 特征数据的形状大小
                print(iris.data.shape)      # (150, 4)
                # 特征名称
                print(iris.feature_names)   # ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
                # 分类标签
                print(pd.Series(iris.target).value_counts())    # 依次为50个0、50个1、50个2
                '''
                0    50
                1    50
                2    50
                Name: count, dtype: int64
                '''
                # 分类名称
                print(iris.target_names)    # ['setosa' 'versicolor' 'virginica']
                # 将数据保存到DataFrame
                iris_df = pd.DataFrame(data=iris.data, columns=[e[:-4] for e in iris.feature_names])
                iris_df['class'] = iris.target
                print(iris_df.head().to_string())
                '''
                   sepal length   sepal width   petal length   petal width   class
                0            5.1           3.5            1.4           0.2      0
                1            4.9           3.0            1.4           0.2      0
                2            4.7           3.2            1.3           0.2      0
                3            4.6           3.1            1.5           0.2      0
                4            5.0           3.6            1.4           0.2      0
                '''
                

                查看是否有缺失值:

                # 查看数据整体信息(缺失值检查):无缺失值
                print(iris_df.info())
                

                数据集可视化描述:

                import matplotlib.pyplot as plt
                # 可视化(2D)
                def show(X, y, data):
                    plt.plot(X[y == 0, 0], X[y == 0, 1], 'rs', label=data.target_names[0])
                    plt.plot(X[y == 1, 0], X[y == 1, 1], 'bx', label=data.target_names[1])
                    plt.plot(X[y == 2, 0], X[y == 2, 1], 'go', label=data.target_names[2])
                    plt.xlabel(data.feature_names[0])
                    plt.ylabel(data.feature_names[1])
                    plt.title("鸢尾花数据集(2D)")
                    plt.legend()
                    plt.rcParams['font.sans-serif'] = 'SimHei'
                    plt.show()
                # 前2列特征(平面只能展示2维)花萼长度(cm)、花萼宽度(cm)
                X = iris.data[:, :2]
                # 后2列特征
                # X = iris.data[:, 2:4]
                # 分类(目标)标签
                y = iris.target
                show(X, y, iris)
                

                2.3.2、逻辑回归中的梯度下降法实现

                由于逻辑回归的损失函数无法像线性回归那样求出一个正规方程解,所以我们需要使用梯度下降法(详见:传送门)。前面我们已经求解得到了逻辑回归代价函数的梯度(详见:传送门)

                ∇ J ( θ ) = 1 m X T ( σ ( X θ ) − Y ) \nabla J(\theta)=\frac{1}{m}X^T(\sigma(X\theta)-Y) ∇J(θ)=m1​XT(σ(Xθ)−Y)

                根据上述理论,我们可以编写对应的求解方法,封装梯度下降法求解逻辑回归解的类GradLogisticRegression

                from sklearn.metrics import accuracy_score     # 精确率
                # 封装使用梯度下降法求解逻辑回归的最优解的类(训练数据、曲线拟合)
                class GradLogisticRegression(object):
                    def __init__(self):
                        # theta初始点列向量
                        self._theta = None
                        # 系数
                        self.coef_ = None
                        # 截距
                        self.intercept_ = None
                    # 定义Sigmoid函数
                    def _sigmoid(self, z):
                        return 1 / (1 + np.exp(-z))
                    def fit_grad(self, X_train, y_train, alpha=0.01, n_iters=1e4):
                        # 定义代价函数(损失函数)
                        def cost(X, Y, m, theta):
                            y_hat = self._sigmoid(X.dot(theta))
                            try:
                                return -(1 / m) * np.sum(Y * np.log(y_hat) + (1 - Y) * np.log(1-y_hat))
                            except:
                                return float('inf')
                        # 定义代价函数的梯度函数
                        def grad(X, Y, m, theta):
                            return (1 / m) * X.T.dot(self._sigmoid(X.dot(theta)) - Y)
                        # 梯度下降迭代算法
                        def gradient_descent(X, Y, m, theta, alpha, n_iters, diff=1e-8):
                            i_iter = 0
                            while i_iter < n_iters:
                                gradient = grad(X, Y, m, theta)
                                last_theta = theta
                                theta = theta - alpha * gradient
                                if abs(cost(X, Y, m, theta) - cost(X, Y, m, last_theta)) < diff: break
                                i_iter = i_iter+1
                            print(f"迭代次数: {i_iter}")
                            return theta
                        # 构建X
                        X = np.hstack([np.ones((len(X_train), 1)), X_train])
                        # 初始化theta向量为元素全为0的向量
                        theta = np.zeros(X.shape[1])
                        self._theta = gradient_descent(X, y_train, len(X), theta, alpha, n_iters)
                        self.intercept_ = self._theta[0]
                        self.coef_ = self._theta[1:]
                        return self
                    # 给定待预测数据集X_pred,返回X_pred的概率向量
                    def predict(self, X_pred):
                        # 构建X_p
                        X_p = np.hstack([np.ones((len(X_pred), 1)), X_pred])
                        # 返回0、1之间的浮点数(概率)
                        probability = self._sigmoid(X_p.dot(self._theta))
                        # 将概率转换为0和1,True对应1,False对应0(返回概率结果和分类结果)
                        return np.array(probability >= 0.5, dtype='int')
                    # 准确率评分
                    def score(self, X_test, y_test):
                        y_pred = self.predict(X_test)
                        return accuracy_score(y_test, y_pred)
                

                接下来我们使用Scikit-Learn提供的鸢尾花数据集来验证我们封装的逻辑回归的方法

                from sklearn.model_selection import train_test_split
                # 使用鸢尾花的前两个种类的前两个特征
                X = iris.data
                y = iris.target
                X = X[y < 2, :2]
                y = y[y < 2]
                # 划分训练集和测试集
                X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
                # 梯度下降法逻辑回归分类器
                log_reg = GradLogisticRegression()
                # 训练模型
                log_reg.fit_grad(X_train, y_train)
                print(log_reg.coef_)         # [3.03026986 -5.08061492]
                print(log_reg.intercept_)    # -0.6938379190860661
                # 预测
                y_pred = log_reg.predict(X_test)
                # 预测结果与真实结果比较
                print(y_pred)    # [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
                print(y_test)    # [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
                # 模型评估
                print(log_reg.score(X_test, y_test))    # 1.0
                

                结果如下:

                通过与真实标签值对比,可以看到,我们封装的逻辑回归算法对鸢尾花的分类预测是100%准确的

                3、Scikit-Learn逻辑回归

                3.1、决策边界

                决策边界,顾名思义就是需要分类的数据中,区分不同类别数据的边界。类似于地图中的省界

                Sigmoid函数是逻辑回归的外层函数,Sigmoid函数将线性模型映射到(0,1)之间的概率值

                h θ ( X ) = g ( θ T X ) = 1 1 + e − θ T X h_\theta(X)=\rm g(\theta^T X)=\frac{1}{1+e^{-\theta^T X}} hθ​(X)=g(θTX)=1+e−θTX1​

                当假设函数 h θ ( X ) = P ( y = 1 ) ≥ 0.5 h_\theta(X)=P(y=1)≥0.5 hθ​(X)=P(y=1)≥0.5,即内层函数 z = θ T X ≥ 0 z=\theta^T X≥0 z=θTX≥0,此时我们将其预测成1(正类),反之我们将其预测成0(负类),即

                { y = 1   , P ≥ 0.5   , θ T X ≥ 0 y = 0   , P < 0.5   , θ T X < 0 \left\{\begin{array}{cc} y=1\,, & P≥0.5\,, & \theta^T X≥0\\ y=0\,, & P<0.5\,, & \theta^T X<0 \end{array}\right. {y=1,y=0,​P≥0.5,P<0.5,​θTX≥0θTX<0​

                当 θ T X \theta^T X θTX=0时,理论上 P P P=0.5,分类既可以为1,也可以为0。由此可见, θ T X \theta^T X θTX=0就是逻辑回归中的决策边界,并且是线性决策边界

                假设我们的线性决策边界有两个特征,即

                θ 0 + θ 1 x 1 + θ 2 x 2 = 0 \theta_0+\theta_1x_1+\theta_2x_2=0 θ0​+θ1​x1​+θ2​x2​=0

                其中, θ 0 \theta_0 θ0​为截距, θ 1 \theta_1 θ1​和 θ 2 \theta_2 θ2​是系数。该直线可用于将两个分类区进行分开,即线性决策边界。为方便将这条直线绘制出来,我们对上式进行变换

                x 2 = − θ 0 − θ 1 x 1 θ 2 x_2=\frac{-\theta_0-\theta_1x_1}{\theta_2} x2​=θ2​−θ0​−θ1​x1​​

                下面我们以鸢尾花数据集的前两个类别为例绘‍‍制线性决策边界直线:

                # 定义求X2的函数
                def X2(X1):
                    return (-log_reg.intercept_ - log_reg.coef_[0] * X1) / log_reg.coef_[1]
                # 构建X1
                X1 = np.linspace(4.5, 7, 1000)
                X2 = X2(X1)
                # 使用鸢尾花的前两个种类的前两个特征可视化
                plt.scatter(X[y == 0, 0], X[y == 0, 1], color='red')
                plt.scatter(X[y == 1, 0], X[y == 1, 1], color='blue')
                plt.plot(X1, X2)
                plt.show()
                

                当然,除了线性决策边界直线,决策边界还有不规则决策边界,例如KNN算法的决策边界

                3.2、Scikit-Learn逻辑回归API

                sklearn.linear_model.LogisticRegression