系列文章目录
01-PyTorch新手必看:张量是什么?5 分钟教你快速创建张量!
02-张量运算真简单!PyTorch 数值计算操作完全指南
03-Numpy 还是 PyTorch?张量与 Numpy 的神奇转换技巧
04-揭秘数据处理神器:PyTorch 张量拼接与拆分实用技巧
05-深度学习从索引开始:PyTorch 张量索引与切片最全解析
06-张量形状任意改!PyTorch reshape、transpose 操作超详细教程
07-深入解读 PyTorch 张量运算:6 大核心函数全面解析,代码示例一步到位!
08-自动微分到底有多强?PyTorch 自动求导机制深度解析
09-从零手写线性回归模型:PyTorch 实现深度学习入门教程
10-PyTorch 框架实现线性回归:从数据预处理到模型训练全流程
11-PyTorch 框架实现逻辑回归:从数据预处理到模型训练全流程
文章目录
- 系列文章目录
- 前言
- 一、逻辑回应基础概念
-
- [1.1 逻辑回应的数学表达](#1.1 逻辑回应的数学表达)
-
- [1.1.1 交叉疵损失函数](#1.1.1 交叉疵损失函数)
- [1.2 逻辑回应的应用场景](#1.2 逻辑回应的应用场景)
- [二、使用 PyTorch 实现逻辑回应](#二、使用 PyTorch 实现逻辑回应)
-
- [2.1 数据准备](#2.1 数据准备)
-
- [2.1.1 注意事项](#2.1.1 注意事项)
- [2.2 模型构建](#2.2 模型构建)
-
- [2.2.1 关键代码解析](#2.2.1 关键代码解析)
- [2.3 模型训练](#2.3 模型训练)
-
- [2.3.1 注意事项](#2.3.1 注意事项)
- [2.4 模型测试](#2.4 模型测试)
-
- [2.4.1 可视化分类结果](#2.4.1 可视化分类结果)
- [2.5 示例输出](#2.5 示例输出)
- 三、常见问题与优化
-
- [3.1 常见问题](#3.1 常见问题)
-
- [3.1.1 训练不收敛](#3.1.1 训练不收敛)
- [3.1.2 模型过拟合](#3.1.2 模型过拟合)
- [3.1.3 测试精度较低](#3.1.3 测试精度较低)
- [3.2 优化技巧](#3.2 优化技巧)
-
- [3.2.1 学习率调整](#3.2.1 学习率调整)
- [3.2.2 正则化](#3.2.2 正则化)
- [3.2.3 批量训练](#3.2.3 批量训练)
- [3.2.4 使用更复杂的特征](#3.2.4 使用更复杂的特征)
- [3.2.5 增加模型复杂度](#3.2.5 增加模型复杂度)
- 四、总结
-
- [4.1 完整代码](#4.1 完整代码)
- [4.2 优化后代码](#4.2 优化后代码)
-
- [4.2.1 完整代码](#4.2.1 完整代码)
- [4.2.2 示例输出](#4.2.2 示例输出)
前言
逻辑回应是机器学习中一种经典的监督学习算法,常用于解决分类问题。在深度学习领域,PyTorch 作为一个高效且灵活的框架,为逻辑回应的实现提供了强大的支持。本篇文章将系统介绍如何使用 PyTorch 实现逻辑回应,逐步从基础概念到代码实现,并结合实际应用场景,为初学者和进阶读者提供完整的学习路径。
适用读者:
- 想了解 PyTorch 基础操作的初学者。
- 希望掌握逻辑回应实现与应用的进阶学习者。
- 寻找分类任务实践指导的开发者。
文章结构:
- 逻辑回应基础概念与理论解析。
- 使用 PyTorch 实现逻辑回应的完整步骤。
- 实际案例分析及代码示例。
- 常见问题与优化技巧。
一、逻辑回应基础概念
逻辑回应是一种用于二分类问题的线性模型,其核心思想是利用 sigmoid 函数将线性回应的输出映射到 (0,1) 区间,从而得到类别概率。
1.1 逻辑回应的数学表达
逻辑回应的目标是建立输入特征 X X X 与输出类别 Y Y Y 之间的关系:
P ( Y = 1 ∣ X ) = σ ( W X + b ) P(Y=1|X) = \sigma(WX + b) P(Y=1∣X)=σ(WX+b)
其中:
- σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1+e^{-z}} σ(z)=1+e−z1 是 sigmoid 激活函数。
- W W W 是权重矩阵, b b b 是偏缘项。
- 输出 P ( Y = 1 ∣ X ) P(Y=1|X) P(Y=1∣X) 表示预测为类别 1 的概率。
通过最小化交叉疵损失(Cross-Entropy Loss),优化 W W W 和 b b b 的值,最终得到分类模型。
1.1.1 交叉疵损失函数
对于二分类问题,交叉疵损失函数的公式为:
L = − 1 N ∑ i = 1 N [ y i log ( y ^ i ) + ( 1 − y i ) log ( 1 − y ^ i ) ] L = -\frac{1}{N} \sum_{i=1}^N \left[ y_i \log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i) \right] L=−N1i=1∑N[yilog(y^i)+(1−yi)log(1−y^i)]
其中:
- y i y_i yi 是真实标签, y ^ i \hat{y}_i y^i 是模型预测概率。
- N N N 是样本数量。
通过梯度下降法优化上述损失函数,即可训练逻辑回应模型。
1.2 逻辑回应的应用场景
逻辑回应广泛应用于以下领域:
- 医学诊断:如病症预测(是否患病)。
- 金融风控:如信用评分(用户是否违约)。
- 自然进行处理:如文本分类(垃圾邮件检测)。
二、使用 PyTorch 实现逻辑回应
接下来,将通过 PyTorch 框架实现一个完整的逻辑回应模型,包括数据准备、模型构建、训练与测试。
2.1 数据准备
我们以一个简单的二分类问题为例,使用 PyTorch 内置的 make_classification
方法生成数据集。
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=2, # 总特征数
n_informative=2, # 有效特征数(与 n_features 相等)
n_redundant=0, # 冗余特征数(设置为 0)
n_repeated=0, # 重复特征数(设置为 0)
n_classes=2, # 分类类别数
random_state=42 # 随机种子
)
# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 数据标准化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 转换为 PyTorch 张量
X_train = torch.tensor(X_train, dtype=torch.float32) # 转换训练集为 PyTorch 张量
y_train = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1) # 保证标签维度为 [N, 1]
X_test = torch.tensor(X_test, dtype=torch.float32) # 转换测试集为 PyTorch 张量
y_test = torch.tensor(y_test, dtype=torch.float32).unsqueeze(1) # 保证测试集标签维度为 [N, 1]
2.1.1 注意事项
- 数据标准化是逻辑回归的关键步骤,可加速模型收敛。
- 确保标签维度一致,例如
y_train
的 shape 应为[N, 1]
。
2.2 模型构建
在 PyTorch 中,我们可以通过 torch.nn.Module
自定义逻辑回归模型。
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) # 定义线性层,用于实现线性变换 WX + b
self.sigmoid = nn.Sigmoid() # 定义 Sigmoid 激活函数
def forward(self, x):
return self.sigmoid(self.linear(x)) # 前向传播过程,线性变换后通过 Sigmoid 激活
# 初始化模型
input_dim = X_train.shape[1] # 获取输入特征的维度
model = LogisticRegression(input_dim) # 实例化逻辑回归模型
2.2.1 关键代码解析
nn.Linear
实现线性变换 W X + b WX + b WX+b。nn.Sigmoid
将线性输出映射到概率范围。
2.3 模型训练
逻辑回归的训练过程包括定义损失函数、优化器,并循环迭代更新参数。
python
# 定义损失函数和优化器
criterion = nn.BCELoss() # 使用二分类交叉熵损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 使用随机梯度下降优化器,学习率为 0.01
# 训练模型
epochs = 100 # 训练轮次
for epoch in range(epochs):
# 前向传播
outputs = model(X_train) # 获取模型对训练集的预测结果
loss = criterion(outputs, y_train) # 计算损失函数值
# 反向传播
optimizer.zero_grad() # 清除梯度缓存,避免梯度累积
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
# 打印训练信息
if (epoch+1) % 10 == 0: # 每 10 个 epoch 打印一次损失
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
2.3.1 注意事项
- 使用
optimizer.zero_grad()
清除梯度,避免累积。 - 交叉熵损失函数需要模型输出为概率值,因此需要
Sigmoid
激活。
2.4 模型测试
最后,我们评估模型的分类性能。
python
# 模型评估
y_pred = model(X_test) # 获取模型对测试集的预测概率
y_pred_class = (y_pred >= 0.5).float() # 将概率值转换为二分类标签(0 或 1)
accuracy = (y_pred_class == y_test).sum() / y_test.shape[0] # 计算分类准确率
print(f'Accuracy: {accuracy:.4f}') # 打印准确率
2.4.1 可视化分类结果
我们可以通过绘制分类边界直观展示模型效果。
python
import matplotlib.pyplot as plt
import numpy as np
# 绘制分类边界
def plot_decision_boundary(X, y, model):
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.01), # 生成网格点
np.arange(y_min, y_max, 0.01))
grid = np.c_[xx.ravel(), yy.ravel()] # 将网格点展开成二维数组
grid_tensor = torch.tensor(grid, dtype=torch.float32) # 转换为 PyTorch 张量
probs = model(grid_tensor).detach().numpy().reshape(xx.shape) # 获取模型预测概率,并重塑为网格形状
plt.contourf(xx, yy, probs, alpha=0.8, cmap=plt.cm.Spectral) # 绘制概率等高线
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor='k', cmap=plt.cm.Spectral) # 绘制样本点
plt.show() # 显示图像
plot_decision_boundary(X_test.numpy(), y_test.numpy().flatten(), model) # 调用绘图函数
2.5 示例输出
lua
Epoch [20/200], Loss: 0.5399
Epoch [40/200], Loss: 0.5289
Epoch [60/200], Loss: 0.5187
Epoch [80/200], Loss: 0.5094
Epoch [100/200], Loss: 0.5009
Epoch [120/200], Loss: 0.4930
Epoch [140/200], Loss: 0.4857
Epoch [160/200], Loss: 0.4789
Epoch [180/200], Loss: 0.4726
Epoch [200/200], Loss: 0.4668
Accuracy: 0.8700
这幅图展示了逻辑回归模型在二分类任务中的分类结果及分类边界:
-
分类边界:
- 背景色代表了模型对数据点分类的概率分布。
- 边界区域颜色的过渡体现了模型预测为某一类别的概率变化。颜色越深,模型对该类别的分类信心越高。
- 分类边界(颜色变化最显著的区域)是模型划分两类数据的核心区域,对应于逻辑回归模型中的决策边界。
-
数据分布:
- 红色和蓝色的散点分别表示两个类别的样本数据。
- 大多数样本在其对应类别的区域中分布良好,说明模型的分类能力较强。
- 边界附近的点可能具有一定的不确定性,模型对这些点的分类可能不够准确。
-
分类效果:
- 模型成功地将两个类别的样本通过一条近似线性的边界进行分割,符合逻辑回归模型的线性分类特性。
- 对于分布较为复杂的数据(如蓝点延伸到红色区域),模型的表现受限于逻辑回归的线性假设。
三、常见问题与优化
在使用 PyTorch 实现逻辑回归的过程中,可能会遇到一些问题,如模型不收敛、过拟合或性能不足。以下将详细分析常见问题的原因,并提供优化建议和实用技巧。
3.1 常见问题
3.1.1 训练不收敛
- 问题现象:模型的损失值在训练过程中没有明显下降。
- 可能原因 :
- 学习率过大或过小,导致梯度更新无法有效优化损失函数。
- 数据没有经过标准化,导致特征值范围不一致,模型难以学习到有效的参数。
- 模型初始化权重不合理,可能会导致梯度爆炸或梯度消失。
- 解决方案 :
- 调整学习率(建议尝试不同量级的学习率,例如
0.1
,0.01
,0.001
)。 - 在训练前对数据进行标准化处理,如使用
StandardScaler
将数据缩放到零均值和单位方差。 - 使用 PyTorch 默认的权重初始化方式,或显式指定权重初始化(例如 Xavier 初始化)。
- 调整学习率(建议尝试不同量级的学习率,例如
3.1.2 模型过拟合
- 问题现象:在训练集上表现良好,但在测试集上的性能明显下降。
- 可能原因 :
- 训练集样本不足或数据分布单一。
- 模型复杂度过高,拟合了数据中的噪声。
- 解决方案 :
- 使用更多的训练数据,或通过数据增强(Data Augmentation)来扩充数据集。
- 加入正则化项,例如 L2 正则化,可以通过优化器中的
weight_decay
参数实现。 - 使用交叉验证评估模型的泛化能力,避免过度依赖单一测试集。
3.1.3 测试精度较低
- 问题现象:训练过程正常,但模型在测试集上的分类精度不足。
- 可能原因 :
- 训练数据与测试数据分布差异较大(即数据集偏移问题)。
- 特征选择不足或数据特征中存在噪声。
- 逻辑回归模型的能力不足以拟合复杂数据分布。
- 解决方案 :
- 检查训练集与测试集的数据分布,确保两者的特征范围一致。
- 尝试使用特征工程(如特征筛选、特征组合)提取更有效的特征。
- 如果问题较复杂,可以尝试更强大的模型(如神经网络)代替逻辑回归。
3.2 优化技巧
为了进一步提升逻辑回归模型的性能,可以采用以下优化方法:
3.2.1 学习率调整
学习率是优化模型的关键参数。可以尝试以下方法:
-
学习率衰减 :随着训练的进行逐步减小学习率,提高模型的收敛性能。例如:
pythonscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
上述代码表示每 10 个 epoch 将学习率衰减为原来的 0.1 倍。
-
动态调整 :使用
ReduceLROnPlateau
动态调整学习率,当损失不再下降时降低学习率。pythonscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=5, factor=0.1)
3.2.2 正则化
在逻辑回归中,L2 正则化(即权重衰减)是最常见的防止过拟合的手段。可以在定义优化器时设置 weight_decay
参数,例如:
python
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=0.01) # 添加 L2 正则化
正则化可以限制模型参数的大小,避免过拟合。
3.2.3 批量训练
通过批量训练(Mini-batch Training),可以在保持训练效率的同时减小训练波动,尤其适用于大规模数据集。
python
from torch.utils.data import DataLoader, TensorDataset
# 创建数据集和 DataLoader
dataset = TensorDataset(X_train, y_train)
data_loader = DataLoader(dataset, batch_size=32, shuffle=True) # 每次训练 32 个样本
# 批量训练
for epoch in range(epochs):
for batch_X, batch_y in data_loader:
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
批量训练有助于平滑损失下降曲线,提高训练效率。
3.2.4 使用更复杂的特征
逻辑回归本质是线性模型,对于非线性问题的拟合能力有限。因此,可以尝试以下方法增强模型的表达能力:
-
特征多项式扩展 :引入二次或更高次特征,例如:
pythonfrom sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=2) X_train_poly = poly.fit_transform(X_train)
-
组合特征:通过构造新特征来捕捉变量间的非线性关系。
3.2.5 增加模型复杂度
如果逻辑回归无法胜任任务,可以尝试使用简单的神经网络,例如多层感知机(MLP):
python
class MLP(nn.Module):
def __init__(self, input_dim):
super(MLP, self).__init__()
self.fc1 = nn.Linear(input_dim, 32) # 第一层
self.relu = nn.ReLU() # 激活函数
self.fc2 = nn.Linear(32, 1) # 输出层
self.sigmoid = nn.Sigmoid() # 输出概率
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.sigmoid(self.fc2(x))
return x
四、总结
本文通过使用 PyTorch 实现逻辑回归模型,完成了以下内容:
- 构建数据集并设计数据加载器 :通过
make_classification
生成二分类数据集,并使用DataLoader
实现小批量训练; - 使用 nn.Linear 定义逻辑回归假设函数 :通过
nn.Linear
实现逻辑回归模型的核心线性变换; - 使用 nn.BCELoss 设计二分类交叉熵损失函数:确保分类问题的损失函数适用于概率输出;
- 使用 optim.SGD 并结合正则化优化模型 :通过随机梯度下降(SGD)优化器结合 L2 正则化(
weight_decay
)防止过拟合; - 动态调整学习率并提升模型收敛效率:引入学习率衰减策略,随着训练的进行逐步降低学习率;
- 训练模型并可视化分类边界:输出训练过程中的损失变化,最终绘制模型的分类边界以直观展示分类效果。
4.1 完整代码
python
import torch
import torch.nn as nn
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np
# 设置 Matplotlib 显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# ====================
# 2.1 数据准备
# ====================
# 生成数据集
X, y = make_classification(
n_samples=1000, # 样本数
n_features=2, # 总特征数
n_informative=2, # 有效特征数
n_redundant=0, # 冗余特征数
n_repeated=0, # 重复特征数
n_classes=2, # 分类类别数
random_state=42 # 随机种子
)
# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 数据标准化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 转换为 PyTorch 张量
X_train = torch.tensor(X_train, dtype=torch.float32) # 转换训练集为 PyTorch 张量
y_train = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1) # 保证标签维度为 [N, 1]
X_test = torch.tensor(X_test, dtype=torch.float32) # 转换测试集为 PyTorch 张量
y_test = torch.tensor(y_test, dtype=torch.float32).unsqueeze(1) # 保证测试集标签维度为 [N, 1]
# ====================
# 2.2 模型构建
# ====================
class LogisticRegression(nn.Module):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__()
self.linear = nn.Linear(input_dim, 1) # 定义线性层,用于实现线性变换 WX + b
self.sigmoid = nn.Sigmoid() # 定义 Sigmoid 激活函数
def forward(self, x):
return self.sigmoid(self.linear(x)) # 前向传播过程,线性变换后通过 Sigmoid 激活
# 初始化模型
input_dim = X_train.shape[1] # 获取输入特征的维度
model = LogisticRegression(input_dim) # 实例化逻辑回归模型
# ====================
# 2.3 模型训练
# ====================
# 定义损失函数和优化器
criterion = nn.BCELoss() # 使用二分类交叉熵损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.1) # 使用随机梯度下降优化器,学习率为 0.01
# 训练模型
epochs = 300 # 训练轮次
for epoch in range(epochs):
# 前向传播
outputs = model(X_train) # 获取模型对训练集的预测结果
loss = criterion(outputs, y_train) # 计算损失函数值
# 反向传播
optimizer.zero_grad() # 清除梯度缓存,避免梯度累积
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
# 打印训练信息
if (epoch+1) % 60 == 0: # 每 10 个 epoch 打印一次损失
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
# ====================
# 2.4 模型测试
# ====================
# 模型评估
y_pred = model(X_test) # 获取模型对测试集的预测概率
y_pred_class = (y_pred >= 0.5).float() # 将概率值转换为二分类标签(0 或 1)
accuracy = (y_pred_class == y_test).sum() / y_test.shape[0] # 计算分类准确率
print(f'Accuracy: {accuracy:.4f}') # 打印准确率
# ====================
# 2.4.1 可视化分类结果
# ====================
# 绘制分类边界
def plot_decision_boundary(X, y, model):
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.01), # 生成网格点
np.arange(y_min, y_max, 0.01))
grid = np.c_[xx.ravel(), yy.ravel()] # 将网格点展开成二维数组
grid_tensor = torch.tensor(grid, dtype=torch.float32) # 转换为 PyTorch 张量
probs = model(grid_tensor).detach().numpy().reshape(xx.shape) # 获取模型预测概率,并重塑为网格形状
plt.contourf(xx, yy, probs, alpha=0.8, cmap=plt.cm.Spectral) # 绘制概率等高线
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor='k', cmap=plt.cm.Spectral) # 绘制样本点
plt.xlabel("特征 1") # 设置 X 轴标签
plt.ylabel("特征 2") # 设置 Y 轴标签
plt.title("逻辑回归分类边界") # 设置标题
plt.show() # 显示图像
plot_decision_boundary(X_test.numpy(), y_test.numpy().flatten(), model) # 调用绘图函数
4.2 优化后代码
- 批量训练:使用 DataLoader 实现小批量训练,提高训练效率。
- 动态学习率:设置学习率衰减策略(每 50 个 epoch 学习率减半)。
- 正则化:在优化器中加入 L2 正则化(weight_decay 参数)。
- 训练日志优化:每 20 个 epoch 输出损失和当前学习率,便于跟踪训练进度。
4.2.1 完整代码
python
import torch
import torch.nn as nn
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from torch.utils.data import DataLoader, TensorDataset
import matplotlib.pyplot as plt
import numpy as np
# 设置 Matplotlib 显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# ====================
# 2.1 数据准备
# ====================
# 生成数据集
X, y = make_classification(
n_samples=1000, # 样本数
n_features=2, # 总特征数
n_informative=2, # 有效特征数
n_redundant=0, # 冗余特征数
n_repeated=0, # 重复特征数
n_classes=2, # 分类类别数
random_state=42 # 随机种子
)
# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 数据标准化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 转换为 PyTorch 张量
X_train = torch.tensor(X_train, dtype=torch.float32) # 转换训练集为 PyTorch 张量
y_train = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1) # 保证标签维度为 [N, 1]
X_test = torch.tensor(X_test, dtype=torch.float32) # 转换测试集为 PyTorch 张量
y_test = torch.tensor(y_test, dtype=torch.float32).unsqueeze(1) # 保证测试集标签维度为 [N, 1]
# 创建 DataLoader
train_dataset = TensorDataset(X_train, y_train)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) # 批量大小为 32
# ====================
# 2.2 模型构建
# ====================
class LogisticRegression(nn.Module):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__()
self.linear = nn.Linear(input_dim, 1) # 定义线性层,用于实现线性变换 WX + b
self.sigmoid = nn.Sigmoid() # 定义 Sigmoid 激活函数
def forward(self, x):
return self.sigmoid(self.linear(x)) # 前向传播过程,线性变换后通过 Sigmoid 激活
# 初始化模型
input_dim = X_train.shape[1] # 获取输入特征的维度
model = LogisticRegression(input_dim) # 实例化逻辑回归模型
# ====================
# 2.3 模型训练
# ====================
# 定义损失函数和优化器
criterion = nn.BCELoss() # 使用二分类交叉熵损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, weight_decay=0.01) # 加入 L2 正则化(weight_decay)
# 动态学习率调整
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.5) # 每 50 个 epoch 学习率减半
# 训练模型
epochs = 200 # 训练轮次
for epoch in range(epochs):
model.train() # 设置模型为训练模式
for batch_X, batch_y in train_loader:
# 前向传播
outputs = model(batch_X) # 获取模型对训练集的预测结果
loss = criterion(outputs, batch_y) # 计算损失函数值
# 反向传播
optimizer.zero_grad() # 清除梯度缓存,避免梯度累积
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
scheduler.step() # 更新学习率
# 打印训练信息
if (epoch + 1) % 20 == 0: # 每 20 个 epoch 打印一次损失
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, Learning Rate: {scheduler.get_last_lr()[0]:.6f}')
# ====================
# 2.4 模型测试
# ====================
# 模型评估
model.eval() # 设置模型为评估模式
with torch.no_grad():
y_pred = model(X_test) # 获取模型对测试集的预测概率
y_pred_class = (y_pred >= 0.5).float() # 将概率值转换为二分类标签(0 或 1)
accuracy = (y_pred_class == y_test).sum() / y_test.shape[0] # 计算分类准确率
print(f'Accuracy: {accuracy:.4f}') # 打印准确率
# ====================
# 2.4.1 可视化分类结果
# ====================
# 绘制分类边界
def plot_decision_boundary(X, y, model):
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.01), # 生成网格点
np.arange(y_min, y_max, 0.01))
grid = np.c_[xx.ravel(), yy.ravel()] # 将网格点展开成二维数组
grid_tensor = torch.tensor(grid, dtype=torch.float32) # 转换为 PyTorch 张量
probs = model(grid_tensor).detach().numpy().reshape(xx.shape) # 获取模型预测概率,并重塑为网格形状
plt.contourf(xx, yy, probs, alpha=0.8, cmap=plt.cm.Spectral) # 绘制概率等高线
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor='k', cmap=plt.cm.Spectral) # 绘制样本点
plt.xlabel("特征 1") # 设置 X 轴标签
plt.ylabel("特征 2") # 设置 Y 轴标签
plt.title("逻辑回归分类边界") # 设置标题
plt.show() # 显示图像
plot_decision_boundary(X_test.numpy(), y_test.numpy().flatten(), model) # 调用绘图函数
4.2.2 示例输出
lua
Epoch [20/200], Loss: 0.4166, Learning Rate: 0.100000
Epoch [40/200], Loss: 0.4401, Learning Rate: 0.100000
Epoch [60/200], Loss: 0.4131, Learning Rate: 0.050000
Epoch [80/200], Loss: 0.3316, Learning Rate: 0.050000
Epoch [100/200], Loss: 0.3692, Learning Rate: 0.025000
Epoch [120/200], Loss: 0.2930, Learning Rate: 0.025000
Epoch [140/200], Loss: 0.3391, Learning Rate: 0.025000
Epoch [160/200], Loss: 0.3489, Learning Rate: 0.012500
Epoch [180/200], Loss: 0.4923, Learning Rate: 0.012500
Epoch [200/200], Loss: 0.3672, Learning Rate: 0.006250
Accuracy: 0.8800