💻 源码
"""
模型调优思路:
1. 对输入数据进行标准化
2. 调整优化器
3. 调整学习率
4. 增加批量归一化层BN1D
5. 增加网络层数、神经元个数
6. 增加dropout层,缓解过拟合
7. 增加训练轮数
8. 使用GPU加速训练
9. ...

"""

import os
import torch  # pytorch框架
from torch.utils.data import TensorDataset  # 数据集对象
from torch.utils.data import DataLoader  # 数据加载器
import torch.nn as nn  # 神经网络层的操作
import torch.optim as optim  # 优化器
from sklearn.model_selection import train_test_split  # 拆分 训练集 和 测试集
import matplotlib.pyplot as plt  # 绘图
import numpy as np  # 数组操作
import pandas as pd  # 数据处理
import time  # 时间模块

from torchsummary import summary
from sklearn.preprocessing import StandardScaler
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 微软雅黑
plt.rcParams['axes.unicode_minus'] = False            # 解决负号显示问题
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"使用设备:{device}")

# 1.准备数据集
def create_dataset():
    # 1.加载CSV数据集
    data = pd.read_csv("data/手机价格预测.csv")
    # 2.获取特征x 和 标签y
    x = data.iloc[:, :-1]
    y = data.iloc[:, -1]

    # 3.特征列 转换为 float
    x = x.astype(np.float32)

    # 4.拆分数据集
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=6, stratify=y)

    # 数据标准化
    # scaler = StandardScaler()
    # x_train = scaler.fit_transform(x_train)
    # x_test = scaler.transform(x_test)
    # 5.把数据集转换为张量, numpy数组 -> 张量 -> 数据集对象TensorDataset -> 数据加载器DataLoader
    train_dataset = TensorDataset(torch.tensor(x_train.values), torch.tensor(y_train.values))
    test_dataset = TensorDataset(torch.tensor(x_test.values), torch.tensor(y_test.values))

    # 6.返回结果
    return train_dataset, test_dataset, x_train.shape[1], len(y_train.unique())

# 2.搭建神经网络
class PhonePriceClassifier(nn.Module):
    # 1.定义__init__方法, 初始化父类成员,搭建神经网络
    def __init__(self, input_dim, output_dim):
        # 初始化父类成员
        super().__init__()
        # 搭建神经网络
        # 优化1:增加网络深度+使用BN1D层
        self.linear1 = nn.Linear(input_dim, 512)
        self.bn1 = nn.BatchNorm1d(512)
        self.linear2 = nn.Linear(512, 512)
        self.bn2 = nn.BatchNorm1d(512)
        self.linear3 = nn.Linear(512, 256)
        self.bn3 = nn.BatchNorm1d(256)
        self.linear4 = nn.Linear(256, output_dim)
        self.dropout = nn.Dropout(0.1)

    # 2.定义forward方法,实现前向传播
    def forward(self, x):
        # 隐藏层1, linear -> BN层 -> 激活函数 -> dropout
        x = torch.relu(self.bn1(self.linear1(x)))
        x = self.dropout(x)
        # 隐藏层2
        x = torch.relu(self.bn2(self.linear2(x)))
        x = self.dropout(x)
        x = torch.relu(self.bn3(self.linear3(x)))
        x = self.dropout(x)
        # 输出层
        x = self.linear4(x)
        # 返回结果
        return x

# 3.模型训练
def train(train_dataset, input_dim, output_dim):
    # 1.创建数据加载器DataLoader
    # 参数1:数据集对象;参数2:批量大小;参数3:是否打乱数据
    train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
    # 2.创建神经网络模型
    model = PhonePriceClassifier(input_dim, output_dim)
    model.to(device)
    # 3.定义损失函数
    loss_fn = nn.CrossEntropyLoss()
    # 4.创建优化器对象
    # 优化3:使用Adam优化方法
    optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
    # 优化4:添加学习率调度,optim.lr_scheduler.CosineAnnealingWarmRestarts
    scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=5*len(train_loader), T_mult=1)
    # 5.模型训练
    # 定义变量,记录 训练总轮数
    epochs = 50
    # 定义遍历,记录每轮次的损失和准确率
    train_losses = []
    train_accs = []
    # 遍历轮数
    for epoch in range(epochs):
        # 记录 每次训练的 损失值,训练样本数,预测正确的样本数
        total_loss = 0.
        total_sample = 0
        total_correct = 0
        # 记录训练开始时间
        start_time = time.time()
        # 遍历批次
        for x, y in train_loader:
            # 训练模式
            model.train()
            # 移动数据到GPU
            x = x.to(device)
            y = y.to(device)
            # 前向传播
            y_pred = model(x)
            # 计算损失值
            loss = loss_fn(y_pred, y)
            # 更新总损失
            batch_size = x.shape[0]
            total_loss += loss.item() * batch_size
            # 更新总样本数
            total_sample += batch_size
            # 更新预测正确的样本数
            total_correct += (torch.argmax(y_pred, dim=-1) == y).sum().item()
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播
            loss.backward()
            # 更新参数
            optimizer.step()
            scheduler.step()

        # 打印训练信息
        print(
            f"epoch: {epoch}, loss: {total_loss / total_sample}, acc: {total_correct / total_sample}, time: {time.time() - start_time:.2f}")
        train_losses.append(total_loss / total_sample)
        train_accs.append(total_correct / total_sample)
    # 6.保存模型参数
    os.makedirs("./model", exist_ok=True)
    # print(f"\n\n模型的参数信息: {model.state_dict()}\n\n")
    torch.save(model.state_dict(), "./model/phone_price_classifier.pth")  # 模型文件,后缀名 .pth, .pkl, .pickle 均可
    # 7.返回训练结果
    return train_losses, train_accs

# 可视化训练过程
def plot_train_process(train_losses, train_accs):
    plt.figure(figsize=(12, 4))
    plt.subplot(1, 2, 1)
    plt.plot(train_losses)
    plt.title("训练损失")
    plt.grid(True)
    plt.subplot(1, 2, 2)
    plt.plot(train_accs)
    plt.title("训练准确率")
    plt.grid(True)
    plt.tight_layout()
    plt.show()

# 4.模型测试
@torch.no_grad()
def evaluate(test_dataset, input_dim, output_dim):
    # 1. 创建神经网络分类对象.
    model = PhonePriceClassifier(input_dim, output_dim)
    model.to(device)
    # 2.加载模型参数
    model.load_state_dict(torch.load('./model/phone_price_classifier.pth'))
    # 3.创建数据加载器DataLoader
    # 参数1:数据集对象;参数2:批量大小;参数3:是否打乱数据
    test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
    # 4.记录正确样本的数量
    correct_sample = 0
    total_sample = 0
    # 5.开始测试, 遍历数据加载器的批次数据
    for x, y in test_loader:
        # 切换到测试模式
        model.eval()
        # 张量 迁移 到 GPU
        x = x.to(device)
        y = y.to(device)
        # 6.前向传播,模型预测
        y_pred = model(x)  # 形状(batch_size, 4), 每个类别的输出logits
        # print(f"预测结果y_pred:{y_pred}")
        # 7.计算预测正确的数量
        correct = y_pred.argmax(dim=1) == y
        correct_sample += correct.sum().item()
        total_sample += y.size(0)
    print(f"测试集准确率:{correct_sample / total_sample}")

# 5.测试
if __name__ == '__main__':
    # 1.准备数据集
    train_dataset, test_dataset, input_dim, output_dim = create_dataset()
    # 打印
    print(f"train_dataset: {train_dataset}")
    print(f"test_dataset: {test_dataset}")
    print(f"input_dim: {input_dim}")
    print(f"output_dim: {output_dim}")
    # 2.构建神经网络
    model = PhonePriceClassifier(input_dim, output_dim)
    # 3.统计模型参数
    model.to(device)
    # summary(model,input_size=(input_dim,))
    # 4.模型训练
    train_losses, train_accs = train(train_dataset, input_dim, output_dim)
    plot_train_process(train_losses, train_accs)
    # 5.模型测试
    evaluate(test_dataset, input_dim, output_dim)