文章目录
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+e−z1,z=w⊤x+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=1∑n[yilogy^i+(1−yi)log(1−y^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 ∂w∂J=n1i=1∑n(y^i−yi)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) ∂b∂J=n1i=1∑n(y^i−yi)
其中, y ^ i = σ ( w ⊤ x i + b ) \hat{y}_i = \sigma(\mathbf{w}^\top \mathbf{x}_i + b) y^i=σ(w⊤xi+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}} w←w−α∂w∂J
b ← b − α ∂ J ∂ b b \leftarrow b - \alpha \frac{\partial J}{\partial b} b←b−α∂b∂J
其中, α \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%