逻辑回归(Logistic回归又名对数几率回归)原理及python代码实现

本文详细介绍了逻辑回归的原理,包括Sigmoid函数、损失函数的推导,以及如何通过多项式回归处理非线性问题。还讨论了多分类问题的解决方法,如OVR和OVO,并展示了如何使用sklearn中的正则化和多分类超参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 公式推导

        为了实现Logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和代人Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分人1类 ,小于0.5即被归人0类 ,所以Logistic回归也可以被看成是一种概率估计。逻辑回归的本质还是线性回归,母体函数是线性回归函数,只不过将结果值代入Sigmoid函数转换为0到1之间的数值用来完成分类。

线性回归方程如下所示:

\hat{y}=\theta ^{T}X_{b}                                                                        (1)

为了将预测结果值转换到0~1之间的值,将\hat{y}代入Sigmoid函数中:

\sigma (\hat{y})= \frac{1}{1+e^{-\hat{y}}}                                                                        (2)

将式(1)代入到式(2)中,得到下式概率公式(3):

\hat{p}= \frac{1}{1+e^{-\theta ^{T}X_{b}}}                                                                       (3)

        构造如下损失函数:

J(\theta ) = \left\{\begin{matrix} -log(\hat{p}) & if & y=1\\ -log(1-\hat{p}) &if& y=0\end{matrix}\right.                                                (4)

        设 \hat{p} 是当 y = 1 时的概率,那么肯定希望概率越大越好,如果偏离的100%就给一定的惩罚,如果概率为0 ,则让其损失函数无限大。

        反之,当 y = 0 时,让其1-\hat{p} 的概率最大化,如果偏离100%就给一定的惩罚,如果概率为1,则让其损失无限大。

        上式分段函数(4)可转换为下式(5):

J(\theta )=-ylog(\hat{p})-(1-y)log(1-\hat{p})                                (5)

        上式便是经典的交叉熵损失函数,又称对数损失函数,此函数可衡量两个分布之间的举例,一般用于分类模型的损失函数。

2. 决策边界

        当概率大于等于0.5时,我们认为y = 1,反之小于0.5时,认为y = 0,即如下式所示:

\hat{y} = \left\{\begin{matrix} 1, &\hat{p}\geqslant 0.5 & \theta ^{T}.X_{b}\geqslant 0\\ 0, &\hat{p}< 0.5& \theta ^{T}.X_{b}< 0\end{matrix}\right.                                        (6)

即决策边界为:

\theta ^{T}.X_{b} = 0                                                                        (7)

        在实际分类问题中,决策边界可能会是一个弯曲的曲线或平面,但以上表达式为线性回归方程,注定最后的决策边界将会是一条直线或一个平面,无法对非线性关系的样本做到精准的分类。

所以为了更好的解决这类非线性的分类问题,我们可以引入多项式回归方程,通过对特征向量增加阶数实现非线性关系的数据集分类,具体实现方法可参考笔者另一篇博文:

多项式回归(非线性回归)的python代码实现_南山十一少的博客-CSDN博客

3. 多分类问题

        逻辑回归本身只能处理二分类问题,但通过巧妙的多次运用逻辑回归,也能完成多分类任务。(sklearn封装的逻辑回归算法可通过设置超参数处理多分类任务,使用方法见本文 5.2)

方法一:OVR法(One vs Rest)

假定N分类问题,首先将其中一个类别和其他类别作为一次二分类,然后从N类别中循环执行N次,分别得到每个样本N个类别的概率,挑出概率最高的类别作为次样本的类别分类。

方法二:OVO法(One vs One)

假定N分类,首先拿出两个类别进行二次分类任务,然后从N类别中循环挑选出两个类别进行分类计算概率,共计算C_{n}^{2}次,得到每个样本N个类别的概率,挑出概率最高的类别作为次样本的类别分类。

4. 自制代码实现

首先编写Sigmoid函数、损失函数、损失函数的偏导,然后根据批量梯度下降的方法不断迭代计算参数\theta,具体编码如下:

注意:

1. 在编写Sigmoid函数时,因为代入的-t = X_b.dot(theta) 比较大,通过指数函数np.exp(-t)后容易导致数据溢出,建议更换为另一个公式代替:return .5 * (1 + np.tanh(.5 * t)),否则会有如下报错:

RuntimeWarning: overflow encountered in exp
  return 1. / (1. + np.exp(-t))

2. 计算损失函数时,由于分母过大,容易导致精度不够,建议增加浮点数的精度,在y_hat后面增加1e-6,否则会有如下报错

RuntimeWarning: divide by zero encountered in log
  return - np.sum(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat)) / len(y)

RuntimeWarning: invalid value encountered in multiply
  return - np.sum(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat)) / len(y)

RuntimeWarning: invalid value encountered in scalar subtract
  if abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon:

# 自编逻辑回归函数
def MyselfLogisticRegression(X_train, y_train, eta=0.01, n_iters=1e4):
    def sigmoid(t):
        # return 1. / (1. + np.exp(-t))
        return .5 * (1 + np.tanh(.5 * t))

    def J(theta, X_b, y):
        y_hat = sigmoid(X_b.dot(theta))
        try:
            return - np.sum(y * np.log(y_hat + 1e-6) + (1 - y) * np.log(1 - y_hat + 1e-6)) / len(y)
        except:
            return float('inf')

    def dJ(theta, X_b, y):
        return X_b.T.dot(sigmoid(X_b.dot(theta)) - y) / len(y)

    def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):

        theta = initial_theta
        cur_iter = 0

        while cur_iter < n_iters:
            gradient = dJ(theta, X_b, y)
            last_theta = theta
            theta = theta - eta * gradient
            if abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon:
                break

            cur_iter += 1

        return theta

    X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
    # initial_theta = np.zeros(X_b.shape[1])
    initial_theta = np.random.randn(X_b.shape[1])
    theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)

    return theta

5 调用sklearn的逻辑回归函数

5.1 模型正则化超参数使用

在sklearn中封装的逻辑函数中,自动封装了正则化,可以通过超参数进行调节,其正则化后的损失函数如下:

C.J(\theta ) + L

如果读者对正则化感兴趣,可以参考笔者另一篇博文:

模型正则化在多项式回归中的运用:Ridge回归(岭回归)、LASSO回归、弹性网络回归的原理及python代码实现_南山十一少的博客-CSDN博客

C:正则化强度权重,大于0的浮点数,不填写默认1.0。C越小,正则化的效力越强,参数会被压缩得越小。

penalty:正则化方式, penalty = l_{1}时,损失函数中的L为系数值绝对值和,penalty = l_{2}时,损失函数的L为系数平方和

solver:优化算法选择参数,参数为liblinear时,使用了坐标轴下降法迭代损失函数。

具体实现代码如下:

from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.model_selection import train_test_split
import numpy as np

if __name__ == "__main__":
    # 构造带随机误差的呈非线性关系的数据集
    np.random.seed(666)
    X = np.random.normal(0, 1, size=(200, 2))
    y = np.array((X[:, 0] ** 2 + X[:, 1]) < 1.5, dtype='int')
    for _ in range(20):
        y[np.random.randint(200)] = 1
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

    # 预处理数据进行非线性化多项式组合
    poly = PolynomialFeatures(degree=20)
    poly.fit(X_train)
    X_train = poly.transform(X_train)
    X_test = poly.transform(X_test)

    # 调用sklearn逻辑回归函数
    poly_log_reg = LogisticRegression(C=0.2,solver='liblinear',penalty="l1")
    poly_log_reg.fit(X_train, y_train)
    train_score = poly_log_reg.score(X_train, y_train)
    test_score = poly_log_reg.score(X_test, y_test)
    print(
        "poly_log_reg.intercept_ = {}, poly_log_reg.coef_ = {}".format(poly_log_reg.intercept_[0], poly_log_reg.coef_))
    print("PolynomialLogisticRegression train_score = {}, test_score = {}".format(train_score, test_score))

    # 调用自编逻辑回归函数对比
    theta = MyselfLogisticRegression(X_train, y_train)
    poly_log_reg.intercept_[0] = theta[0]
    poly_log_reg.coef_[0] = theta[1:]
    print("poly_log_reg.intercept_ = {}, poly_log_reg.coef_ = {}".format(poly_log_reg.intercept_, poly_log_reg.coef_))
    train_score = poly_log_reg.score(X_train, y_train)
    test_score = poly_log_reg.score(X_test, y_test)
    print("MyselfLogisticRegression train_score = {}, test_score = {}".format(train_score, test_score))

5.2 多分类超参数使用

如果要进行多分类任务,同样sklearn也封装了OVR和OVO的方法,只需通过超参数设置便能使用。

multi_class:多分类模式

ovr:,一对剩余。有K类,则训练K个模型,每个模型把第i类当一类,其余当一类。最后选择预测概率最高的一类作为预测类别。
multinomial:多项模式,OVR模式,两两分类,最后选择预测概率最高的一类作为预测类,值得注意的是,如果选择次模式,solver超参数需要选择newton-cg

代码如下:

if __name__ == "__main__":
    # 鸢尾花为例调用sklearn逻辑回归函数进行多分类试验
    iris = datasets.load_iris()
    X = iris.data
    y = iris.target
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
    log_reg = LogisticRegression(multi_class='multinomial',solver='newton-cg')
    log_reg.fit(X_train, y_train)
    score = log_reg.score(X_test, y_test)
    y_predict = log_reg.predict(X_test)
    print("multi_class predict y_predict = {}.".format(y_predict))
    print("multi_class LogisticRegression score = {}".format(score))

6 结论

        本文通过讲解逻辑回归(对数几率回归)从回归模型到分类模型的演变过程以及损失函数的推导过程,然后利用梯度下降编写逻辑回归方法,最后通过调用sklearn封装的逻辑回归方法,分别演示了模型正则化和多分类的超参数使用。

### 对数几率回归逻辑回归)的Python实现思路 对数几率回归Logistic Regression)是一种广泛用于分类任务的线性模型,尤其适用于二分类问题。其核心思想是通过 Sigmoid 函数将线性输出映射到 [0, 1] 区间,表示样本属于某一类的概率。 #### 基本原理 - **Sigmoid 函数**:定义为 $ \sigma(z) = \frac{1}{1 + e^{-z}} $,它将任意实数映射到 (0, 1) 范围内。 - **模型表达式**:给定输入特征 $ X $ 和权重参数 $ w $,预测值为 $ P(y=1|X) = \sigma(w^T X + b) $。 - **损失函数**:通常采用交叉熵损失函数进行优化,目标是最小化真实标签与预测概率之间的差异。 #### Python 实现步骤 以下是一个基于 NumPy 的对数几率回归实现框架: ##### 1. 定义 Sigmoid 函数 ```python import numpy as np def sigmoid(z): return 1 / (1 + np.exp(-z)) ``` ##### 2. 初始化模型参数 ```python def initialize_parameters(dim): w = np.zeros((dim, 1)) b = 0 return w, b ``` ##### 3. 定义损失函数 使用交叉熵损失函数衡量预测值与真实标签之间的误差: ```python def compute_loss(y, y_hat): m = y.shape[0] loss = -np.sum(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat)) / m return loss ``` ##### 4. 梯度下降优化 计算梯度并更新参数: ```python def propagate(w, b, X, y): m = X.shape[0] z = np.dot(X, w) + b y_hat = sigmoid(z) cost = compute_loss(y, y_hat) dw = np.dot(X.T, (y_hat - y)) / m db = np.sum(y_hat - y) / m grads = {"dw": dw, "db": db} return grads, cost ``` ##### 5. 参数更新过程 ```python def optimize(w, b, X, y, num_iterations, learning_rate): costs = [] for i in range(num_iterations): grads, cost = propagate(w, b, X, y) dw = grads["dw"] db = grads["db"] w -= learning_rate * dw b -= learning_rate * db if i % 100 == 0: costs.append(cost) print(f"Cost after iteration {i}: {cost}") parameters = {"w": w, "b": b} return parameters, costs ``` ##### 6. 预测函数 根据学习到的参数进行分类预测: ```python def predict(w, b, X): m = X.shape[0] y_prediction = np.zeros((m, 1)) z = np.dot(X, w) + b y_hat = sigmoid(z) for i in range(y_hat.shape[0]): if y_hat[i, 0] > 0.5: y_prediction[i, 0] = 1 else: y_prediction[i, 0] = 0 return y_prediction ``` ##### 7. 完整训练流程 整合所有组件完成模型训练和预测: ```python def model(X_train, y_train, num_iterations=2000, learning_rate=0.005): w, b = initialize_parameters(X_train.shape[1]) parameters, costs = optimize(w, b, X_train, y_train, num_iterations, learning_rate) w = parameters["w"] b = parameters["b"] y_prediction = predict(w, b, X_train) accuracy = 100 - np.mean(np.abs(y_prediction - y_train)) * 100 print(f"Train Accuracy: {accuracy:.2f}%") return parameters ``` #### 使用示例 假设数据集如下: ```python from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split # 生成模拟数据 X, y = make_blobs(n_samples=1000, centers=2, cluster_std=2, random_state=42) y = y.reshape((y.shape[0], 1)) # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 训练模型 parameters = model(X_train, y_train, num_iterations=2000, learning_rate=0.01) # 测试模型 y_pred = predict(parameters["w"], parameters["b"], X_test) test_accuracy = 100 - np.mean(np.abs(y_pred - y_test)) * 100 print(f"Test Accuracy: {test_accuracy:.2f}%") ``` ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值