作为一名长期奋战在AI一线的开发者,我经常被问到如何用PyTorch解决实际的二分类问题。今天我们就来聊聊Logistic Regression这个看似简单却异常强大的算法。别看它名字里带着"回归",实际上可是解决分类问题的利器。
在实际项目中,二分类问题无处不在:邮件过滤(垃圾邮件/正常邮件)、金融风控(欺诈交易/正常交易)、医疗诊断(患病/健康)等等。而Logistic Regression因其模型简单、解释性强、计算效率高等特点,往往是我们在处理这类问题时的首选武器。
注意:虽然深度学习领域有更复杂的模型,但在数据量不大或特征维度不高的情况下,Logistic Regression往往能提供出人意料的好效果,而且训练速度极快。
Logistic Regression的核心思想其实很简单:它通过一个Sigmoid函数将线性回归的输出映射到(0,1)区间,这个值可以被解释为属于正类的概率。数学表达式如下:
P(y=1|x) = σ(wᵀx + b) = 1 / (1 + e^{-(wᵀx + b)})
其中σ就是Sigmoid函数,它的形状像一个平滑的"S"曲线,能够将任何实数映射到(0,1)区间。这个特性使得它非常适合用来表示概率。
对于二分类问题,我们通常使用二元交叉熵(BCE)作为损失函数,而不是回归问题中常用的均方误差(MSE)。原因主要有两点:
MSE在分类问题中会导致梯度消失问题,特别是当预测值接近0或1时,梯度会变得非常小,严重影响训练效果。
从概率论的角度看,交叉熵损失实际上对应着最大似然估计,它能够更直接地衡量预测概率分布与真实分布的差异。
交叉熵损失的公式为:
L = -[y·log(p) + (1-y)·log(1-p)]
其中y是真实标签(0或1),p是预测概率。
在实际项目中,数据准备往往是最耗时但也最重要的环节。对于二分类问题,我们需要确保数据满足以下条件:
python复制import torch
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 生成模拟数据
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 转换为PyTorch张量
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
在PyTorch中定义Logistic Regression模型非常简单,因为它本质上就是一个没有隐藏层的神经网络:
python复制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))
这里有几个关键点需要注意:
训练过程遵循标准的PyTorch训练流程,但有几点需要特别注意:
python复制# 初始化模型
model = LogisticRegression(X_train.shape[1])
criterion = nn.BCELoss() # 二元交叉熵损失
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# 训练循环
num_epochs = 100
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train)
loss = criterion(outputs, y_train)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每10个epoch打印一次损失
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
提示:在实际项目中,建议添加验证集评估和早停机制,防止过拟合。
对于二分类问题,常用的评估指标包括:
python复制from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
with torch.no_grad():
y_pred = model(X_test)
y_pred_class = (y_pred > 0.5).float()
print(f'Accuracy: {accuracy_score(y_test, y_pred_class):.4f}')
print(f'Precision: {precision_score(y_test, y_pred_class):.4f}')
print(f'Recall: {recall_score(y_test, y_pred_class):.4f}')
print(f'F1 Score: {f1_score(y_test, y_pred_class):.4f}')
print(f'AUC-ROC: {roc_auc_score(y_test, y_pred):.4f}')
学习率是影响模型性能的关键超参数。太大可能导致震荡甚至发散,太小则收敛缓慢。我通常的做法是:
python复制from torch.optim.lr_scheduler import StepLR
# 在优化器之后添加调度器
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
为了防止过拟合,可以考虑添加L1或L2正则化:
python复制# L2正则化(权重衰减)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, weight_decay=0.01)
# 或者在损失函数中手动添加
l2_lambda = 0.01
l2_norm = sum(p.pow(2.0).sum() for p in model.parameters())
loss = criterion(outputs, y_train) + l2_lambda * l2_norm
当正负样本比例严重失衡时(如1:99),模型可能会倾向于总是预测多数类。解决方法包括:
python复制# 计算类别权重
pos_weight = torch.tensor([(len(y_train)-y_train.sum())/y_train.sum()])
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
虽然Logistic Regression是线性模型,但通过巧妙的特征工程可以显著提升性能:
当模型表现不佳时,可以尝试以下调试步骤:
python复制# 监控梯度
for name, param in model.named_parameters():
if param.grad is not None:
print(f'{name} gradient norm: {param.grad.norm().item():.4f}')
虽然本文聚焦二分类,但Logistic Regression也可以扩展到多分类(称为Softmax Regression)。主要区别在于:
Logistic Regression常常作为更复杂模型的组成部分或最后一层,例如:
在实际项目中,我经常先用Logistic Regression建立基线模型,然后再尝试更复杂的模型。这样既能快速验证特征的有效性,也能为后续模型提供性能比较基准。