【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 的设计思想,为未来构建更复杂、更强大的模型打下了坚实的地基。


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

相关推荐
bylander11 小时前
【AI学习】TM Forum自智网络L4级标准体系
人工智能·学习·智能体·自动驾驶网络
我想我不够好。11 小时前
2026.1.28 消防监控学习
学习
2301_8174973311 小时前
自然语言处理(NLP)入门:使用NLTK和Spacy
jvm·数据库·python
Engineer邓祥浩11 小时前
设计模式学习(24) 23-22 策略模式
学习·设计模式·策略模式
2601_9497202611 小时前
flutter_for_openharmony手语学习app实战+手语识别实现
学习·flutter
weixin_5500831511 小时前
QTdesigner配置在pycharm里使用anaconda环境配置安装成功
ide·python·pycharm
浅念-11 小时前
C语言——内存函数
c语言·经验分享·笔记·学习·算法
强化试剂瓶11 小时前
Silane-PEG8-DBCO,硅烷-聚乙二醇8-二苯并环辛炔技术应用全解析
python·flask·numpy·pyqt·fastapi
●VON11 小时前
Flutter for OpenHarmony:基于 SharedPreferences 的本地化笔记应用架构与实现
笔记·学习·flutter·ui·架构·openharmony·von
求真求知的糖葫芦11 小时前
耦合传输线分析学习笔记(九)对称耦合微带线S参数矩阵推导与应用(下)
笔记·学习·矩阵·射频工程