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_norm6.3 与其他模型的结合
逻辑回归可以作为更复杂模型的组成部分,例如:
- 作为神经网络的第一层或最后一层
- 集成到图神经网络中处理节点分类任务
- 与注意力机制结合处理序列数据