PyTorch 入门实践:表格数据分类任务记录

深度学习是人工智能领域的关键方向。PyTorch 作为一款开源框架,以其直观、灵活的特性显著降低了入门门槛。本文记录了初次接触 PyTorch 并应用于表格数据分类任务的学习过程与核心步骤。

PyTorch 简述

PyTorch 由 Facebook AI 研究团队 (FAIR) 于 2016 年推出。其设计强调与 Python 语言的深度契合,提供易读、简洁的接口和动态计算图特性,便于理解内部机制与实验调试。PyTorch 已成为当前主流的深度学习框架之一。

学习动机

深度学习常被视为需要深厚专业背景的领域。PyTorch 的设计理念则致力于降低学习曲线,具备基础的 Python 编程能力和数学概念即可开始实践。其动态计算图特性为模型调试和迭代提供了显著便利,尤其适合初学者快速构建神经网络并体会深度学习的核心流程。(契机源于一位学长的推荐)。

环境与资源


实践一:表格数据分类

本次练习目标在于熟悉 PyTorch 核心工作流,并将其应用于表格数据分类任务。流程涵盖数据加载、模型构建、训练及评估。

数据集:Rice Type Classification

数据集目标为区分不同类型的水稻,包含颗粒的几何属性:

  • Area: 颗粒面积
  • MajorAxisLength: 主轴长度
  • MinorAxisLength: 副轴长度
  • Eccentricity: 偏心率
  • ConvexArea: 凸包面积
  • EquivDiameter: 等效直径
  • Extent: 边界框像素比例
  • Perimeter: 周长
  • Roundness: 圆度
  • AspectRation: 纵横比
  • Class: 水稻类型 (目标变量,二分类)。

1. 数据获取与环境配置

首要步骤是将 Kaggle 数据集导入 Colab 环境,并加载必要的 Python 库。

# 安装 opendatasets 库 (Kaggle 数据集下载)
!pip install opendatasets --quiet
import opendatasets as od

# 下载数据集
dataset_url = 'https://www.kaggle.com/datasets/mssmartypants/rice-type-classification'
od.download(dataset_url)  # 需输入 Kaggle 凭据
# 核心 PyTorch 模块
import torch
import torch.nn as nn
from torch.optim import Adam
from torch.utils.data import DataLoader, Dataset
from torchsummary import summary

# 数据处理与评估
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
import numpy as np

# 可视化
import matplotlib.pyplot as plt

# 设备配置 (优先 GPU)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"Using device: {device}")

说明: opendatasets 简化了 Kaggle 数据获取流程。导入的库构成了 PyTorch 项目的基础框架。


2. 数据加载与预处理

加载数据至 Pandas DataFrame,进行初步清洗,并按标准划分为训练集、验证集和测试集。

# 加载数据
data_df = pd.read_csv("/content/rice-type-classification/riceClassification.csv")

# 数据清洗
data_df.dropna(inplace=True)  # 移除缺失值
data_df.drop(['id'], axis=1, inplace=True)  # 移除无关 ID 列
print(f"Processed data shape: {data_df.shape}")  # (18185, 11)
# 分离特征与标签
x = data_df.iloc[:, :-1].values  # 特征 (NumPy 数组)
y = data_df.iloc[:, -1].values   # 标签 (NumPy 数组)

# 数据集划分 (70% 训练, 15% 验证, 15% 测试)
x_train, x_temp, y_train, y_temp = train_test_split(x, y, test_size=0.3)
x_val, x_test, y_val, y_test = train_test_split(x_temp, y_temp, test_size=0.5)

print(f"Train: {x_train.shape}, Val: {x_val.shape}, Test: {x_test.shape}")

说明: id 列无预测价值,常规清洗移除缺失值。数据集划分遵循机器学习标准实践:训练集用于学习模型参数,验证集用于调参与防止过拟合,测试集用于最终评估泛化能力。


3. 构建 PyTorch Dataset 与 DataLoader

Dataset 封装单个样本访问,DataLoader 实现批量加载、数据打乱与并行处理,提升训练效率。

class RiceDataset(Dataset):
    def __init__(self, features, labels):
        # 转换数据为 PyTorch 张量并移至目标设备
        self.features = torch.tensor(features, dtype=torch.float32).to(device)
        self.labels = torch.tensor(labels, dtype=torch.float32).to(device)

    def __len__(self):
        return len(self.features)

    def __getitem__(self, idx):
        return self.features[idx], self.labels[idx]

# 创建数据集实例
train_dataset = RiceDataset(x_train, y_train)
val_dataset = RiceDataset(x_val, y_val)
test_dataset = RiceDataset(x_test, y_test)

# 创建 DataLoader
BATCH_SIZE = 8
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE)

说明: 实现 Dataset 类明确了数据访问逻辑。to(device) 确保数据与模型位于同一计算设备(GPU/CPU)。DataLoaderbatch_sizeshuffle 参数对训练效率和模型泛化性至关重要。


4. 神经网络模型构建

针对表格数据,构建一个基础的全连接网络 (Fully Connected Network)。

HIDDEN_SIZE = 10  # 隐藏层神经元数量

class RiceClassifier(nn.Module):
    def __init__(self, input_size):
        super().__init__()
        self.input_layer = nn.Linear(input_size, HIDDEN_SIZE)
        self.output_layer = nn.Linear(HIDDEN_SIZE, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = self.input_layer(x)
        x = self.output_layer(x)
        x = self.sigmoid(x)
        return x

# 实例化模型 (输入特征数为 x.shape[1])
model = RiceClassifier(x.shape[1]).to(device)

说明: 继承 nn.Module 并定义 __init__ (层结构) 与 forward (数据流) 是 PyTorch 模型的标准模式。选择 Sigmoid 激活函数适配二分类任务的概率输出。


5. 模型结构概览

使用 torchsummary 快速查看模型结构与参数量。

summary(model, (x.shape[1],))
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
           Linear-1                   [-1, 10]             110
           Linear-2                    [-1, 1]              11
          Sigmoid-3                    [-1, 1]               0
================================================================
Total params: 121
Trainable params: 121
Non-trainable params: 0
----------------------------------------------------------------
...

说明: summary 输出清晰展示了模型层级、输出维度及参数总量,有助于理解模型复杂度。


6. 训练流程:损失函数、优化器与循环

配置训练核心组件并执行迭代优化。

# 损失函数与优化器
criterion = nn.BCELoss()  # 二元交叉熵 (Binary Cross Entropy)
optimizer = Adam(model.parameters(), lr=0.001)  # Adam 优化器

EPOCHS = 100  # 训练轮数

# 记录指标
train_losses, val_losses = [], []
train_accs, val_accs = [], []

for epoch in range(EPOCHS):
    # ----- 训练阶段 -----
    model.train()
    epoch_train_loss = 0.0
    epoch_train_correct = 0

    for features, targets in train_loader:
        optimizer.zero_grad()  # 清零梯度
        outputs = model(features).squeeze(1)  # 前向传播 [batch_size, 1] -> [batch_size]
        loss = criterion(outputs, targets)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数

        epoch_train_loss += loss.item()
        epoch_train_correct += ((outputs.round() == targets).sum().item())

    avg_train_loss = epoch_train_loss / len(train_loader)
    avg_train_acc = 100.0 * epoch_train_correct / len(train_dataset)
    train_losses.append(avg_train_loss)
    train_accs.append(avg_train_acc)

    # ----- 验证阶段 -----
    model.eval()
    epoch_val_loss = 0.0
    epoch_val_correct = 0

    with torch.no_grad():
        for features, targets in val_loader:
            outputs = model(features).squeeze(1)
            loss = criterion(outputs, targets)
            epoch_val_loss += loss.item()
            epoch_val_correct += ((outputs.round() == targets).sum().item())

    avg_val_loss = epoch_val_loss / len(val_loader)
    avg_val_acc = 100.0 * epoch_val_correct / len(val_dataset)
    val_losses.append(avg_val_loss)
    val_accs.append(avg_val_acc)

    # 输出当前 Epoch 结果
    print(f"Epoch {epoch+1:03d}/{EPOCHS}: "
          f"Train Loss: {avg_train_loss:.4f}, Train Acc: {avg_train_acc:.2f}%, "
          f"Val Loss: {avg_val_loss:.4f}, Val Acc: {avg_val_acc:.2f}%")

说明: 训练循环包含标准步骤:梯度清零 (zero_grad)、前向传播 (forward)、损失计算 (criterion)、反向传播 (backward)、参数更新 (step)。model.train()/model.eval() 切换训练/评估模式。指标记录用于监控过程。


7. 模型评估

在独立测试集上评估模型的最终泛化性能。

model.eval()
test_correct = 0

with torch.no_grad():
    for features, targets in test_loader:
        outputs = model(features).squeeze(1)
        test_correct += ((outputs.round() == targets).sum().item())

test_accuracy = 100.0 * test_correct / len(test_dataset)
print(f"Final Test Accuracy: {test_accuracy:.4f}%")
Final Test Accuracy: 98.6437%

说明: 测试集评估是衡量模型泛化到未见数据能力的关键步骤。98.64% 的准确率表明模型在该数据集上表现良好。


8. 训练过程可视化

绘制损失和准确率曲线以分析训练动态。

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))

# 损失曲线
ax1.plot(train_losses, label='Training Loss')
ax1.plot(val_losses, label='Validation Loss')
ax1.set_title('Training & Validation Loss')
ax1.set_xlabel('Epochs')
ax1.set_ylabel('Loss')
ax1.legend()
ax1.set_ylim(bottom=0)

# 准确率曲线
ax2.plot(train_accs, label='Training Accuracy')
ax2.plot(val_accs, label='Validation Accuracy')
ax2.set_title('Training & Validation Accuracy')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('Accuracy (%)')
ax2.legend()
ax2.set_ylim([80, 100])

plt.tight_layout()
plt.show()

说明: 损失曲线稳定下降且训练/验证损失趋势接近,准确率曲线同步上升,表明模型有效学习且无明显过拟合现象。可视化是监控训练状态的有效工具。


9. 单样本预测 (注意事项)

对新样本进行预测需遵循与训练数据相同的预处理流程。重要提示:原始代码未包含显式数据标准化。神经网络通常受益于输入特征的缩放(归一化/标准化)。若训练时应用了此类处理,预测时的新样本必须使用相同的参数进行变换。

# 示例:假设使用最大值进行简单缩放 (Min-Max Scaling 变体)。实际应基于训练集统计量。
# 此处仅作演示,实际项目需在预处理阶段统一处理并保存参数。
temp_df = pd.read_csv("/content/rice-type-classification/riceClassification.csv").drop('id', axis=1)
max_vals = temp_df.max().values[:-1]  # 获取各特征最大值 (排除标签列)

# 构造一个新样本 (示例值)
new_sample = np.array([2353, 81, 42, 0.75, 12, 12, 0.70, 927, 0.85, 1.5])
scaled_sample = new_sample / max_vals  # 应用模拟缩放

# 预测
model.eval()
with torch.no_grad():
    # 将样本转为张量并添加批次维度 [1, num_features]
    input_tensor = torch.tensor(scaled_sample.reshape(1, -1), dtype=torch.float32).to(device)
    prediction_prob = model(input_tensor)
    predicted_class = prediction_prob.round().item()  # 四舍五入得类别 (0/1)

print(f"Predicted Probability: {prediction_prob.item():.4f}")
print(f"Predicted Class: {predicted_class}")
Predicted Probability: 0.0000
Predicted Class: 0.0

说明: 单样本预测需注意输入张量的形状 ([batch_size=1, num_features])。模型输出为属于正类的概率,通过 round() 获得最终类别。此步骤模拟了模型在实际应用中的推理过程。


总结

本次实践系统性地应用 PyTorch 完成了一个表格数据分类任务。从数据加载、预处理、模型构建、训练、评估到可视化,完整地走通了核心流程。尽管模型结构较为基础,但达到了较高的测试准确率(98.64%)。训练/验证曲线表明学习过程稳定有效。

通过这次探索,加深了对 PyTorch 基础概念(Dataset, DataLoader, nn.Module, 训练循环)的理解,为后续处理更复杂的模型和任务奠定了基础。感谢相关学习资源提供的清晰指引。

想温柔的对待这个世界