PyTorch逻辑回归实现与交叉熵损失函数详解
1. 逻辑回归与交叉熵损失函数基础
逻辑回归是机器学习中最基础的分类算法之一,尽管名字中带有"回归",但它实际上解决的是二分类问题。在PyTorch中实现逻辑回归训练,核心在于正确理解交叉熵损失函数的数学原理及其实现方式。
逻辑回归模型的输出经过sigmoid函数转换后,表示样本属于正类的概率。数学表达式为: P(y=1|x) = σ(w^T x + b) = 1/(1+e^(-(w^T x + b)))
交叉熵损失函数衡量的是模型预测概率分布与真实分布的差异。对于二分类问题,其定义为: L = -[y log(p) + (1-y)log(1-p)]
这个损失函数有几个重要特性:
- 当预测概率p接近真实标签y时,损失趋近于0
- 当预测与真实标签相反时,损失会趋近于无穷大
- 对错误预测的惩罚随着错误程度的增加而呈对数增长
注意:在实际实现中,我们通常使用PyTorch内置的BCELoss(Binary Cross Entropy Loss)或BCEWithLogitsLoss(结合了sigmoid和BCE的版本),后者在数值稳定性上表现更好。
2. PyTorch实现逻辑回归的完整流程
2.1 数据准备与预处理
首先需要准备适合逻辑回归处理的数据。逻辑回归假设数据是线性可分的(在特征空间中可以找到一个超平面将两类分开),因此对于非线性问题需要考虑特征工程。
import torch
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# 生成模拟数据
X, y = make_classification(n_samples=1000, n_features=10, n_classes=2, random_state=42)
# 转换为PyTorch张量并划分训练集/测试集
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1) # 需要reshape为(n_samples, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
2.2 模型定义
在PyTorch中定义逻辑回归模型非常简单,因为它本质上就是一个线性层加上sigmoid激活函数。
import torch.nn as nn
class LogisticRegression(nn.Module):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__()
self.linear = nn.Linear(input_dim, 1)
def forward(self, x):
return torch.sigmoid(self.linear(x))
2.3 训练循环实现
完整的训练循环包括前向传播、损失计算、反向传播和参数更新四个主要步骤。
# 初始化模型、损失函数和优化器
model = LogisticRegression(X_train.shape[1])
criterion = nn.BCELoss() # 二分类交叉熵损失
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# 训练参数
n_epochs = 1000
for epoch in range(n_epochs):
# 前向传播
outputs = model(X_train)
loss = criterion(outputs, y_train)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每100轮打印一次损失
if (epoch+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{n_epochs}], Loss: {loss.item():.4f}')
3. 关键实现细节与优化技巧
3.1 使用BCEWithLogitsLoss的优势
在实践中,我们更推荐使用BCEWithLogitsLoss而不是手动应用sigmoid+BCELoss的组合。主要原因包括:
- 数值稳定性更好:直接在logits空间计算损失,避免了sigmoid函数在极端值时的数值不稳定问题
- 计算效率更高:合并了两个操作,减少了中间变量的存储
- 内置了防止数值溢出的保护机制
修改后的模型和训练代码如下:
class LogisticRegression(nn.Module):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__()
self.linear = nn.Linear(input_dim, 1)
def forward(self, x):
return self.linear(x) # 不再应用sigmoid
# 使用BCEWithLogitsLoss
criterion = nn.BCEWithLogitsLoss()
3.2 学习率选择与优化器配置
逻辑回归对学习率比较敏感,过大容易震荡,过小收敛缓慢。一些实用建议:
- 初始学习率可以尝试0.1、0.01等常见值
- 使用学习率调度器动态调整学习率
- 考虑使用带动量的优化器如Adam
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=100, gamma=0.1)
3.3 特征标准化的重要性
逻辑回归的性能很大程度上依赖于特征尺度。对特征进行标准化通常能显著提高模型性能和训练稳定性。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
4. 模型评估与性能分析
4.1 评估指标实现
对于二分类问题,常用的评估指标包括准确率、精确率、召回率、F1分数和AUC-ROC等。
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
def evaluate(model, X, y):
with torch.no_grad():
outputs = model(X)
preds = (outputs > 0.5).float()
acc = accuracy_score(y, preds)
prec = precision_score(y, preds)
rec = recall_score(y, preds)
f1 = f1_score(y, preds)
auc = roc_auc_score(y, outputs.numpy())
print(f'Accuracy: {acc:.4f}, Precision: {prec:.4f}')
print(f'Recall: {rec:.4f}, F1: {f1:.4f}, AUC: {auc:.4f}')
print("Training set performance:")
evaluate(model, X_train, y_train)
print("\nTest set performance:")
evaluate(model, X_test, y_test)
4.2 决策边界可视化
对于二维特征的情况,我们可以可视化模型的决策边界,直观理解模型的分类行为。
import matplotlib.pyplot as plt
import numpy as np
# 生成二维数据示例
X_2d, y_2d = make_classification(n_samples=1000, n_features=2, n_classes=2, random_state=42)
X_2d = torch.tensor(X_2d, dtype=torch.float32)
y_2d = torch.tensor(y_2d, dtype=torch.float32).reshape(-1, 1)
# 训练二维模型
model_2d = LogisticRegression(2)
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.SGD(model_2d.parameters(), lr=0.1)
for epoch in range(1000):
outputs = model_2d(X_2d)
loss = criterion(outputs, y_2d)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 可视化
def plot_decision_boundary(model, X, y):
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.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
Z = model(torch.tensor(np.c_[xx.ravel(), yy.ravel()], dtype=torch.float32))
Z = (Z > 0.5).float().reshape(xx.shape)
plt.contourf(xx, yy, Z, alpha=0.4)
plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k')
plt.title("Decision Boundary Visualization")
plt.show()
plot_decision_boundary(model_2d, X_2d.numpy(), y_2d.numpy())
5. 常见问题与解决方案
5.1 损失不下降的可能原因
- 学习率设置不当:尝试调整学习率大小
- 特征尺度差异大:进行特征标准化
- 模型过于简单:检查是否所有特征都已被正确使用
- 数据标签不平衡:考虑类别权重或重采样
5.2 处理类别不平衡问题
对于不平衡数据集,可以通过以下方式调整:
- 在损失函数中设置类别权重
pos_weight = torch.tensor([num_negative/num_positive])
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
- 使用过采样/欠采样技术
- 采用不同的分类阈值(而非默认的0.5)
5.3 数值稳定性问题
当使用普通BCELoss时,可能会遇到数值不稳定的情况。解决方案包括:
- 使用BCEWithLogitsLoss代替
- 在手动实现时对sigmoid输出进行裁剪
outputs = torch.clamp(outputs, 1e-7, 1-1e-7) # 避免log(0)
6. 高级扩展与变体
6.1 多分类逻辑回归
虽然本文主要讨论二分类问题,但逻辑回归可以扩展到多分类场景(称为多项逻辑回归或softmax回归)。PyTorch实现时主要变化:
- 输出维度变为类别数
- 使用CrossEntropyLoss(已经包含softmax)
- 不再需要sigmoid激活
class MultinomialLogisticRegression(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.linear = nn.Linear(input_dim, output_dim)
def forward(self, x):
return self.linear(x) # CrossEntropyLoss会自动应用softmax
# 使用时
criterion = nn.CrossEntropyLoss()
6.2 正则化技术应用
为了防止过拟合,可以在逻辑回归中加入L1或L2正则化:
- 通过优化器的weight_decay参数实现L2正则化
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, weight_decay=1e-4)
- 手动实现L1正则化
l1_lambda = 0.001
l1_norm = sum(p.abs().sum() for p in model.parameters())
loss = criterion(outputs, y_train) + l1_lambda * l1_norm
6.3 与其他模型的结合
逻辑回归可以作为更复杂模型的组成部分,例如:
- 作为神经网络的第一层或最后一层
- 集成到图神经网络中处理节点分类任务
- 与注意力机制结合处理序列数据
更多推荐


所有评论(0)