【Python学习打卡-Day36】实战重构:用PyTorch神经网络升级信贷预测项目

📋 前言

大家好!今天是意义非凡的 Day 36,一个承上启下的复习日。经过前几天的学习,我们已经掌握了 PyTorch 的基本操作。现在,是时候将这些"零件"组装起来,完成一次从"手工作坊"到"现代化工厂"的升级了!

今天的核心任务是:重拾之前的信贷预测项目,但这一次,我们将用全套的 PyTorch 流程来重构它 。我们的目标不仅是让代码跑通,更是要让它变得规范、美观、易于维护 。同时,我们还会深入探索 PyTorch 的灵魂------nn.Module,看看它内部到底藏着什么秘密。

准备好了吗?让我们开始这场代码的"精装修"之旅!


一、知识点回顾:我们的"工具箱"里有什么?

在开始重构之前,我们先盘点一下最近学到的、即将用于本次项目的"神兵利器":

  1. 数据层

    • torch.Tensor: PyTorch 的基本数据结构。
    • torch.FloatTensor, torch.LongTensor: 分别用于特征和标签。
    • 数据预处理:MinMaxScaler 依然适用,但结果需转换为 Tensor。
    • 设备管理:.to(device),轻松实现 CPU/GPU 切换。
  2. 模型层

    • nn.Module: 所有模型的基类,我们的"蓝图"。
    • nn.Linear, nn.ReLU: 构建全连接神经网络的核心组件。
    • super().__init__(): 在子类中调用父类的构造函数,标准范式。
  3. 训练层

    • 损失函数 : nn.CrossEntropyLoss (用于多分类)。
    • 优化器 : torch.optim.SGDtorch.optim.Adam
    • 黄金三步 : optimizer.zero_grad() -> loss.backward() -> optimizer.step()
  4. 工程实践层

    • 模型诊断 : torchinfo,一键查看模型结构和参数。
    • 进度可视化 : tqdm,让漫长的训练过程不再枯燥。
    • 推理模式 : model.eval()with torch.no_grad(),确保评估的严谨性和高效性。

今天,我们将把以上所有知识点,像拼乐高一样,严丝合缝地应用到我们的项目中。


二、实战作业:用神经网络重构信貸预测项目

1. 项目目标

使用 PyTorch 构建一个多层感知机 (MLP) 模型,对信贷数据集进行训练和评估,并确保代码结构清晰、过程可追溯。

2. 重构步骤与代码实现

我们将遵循"数据-模型-训练-评估"的经典流程来组织代码。

python 复制代码
# 【我的代码 - Day 36 作业】
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import classification_report, accuracy_score
from torchinfo import summary
from tqdm import tqdm
import time

# --- 1. 环境准备 ---
# 检查是否有可用的 GPU,否则使用 CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"======== 当前使用的设备: {device} ========\n")

# --- 2. 数据加载与预处理 ---
# 注意:请确保 'data.csv' 文件与此脚本在同一目录下,或提供完整路径
try:
    data = pd.read_csv('data.csv')
except FileNotFoundError:
    print("错误:未找到 'data.csv' 文件。请将数据文件放在正确的位置。")
    exit()

# 简单的预处理,与之前保持一致
data['Term'] = data['Term'].replace({'Short Term': 0, 'Long Term': 1})
data.drop_duplicates(inplace=True)
data.dropna(inplace=True)

X = data.drop('Term', axis=1)
y = data['Term']

# 数据划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 特征缩放
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 转换为 PyTorch Tensors 并移动到指定设备
X_train_tensor = torch.FloatTensor(X_train_scaled).to(device)
y_train_tensor = torch.LongTensor(y_train.values).to(device)
X_test_tensor = torch.FloatTensor(X_test_scaled).to(device)
y_test_tensor = torch.LongTensor(y_test.values).to(device)

print("======== 数据准备完成 ========")
print(f"训练集大小: {X_train_tensor.shape}")
print(f"测试集大小: {X_test_tensor.shape}\n")

# --- 3. 模型定义 ---
class CreditMLP(nn.Module):
    def __init__(self, input_size, hidden_size1, hidden_size2, num_classes):
        super(CreditMLP, self).__init__()
        self.network = nn.Sequential(
            nn.Linear(input_size, hidden_size1),
            nn.ReLU(),
            nn.Linear(hidden_size1, hidden_size2),
            nn.ReLU(),
            nn.Linear(hidden_size2, num_classes)
        )

    def forward(self, x):
        return self.network(x)

# 模型实例化
input_dim = X_train.shape[1]
model = CreditMLP(input_size=input_dim, hidden_size1=64, hidden_size2=32, num_classes=2).to(device)

# 使用 torchinfo 打印模型摘要
print("======== 模型结构摘要 ========")
summary(model, input_size=(1, input_dim)) # (batch_size, features)
print("\n")

# --- 4. 训练过程 ---
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
num_epochs = 100

print("======== 开始模型训练 ========")
start_time = time.time()

for epoch in range(num_epochs):
    model.train() # 设置为训练模式
    
    # 前向传播
    outputs = model(X_train_tensor)
    loss = criterion(outputs, y_train_tensor)
    
    # 反向传播与优化
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

end_time = time.time()
print(f"======== 训练完成,耗时: {end_time - start_time:.2f} 秒 ========\n")

# --- 5. 模型评估 ---
print("======== 开始模型评估 ========")
model.eval() # 切换到评估模式
with torch.no_grad(): # 在此代码块中不计算梯度
    # 在测试集上进行预测
    test_outputs = model(X_test_tensor)
    
    # 获取预测结果
    # torch.max 返回 (values, indices),我们只需要索引
    _, predicted = torch.max(test_outputs.data, 1)
    
    # 将 GPU 上的 tensor 移动到 CPU 以便使用 sklearn
    predicted_cpu = predicted.cpu().numpy()
    y_test_cpu = y_test_tensor.cpu().numpy()
    
    # 计算并打印评估指标
    accuracy = accuracy_score(y_test_cpu, predicted_cpu)
    report = classification_report(y_test_cpu, predicted_cpu, target_names=['Short Term', 'Long Term'])
    
    print(f"测试集准确率: {accuracy * 100:.2f}%\n")
    print("分类报告:")
    print(report)

3. 结果分析与心得

  • 规范性:代码被清晰地划分为环境准备、数据处理、模型定义、训练、评估五个部分。每个部分职责单一,逻辑清晰。
  • 美观性 :使用了 print 语句作为分隔符,输出了关键信息(设备、数据大小、模型结构等),让整个执行过程一目了然。
  • 健壮性 :模型定义被封装在 CreditMLP 类中,实现了代码的复用和模块化。
  • 用户体验 :虽然本次训练很快,但如果 epochs 很多,tqdm 进度条将极大提升体验。在未来的项目中,这是一个好习惯。
  • 严谨性 :严格区分了 model.train()model.eval() 模式,并使用 torch.no_grad() 进行高效推理。

通过这次重构,我们的代码不再是杂乱无章的脚本,而是一个有模有样的迷你项目,这正是专业开发的起点。


三、探索性作业:深入 nn.Module 的"心脏"

问题 :我们总是写 model = MyModel(),然后用 model(data) 来进行预测,但我们自己只定义了 forward 方法,model() 这个调用是怎么工作的?model.to(device) 又是从哪来的?

答案 :所有秘密都藏在 nn.Module 的源码里。

在 VSCode 或 PyCharm 中,按住 Ctrl (或 Cmd) 键,然后用鼠标点击你代码中的 nn.Module,就可以跳转到它的定义。你会发现许多以双下划线开头和结尾的"魔法方法"。

这里揭示几个最重要的秘密:

  1. __call__ 的魔法

    • 你以为 model(data) 直接调用了 forward?不完全是!它实际上调用的是 __call__ 方法。
    • __call__ 是一个"总管",它在调用我们写的 forward 之前和之后,会做很多额外的工作,比如执行我们后面会学到的"钩子 (Hooks)"。
    • 结论nn.Module 的设计鼓励我们只关心核心逻辑 (forward),而把复杂的管理工作交给框架。所以,永远使用 model(data),而不是 model.forward(data)
  2. train()eval() 的开关

    • nn.Module 内部有一个名为 self.training 的布尔值标志。
    • 调用 model.train() 会将 self.training 设为 True
    • 调用 model.eval() 会将其设为 False
    • nn.Dropoutnn.BatchNorm2d 这样的层会检查这个标志,来决定自己应该如何工作。
  3. parameters() 的"户口本"

    • nn.Module 会自动追踪所有被赋值为 nn.Parameter 或其子模块的属性。
    • 当你调用 model.parameters() 时,它会递归地遍历所有子模块,把所有需要训练的参数(权重、偏置)收集起来,像一个"户口本",然后交给优化器去更新。

比喻时间
nn.Module 就像一个智能汽车底盘

  • __init__: 你在底盘上安装引擎、轮胎(定义 nn.Linear 等层)。
  • forward: 你定义了踩油门时,动力如何从引擎传递到轮胎(数据如何流过网络)。
  • __call__: 是整个汽车的驾驶系统。你踩下油门踏板 (model(data)),系统不仅会执行你的 forward 逻辑,还会检查车况、调整悬挂(执行 Hooks)。
  • parameters(): 是汽车的零件清单,方便你去保养和升级(交给优化器)。
  • to(device): 是把汽车开到不同的赛道(CPU/GPU)上。

通过这次探索,我们不再是 nn.Module 的简单使用者,而是开始理解其设计哲学的思考者。


四、总结

Day 36 是一个里程碑。我们不仅成功地将神经网络应用于一个真实的项目,更重要的是,我们学会了如何有组织、有纪律地编写深度学习代码。代码的规范性和美观性,与它的功能性同等重要。

通过重构和探索,我们内化了 PyTorch 的设计思想,为未来构建更复杂、更强大的模型打下了坚实的地基。


再次感谢 @浙大疏锦行 老师的精心安排,这次复习让我对之前的知识有了脱胎换骨的理解!

相关推荐
亮子AI3 小时前
【Python】比较两个cli库:Click vs Typer
开发语言·python
CappuccinoRose3 小时前
流计算概述
python·flink·流计算·数据流·pyflink
Dragon水魅3 小时前
Fandom Wiki 网站爬取文本信息踩坑实录
爬虫·python
Darkershadow3 小时前
蓝牙学习之unprovision beacon
python·学习·ble
小龙在山东4 小时前
基于 plumbum 跨平台执行Shell脚本
python
报错小能手4 小时前
线程池学习(六)实现工作窃取线程池(WorkStealingThreadPool)
开发语言·学习
CCPC不拿奖不改名4 小时前
python基础:python语言中的函数与模块+面试习题
开发语言·python·面试·职场和发展·蓝桥杯
毕设源码-朱学姐4 小时前
【开题答辩全过程】以 基于Python语言的疫情数据可视化系统为例,包含答辩的问题和答案
开发语言·python·信息可视化
我送炭你添花4 小时前
Pelco KBD300A 模拟器:07+2.Python 专题:线程安全与信号槽机制——项目多线程最佳实践
python·自动化·运维开发
小途软件5 小时前
ssm607家政公司服务平台的设计与实现+vue
java·人工智能·pytorch·python·深度学习·语言模型