从0实现逻辑回归

1. 什么是逻辑回归?

逻辑回归是一种 分类算法,用于预测样本属于某一类别的概率。

其核心思想是用线性模型输出经过Sigmoid函数映射到(0,1)区间,表示概率。

常见应用:二分类问题,如垃圾邮件识别、肿瘤良恶性判断等。

2. 逻辑回归的数学原理

2.1 Sigmoid函数

逻辑回归的输出为:

y ^ = σ ( z ) = 1 1 + e − z , z = w ⊤ x + b \hat{y} = \sigma(z) = \frac{1}{1 + e^{-z}}, \quad z = \mathbf{w}^\top \mathbf{x} + b y^=σ(z)=1+ez1,z=wx+b

其中 σ ( ⋅ ) \sigma(\cdot) σ() 为Sigmoid函数, w \mathbf{w} w为权重, b b b为偏置。

2.2 交叉熵损失函数

损失函数采用交叉熵

J ( w , b ) = − 1 n ∑ i = 1 n [ y i log ⁡ y ^ i + ( 1 − y i ) log ⁡ ( 1 − y ^ i ) ] J(\mathbf{w}, b) = -\frac{1}{n} \sum_{i=1}^n \left[ y_i \log \hat{y}_i + (1-y_i)\log(1-\hat{y}_i) \right] J(w,b)=n1i=1n[yilogy^i+(1yi)log(1y^i)]

2.3 梯度下降法

梯度下降法是一种迭代优化算法,旨在通过不断调整模型参数(权重 w \mathbf{w} w 和偏置 b b b)来最小化损失函数 J ( w , b ) J(\mathbf{w}, b) J(w,b)。其核心思想是沿着损失函数梯度(导数)的反方向更新参数,因为梯度指向函数值增长最快的方向,所以其反方向是函数值下降最快的方向。

1. 初始化参数:随机初始化权重向量 w \mathbf{w} w 和偏置 b b b,通常初始化为接近零的小值。

2. 迭代更新:重复以下步骤,直到满足停止条件:

计算梯度:计算损失函数对权重和偏置的偏导数:

∂ J ∂ w = 1 n ∑ i = 1 n ( y ^ i − y i ) x i \frac{\partial J}{\partial \mathbf{w}} = \frac{1}{n} \sum_{i=1}^n (\hat{y}_i - y_i) \mathbf{x}_i wJ=n1i=1n(y^iyi)xi

∂ J ∂ b = 1 n ∑ i = 1 n ( y ^ i − y i ) \frac{\partial J}{\partial b} = \frac{1}{n} \sum_{i=1}^n (\hat{y}_i - y_i) bJ=n1i=1n(y^iyi)

其中, y ^ i = σ ( w ⊤ x i + b ) \hat{y}_i = \sigma(\mathbf{w}^\top \mathbf{x}_i + b) y^i=σ(wxi+b) 是模型预测概率, y i y_i yi 是真实标签, x i \mathbf{x}_i xi 是特征向量。

更新参数:根据梯度更新权重和偏置:

w ← w − α ∂ J ∂ w \mathbf{w} \leftarrow \mathbf{w} - \alpha \frac{\partial J}{\partial \mathbf{w}} wwαwJ

b ← b − α ∂ J ∂ b b \leftarrow b - \alpha \frac{\partial J}{\partial b} bbαbJ

其中, α \alpha α 是学习率,决定参数更新的步长:

  • 学习率过小:收敛速度慢
  • 学习率过大:可能导致震荡或发散

3. 停止条件:当满足预设条件时,迭代结束,得到最终的模型参数。

3. 从0用 Python 代码训练一个逻辑回归模型

完整代码仓库

3.1 导入必要库并生成模拟数据

使用sklearn.datasets.make_classification生成二分类数据,并用plotly可视化。

import numpy as np  # 导入numpy库,用于数值计算和数组操作
from sklearn.datasets import make_classification  # 导入数据生成器,用于生成可用于分类的数据集
import plotly.graph_objects as go  # 导入plotly用于交互式可视化

# 生成模拟数据
X, y = make_classification(
    n_samples=200,        # 样本数量为200
    n_features=2,         # 每个样本有2个特征
    n_redundant=0,        # 冗余特征数为0
    n_clusters_per_class=1,  # 每个类别只有1个簇
    random_state=42       # 随机种子,保证结果可复现
)
y = y.reshape(-1, 1)  # 将y变为列向量,形状为(200,1)

# 可视化数据分布
fig = go.Figure()
fig.add_trace(go.Scatter(
    x=X[y[:,0]==0,0],  # 类别0的第一个特征
    y=X[y[:,0]==0,1],  # 类别0的第二个特征
    mode='markers',    # 散点图
    name='类别0'
))
fig.add_trace(go.Scatter(
    x=X[y[:,0]==1,0],  # 类别1的第一个特征
    y=X[y[:,0]==1,1],  # 类别1的第二个特征
    mode='markers',
    name='类别1'
))
fig.update_layout(
    title='模拟二分类数据',  # 图标题
    xaxis_title='x1',      # x轴标签
    yaxis_title='x2',      # y轴标签
    title_x=0.5            # 标题居中
)
fig.show()

在这里插入图片描述

3.2 梯度下降法实现

实现逻辑回归的训练过程,输出损失曲线。

# Sigmoid函数,用于将输入z映射到(0,1)区间,作为概率输出
def sigmoid(z):
    return 1 / (1 + np.exp(-z))  # z可以是标量、向量或矩阵
    
# 逻辑回归梯度下降实现
def logistic_regression(X, y, lr=0.1, n_iters=200):
    m, n = X.shape  # m为样本数,n为特征数
    X_b = np.c_[np.ones((m, 1)), X]  # 在X左侧加一列全1,作为偏置项,形状(m, n+1)
    w = np.zeros((n+1, 1))  # 初始化权重参数w,包含偏置项,形状(n+1, 1)
    losses = []  # 用于记录每次迭代的损失
    for i in range(n_iters):  # 迭代n_iters次
        z = X_b @ w  # 线性部分,形状(m,1)
        y_pred = sigmoid(z)  # 预测概率,形状(m,1)
        # 损失函数:对数损失(交叉熵),加1e-8防止log(0)
        loss = -np.mean(y * np.log(y_pred + 1e-8) + (1-y) * np.log(1-y_pred + 1e-8))
        grad = X_b.T @ (y_pred - y) / m  # 损失对参数的梯度,形状(n+1,1)
        w -= lr * grad  # 按学习率lr更新参数
        losses.append(loss)  # 记录损失
    return w, losses  # 返回训练好的参数和损失曲线
w, losses = logistic_regression(X, y, lr=0.2, n_iters=200)
# lr=0.2:学习率,控制每次参数更新步长
# n_iters=200:迭代次数

print(f'训练结束,最终损失:{losses[-1]:.4f}')  # 输出最后一次迭代的损失

fig_loss = go.Figure()
fig_loss.add_trace(go.Scatter(
    y=losses,         # y轴为损失值
    mode='lines',     # 折线图
    name='训练损失'
))
fig_loss.update_layout(
    title='逻辑回归损失曲线',  # 图标题
    xaxis_title='迭代次数',    # x轴标签
    yaxis_title='损失',        # y轴标签
    title_x=0.5
)
fig_loss.show()

在这里插入图片描述

3.3 可视化决策边界

在这里插入图片描述

可视化代码如下:

# 可视化决策边界
x_min, x_max = X[:,0].min()-1, X[:,0].max()+1  # 第一个特征的取值范围
y_min, y_max = X[:,1].min()-1, X[:,1].max()+1  # 第二个特征的取值范围
xx, yy = np.meshgrid(
    np.linspace(x_min, x_max, 200),  # 生成200个x坐标点
    np.linspace(y_min, y_max, 200)   # 生成200个y坐标点
)
grid = np.c_[np.ones(xx.ravel().shape), xx.ravel(), yy.ravel()]  # 拼接偏置项和特征,形状(40000,3)
probs = sigmoid(grid @ w).reshape(xx.shape)  # 计算每个网格点属于类别1的概率,重塑为(200,200)

# 可视化模型参数和决策边界
fig = go.Figure()

# 添加决策边界(概率等于0.5的曲线)
fig.add_trace(go.Contour(
    z=probs,  # 概率值
    x=np.linspace(x_min, x_max, 200),  # x坐标
    y=np.linspace(y_min, y_max, 200),  # y坐标
    contours_coloring='lines',         # 只显示等高线
    contours=dict(
        start=0.5,                     # 起始等高线值
        end=0.5,                       # 结束等高线值
        size=0                         # 间隔(0表示只显示0.5的等高线)
    ),
    line_width=2,
    showscale=False,                   # 不显示色条
    name='决策边界'
))

# 添加原始数据点
fig.add_trace(go.Scatter(
    x=X[y[:,0]==0,0], y=X[y[:,0]==0,1],
    mode='markers', name='类别0'
))
fig.add_trace(go.Scatter(
    x=X[y[:,0]==1,0], y=X[y[:,0]==1,1],
    mode='markers', name='类别1'
))

fig.update_layout(
    title='逻辑回归参数和决策边界',
    xaxis_title='x1',
    yaxis_title='x2',
    title_x=0.5,
    width=700,
    height=600
)

fig.show()

# 打印模型参数
print(f'截距(w0): {w[0,0]:.4f}')
print(f'特征1权重(w1): {w[1,0]:.4f}')
print(f'特征2权重(w2): {w[2,0]:.4f}')
print(f'决策边界方程: {w[0,0]:.4f} + {w[1,0]:.4f}*x1 + {w[2,0]:.4f}*x2 = 0')

我的实验结果:

截距( w 0 w_0 w0): 0.0490
特征1权重( w 1 w_1 w1): 1.7014
特征2权重( w 2 w_2 w2): -0.2956
决策边界方程: 0.0490 + 1.7014 ∗ x 1 *x_1 x1 + -0.2956* x 2 x_2 x2 = 0

3.4 计算准确率

用sklearn.metrics.accuracy_score评估模型性能。

from sklearn.metrics import accuracy_score  # 导入准确率评估函数
X_b = np.c_[np.ones((X.shape[0], 1)), X]   # 增加偏置项,形状(m, n+1)
y_pred = sigmoid(X_b @ w) >= 0.5           # 预测概率大于等于0.5为正类,否则为负类
acc = accuracy_score(y, y_pred)            # 计算预测准确率
print(f'准确率:{acc:.2%}')            # 输出准确率,百分比格式

输出结果:准确率:84.00%

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值