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(θ)=−m1i=1∑m[yilny^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(θ)=m1XT(σ(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+θ1x1+θ2x2=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−θ1x1
下面我们以鸢尾花数据集的前两个类别为例绘制线性决策边界直线:
# 定义求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