【深度学习】03-神经网络 5 (完结篇) 一文讲解 pytroch手机价格神经网络分类与准确率优化案例

手机价格分类数据集已经上传,用户可以自行下载进行训练。

构建数据集

数据共有 2000 条, 其中 1600 条数据作为训练集, 400 条数据用作测试集。 我们使用 sklearn 的数据集划分工作来完成 。并使用 PyTorch 的 TensorDataset 来将数据集构建为 Dataset 对象,方便构造数据集加载对象。

构建分类网络模型

构建全连接神经网络来进行手机价格分类,该网络主要由四个线性层来构建,使用relu激活函数。

网络共有 4个全连接层, 具体信息如下:

1.第一层: 输入为维度为 20, 输出维度为: 128

2.第二层: 输入为维度为 128, 输出维度为: 256

3.第三层: 输入为维度为 256, 输出维度为: 64

4.第四层: 输入为维度为 64, 输出维度为: 4

模型训练

网络编写完成之后,我们需要编写训练函数。所谓的训练函数,指的是输入数据读取、送入网络、计算损失、更新参数 的流程,该流程较为固定。我们使用的是多分类交叉生损失函数、使用 SGD 优化方法。最终,将训练好的模型持久化 到磁盘中。

编写评估函数

复制代码
使用训练好的模型,对未知的样本的进行预测的过程。我们这里使用前面单独划分出来的验证集来进行评估。

第一次训练:

python 复制代码
import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset, DataLoader
from torch import nn
from torchsummary import summary
from torch import optim
import matplotlib.pyplot as plt

# 数据获取与预处理
data = pd.read_csv('./深度学习/data/手机价格预测.csv')  # 从CSV文件加载数据
# print(data.head())  # 打印前几行数据(暂时注释掉)

# 分离特征和目标
x = data.iloc[:, :-1]  # 获取除最后一列之外的所有特征列
y = data.iloc[:, -1]   # 获取最后一列作为目标变量(标签)

# 划分训练集和测试集,测试集比例为20%
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=22)

# 将数据转换为TensorDataset,供PyTorch使用
train_dataset = TensorDataset(torch.tensor(x_train.values, dtype=torch.float32),  # 训练集特征转为Tensor
                              torch.tensor(y_train.values, dtype=torch.int64))    # 训练集标签转为Tensor
test_dataset = TensorDataset(torch.tensor(x_test.values, dtype=torch.float32),    # 测试集特征转为Tensor
                             torch.tensor(y_test.values, dtype=torch.int64))      # 测试集标签转为Tensor

# print(x.shape)
# print(y.shape)

# 定义神经网络模型
class PhoneModel(nn.Module):  # 继承自PyTorch的nn.Module类
    def __init__(self):
        super(PhoneModel, self).__init__()
        # 定义三层全连接层与一个输出层
        self.layer1 = nn.Linear(in_features=20, out_features=128)  # 第一层:输入特征为20,输出为128
        self.layer2 = nn.Linear(in_features=128, out_features=256)  # 第二层:输入128,输出256
        self.layer3 = nn.Linear(in_features=256, out_features=64)   # 第三层:输入256,输出64
        self.out = nn.Linear(in_features=64, out_features=4)        # 输出层:输入64,输出4(假设有4个分类)
        self.dropout = nn.Dropout(p=0.4)  # Dropout层,用于防止过拟合,p=0.4 表示40%的神经元随机失活

    def forward(self, x):
        # 前向传播过程定义
        x = self.layer1(x)           # 输入经过第一层全连接层
        x = self.dropout(x)          # 进行Dropout操作(随机失活)
        x = torch.relu(x)            # ReLU激活函数
        x = self.dropout(torch.relu(self.layer2(x)))  # 第二层:全连接 + Dropout + ReLU激活
        x = torch.relu(self.dropout(self.layer3(x)))  # 第三层:全连接 + Dropout + ReLU激活
        out = self.out(x)            # 输出层
        return out                   # 返回输出

# 实例化模型
model = PhoneModel()
summary(model, input_size=(20,), batch_size=8)  # 打印模型结构和参数数量,input_size为每个样本输入的形状

# 模型训练部分
optimizer = optim.SGD(model.parameters(), lr=0.00001)  # 使用随机梯度下降法(SGD)优化器,学习率设置为0.00001
error = nn.CrossEntropyLoss()  # 交叉熵损失函数,适用于多分类问题

epochs = 100  # 定义训练轮数
train_loss_per_epoch = []  # 用于记录每轮的训练损失

# 开始训练
for epoch in range(epochs):
    dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)  # 创建DataLoader,按批量(batch)喂入数据
    loss_sum = 0  # 用于计算损失总和
    num_batches = 0  # 记录每个epoch的批次数量

    # 遍历每一个小批次
    for x_batch, y_batch in dataloader:
        y_pred = model(x_batch)  # 模型对当前批次数据进行预测
        loss = error(y_pred, y_batch)  # 计算当前批次的损失
        loss_sum += loss.item()  # 累加损失
        num_batches += 1  # 更新批次数量

        optimizer.zero_grad()  # 梯度清零,避免梯度累加
        loss.backward()  # 反向传播计算梯度
        optimizer.step()  # 更新模型参数

    avg_loss = loss_sum / num_batches  # 计算每轮的平均损失
    train_loss_per_epoch.append(avg_loss)  # 将当前轮的损失记录到列表中
    # print(f"Epoch {epoch+1}/{epochs}, Loss: {avg_loss}")  # 打印每轮的损失值(暂时注释掉)

# 画出每轮的训练损失曲线
plt.plot(range(1, epochs + 1), train_loss_per_epoch, label='Train Loss')  # 绘制损失曲线
plt.xlabel('Epochs')  # X轴为训练轮数
plt.ylabel('Loss')    # Y轴为损失值
plt.title('Training Loss Over Epochs')  # 图表标题
plt.legend()  # 添加图例
plt.show()  # 显示图像

# 模型保存
torch.save(model.state_dict(), './深度学习/model/phone_model.pth')  # 保存模型的参数到文件 'model.pth'

# 模型评估
model = PhoneModel()
model.load_state_dict(torch.load('./深度学习/model/phone_model.pth'))  # 加载已经保存的模型参数
test_dataloader = DataLoader(test_dataset, batch_size=8, shuffle=False)  # 创建测试集的DataLoader

correct_predictions = 0  # 记录正确预测的数量
for x_batch, y_batch in test_dataloader:
    y_pred = model(x_batch)  # 模型对测试集进行预测
    out = torch.argmax(y_pred, dim=1)  # 找到预测值中概率最高的类别
    correct_predictions += (y_batch == out).sum().item()  # 统计正确预测的数量

# 计算并打印准确率
accuracy = correct_predictions / len(test_dataset)  # 计算模型的准确率
print(f"Test Accuracy: {accuracy * 100:.2f}%")  # 打印准确率

第一次训练的模型的测试集输出:

python 复制代码
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Linear-1                   [8, 128]           2,688
           Dropout-2                   [8, 128]               0
            Linear-3                   [8, 256]          33,024
           Dropout-4                   [8, 256]               0
            Linear-5                    [8, 64]          16,448
           Dropout-6                    [8, 64]               0
            Linear-7                     [8, 4]             260
================================================================
Total params: 52,420
Trainable params: 52,420
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.05
Params size (MB): 0.20
Estimated Total Size (MB): 0.26
----------------------------------------------------------------

<Figure size 640x480 with 1 Axes>
Test Accuracy: 40.50%
复制代码
epochs = 100  # 定义训练轮数

第二次训练

我们数据进行标准化,使用adam优化器(可以自适应学习率和 修正梯度),我们减少了神经网络层级,增加了训练次数,

python 复制代码
from sklearn.preprocessing import StandardScaler
import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset, DataLoader
from torch import nn
from torchsummary import summary
from torch import optim
import matplotlib.pyplot as plt

# Step 1: 数据获取与预处理
# 从CSV文件加载数据
data = pd.read_csv('../data/手机价格预测.csv')

# 分离特征和目标变量
# X 代表特征 (输入数据),去掉最后一列
# y 代表目标 (标签),最后一列
X = data.iloc[:, :-1]  # 获取除最后一列之外的所有特征列
y = data.iloc[:, -1]   # 获取最后一列作为目标变量(分类标签)

# 标准化特征数据
# 初始化标准化对象,将特征数据进行标准化(零均值,单位方差)
scaler = StandardScaler()
X = scaler.fit_transform(X)  # 标准化特征,使每列特征的均值为0,标准差为1

# 划分训练集和测试集
# 使用 train_test_split 将数据分为80%的训练集和20%的测试集,random_state=22 保持随机性的可重复性
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=22)

# 将数据转换为 TensorDataset,供 PyTorch 使用
# 将训练集和测试集特征和标签转为 PyTorch 的张量格式
train_dataset = TensorDataset(torch.tensor(X_train, dtype=torch.float32), torch.tensor(y_train.values, dtype=torch.int64))
test_dataset = TensorDataset(torch.tensor(X_test, dtype=torch.float32), torch.tensor(y_test.values, dtype=torch.int64))

# Step 2: 定义神经网络模型
# 定义一个多层全连接神经网络,继承自 nn.Module
class PhonePriceModel(nn.Module):
    def __init__(self):
        super(PhonePriceModel, self).__init__()
        # 定义网络结构
        # 第一层:输入特征20,输出128个神经元
        self.layer1 = nn.Linear(in_features=20, out_features=128)
        # 第二层:输入128个神经元,输出64个神经元
        self.layer2 = nn.Linear(in_features=128, out_features=64)
        # 输出层:输入64个神经元,输出4个类别 (假设是 4 分类问题)
        self.out = nn.Linear(in_features=64, out_features=4)
        # Dropout 层,防止过拟合,p=0.4 表示40%的神经元随机失活
        self.dropout = nn.Dropout(p=0.4)

    # 前向传播函数,定义数据如何流经网络层
    def forward(self, x):
        # 第一层:全连接层 -> ReLU 激活 -> Dropout
        x = self.dropout(torch.relu(self.layer1(x)))
        # 第二层:全连接层 -> ReLU 激活 -> Dropout
        x = self.dropout(torch.relu(self.layer2(x)))
        # 输出层:直接输出,不需要激活函数,交叉熵损失会自动处理
        out = self.out(x)
        return out

# 实例化模型
model = PhonePriceModel()
# 打印模型结构和参数数量
summary(model, input_size=(20,), batch_size=16)

# Step 3: 模型训练部分
# 使用 Adam 优化器,设置学习率为 0.0001,betas 参数用于控制一阶和二阶矩估计的加权平均
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001, betas=(0.9, 0.99))
# 使用交叉熵损失函数,适用于多分类问题
error = nn.CrossEntropyLoss()

# 设定训练轮数为1000轮
epochs = 1000
# 用于记录每轮的训练损失值
train_loss_per_epoch = []

# 开始训练循环
for epoch in range(epochs):
    # 使用 DataLoader 加载训练数据,设置批量大小为 8,数据会在每个 epoch 随机打乱
    dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)
    # 初始化每轮的损失总和
    loss_sum = 0
    # 记录批次数量
    num_batches = 0

    # 遍历每个小批次
    for x_batch, y_batch in dataloader:
        # 模型预测当前批次的标签
        y_pred = model(x_batch)
        # 计算当前批次的损失
        loss = error(y_pred, y_batch)
        # 累加损失
        loss_sum += loss.item()
        # 更新批次数量
        num_batches += 1

        # 梯度清零,防止梯度累加
        optimizer.zero_grad()
        # 反向传播计算梯度
        loss.backward()
        # 优化器更新模型参数
        optimizer.step()

    # 计算每轮的平均损失
    avg_loss = loss_sum / num_batches
    # 将当前轮的损失记录到列表中
    train_loss_per_epoch.append(avg_loss)

# Step 4: 画出训练损失曲线
# 绘制每轮的训练损失变化
plt.plot(range(1, epochs + 1), train_loss_per_epoch, label='Train Loss')
plt.xlabel('Epochs')  # 设置 x 轴标签为训练轮数
plt.ylabel('Loss')    # 设置 y 轴标签为损失值
plt.title('Training Loss Over Epochs')  # 设置图表标题
plt.legend()  # 显示图例
plt.show()  # 显示图像

# Step 5: 模型保存
# 保存模型的参数到文件
torch.save(model.state_dict(), '../model/phone_model2.pth')

# Step 6: 模型评估
# 加载模型并进行测试集评估
# 首先重新实例化模型并加载保存的权重
model = PhonePriceModel()
model.load_state_dict(torch.load('../model/phone_model2.pth'))

# 创建测试集的 DataLoader,批量大小为8,测试时不需要打乱数据
test_dataloader = DataLoader(test_dataset, batch_size=8, shuffle=False)

# 初始化正确预测的数量
correct_predictions = 0

# 遍历测试集,进行预测
for x_batch, y_batch in test_dataloader:
    # 模型预测
    y_pred = model(x_batch)
    # 获取预测类别,取每个样本的最大概率对应的类别
    out = torch.argmax(y_pred, dim=1)
    # 计算正确预测的数量
    correct_predictions += (y_batch == out).sum().item()

# 计算并打印准确率
accuracy = correct_predictions / len(test_dataset)  # 准确率为正确预测的样本数量除以总样本数
print(f"Test Accuracy: {accuracy * 100:.2f}%")  # 打印准确率

第二次的输出

python 复制代码
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Linear-1                  [16, 128]           2,688
           Dropout-2                  [16, 128]               0
            Linear-3                   [16, 64]           8,256
           Dropout-4                   [16, 64]               0
            Linear-5                    [16, 4]             260
================================================================
Total params: 11,204
Trainable params: 11,204
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.05
Params size (MB): 0.04
Estimated Total Size (MB): 0.09
----------------------------------------------------------------

<Figure size 640x480 with 1 Axes>
Test Accuracy: 89.50%


第三次训练:
我们改用

复制代码
AdamW, 加入L2正则化防止过拟合,加入nn.BatchNorm1d # 批量归一化层,防止过拟合,训练批次增大改成16,增加平滑度。继续训练1000次
python 复制代码
from sklearn.preprocessing import StandardScaler
import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset, DataLoader
from torch import nn
from torchsummary import summary
from torch import optim
import matplotlib.pyplot as plt
import numpy as np

# Step 1: 数据获取与预处理
# 从CSV文件加载数据,并分离特征和目标变量
data = pd.read_csv('../data/手机价格预测.csv')  # 从文件中读取数据

X = data.iloc[:, :-1]  # 获取特征列,去除最后一列
y = data.iloc[:, -1]   # 获取目标列,即最后一列(分类标签)

# 特征标准化
scaler = StandardScaler()  # 初始化标准化对象
X = scaler.fit_transform(X)  # 对特征进行标准化(均值为0,方差为1)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=22)

# 将训练集和测试集数据转换为Tensor格式,以供PyTorch使用
train_dataset = TensorDataset(torch.tensor(X_train, dtype=torch.float32), torch.tensor(y_train.values, dtype=torch.int64))
test_dataset = TensorDataset(torch.tensor(X_test, dtype=torch.float32), torch.tensor(y_test.values, dtype=torch.int64))

# Step 2: 定义神经网络模型
# 定义一个多层全连接神经网络,继承自nn.Module
class PhonePriceModel(nn.Module):
    def __init__(self):
        super(PhonePriceModel, self).__init__()
        # 定义网络结构,包括全连接层、批量归一化层和Dropout层
        self.layer1 = nn.Linear(in_features=20, out_features=256)  # 输入层,20维特征,256个神经元
        self.bn1 = nn.BatchNorm1d(256)  # 批量归一化层,减少训练过程中的梯度消失问题
        self.layer2 = nn.Linear(in_features=256, out_features=128)  # 隐藏层,256 -> 128
        self.bn2 = nn.BatchNorm1d(128)  # 批量归一化层
        self.layer3 = nn.Linear(in_features=128, out_features=64)  # 隐藏层,128 -> 64
        self.bn3 = nn.BatchNorm1d(64)   # 批量归一化层
        self.out = nn.Linear(in_features=64, out_features=4)  # 输出层,假设有4个分类
        self.dropout = nn.Dropout(p=0.4)  # Dropout层,p=0.4表示40%的神经元在训练过程中随机失活,防止过拟合

    def forward(self, x):
        # 定义前向传播过程
        x = self.dropout(torch.relu(self.bn1(self.layer1(x))))  # 第一层 + 批量归一化 + ReLU激活函数 + Dropout
        x = self.dropout(torch.relu(self.bn2(self.layer2(x))))  # 第二层 + 批量归一化 + ReLU激活函数 + Dropout
        x = self.dropout(torch.relu(self.bn3(self.layer3(x))))  # 第三层 + 批量归一化 + ReLU激活函数 + Dropout
        out = self.out(x)  # 输出层
        return out  # 返回模型的预测结果

# 实例化模型
model = PhonePriceModel()
summary(model, input_size=(20,), batch_size=16)  # 打印模型结构和参数数量

# Step 3: 设置优化器和损失函数
# 使用AdamW优化器,加入weight_decay防止过拟合(L2正则化)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0001, betas=(0.9, 0.99), weight_decay=0.01)

# 使用交叉熵损失函数,适用于多分类任务
error = nn.CrossEntropyLoss()

# 设定早停策略,防止过拟合(如果验证损失10个epoch不下降,就停止训练)
class EarlyStopping:
    def __init__(self, patience=10, delta=0):
        self.patience = patience  # 最大等待的epoch数量
        self.delta = delta  # 损失值变化的最小差异
        self.best_loss = np.inf  # 最佳验证损失初始值为无穷大
        self.counter = 0  # 记录等待的epoch数量
        self.early_stop = False  # 是否停止训练

    def __call__(self, val_loss):
        if val_loss < self.best_loss - self.delta:  # 如果验证损失有显著下降
            self.best_loss = val_loss  # 更新最佳验证损失
            self.counter = 0  # 重置等待计数
        else:
            self.counter += 1  # 否则等待计数+1
            if self.counter >= self.patience:  # 超过最大等待epoch,停止训练
                self.early_stop = True

# Step 4: 模型训练部分
epochs = 1000  # 定义训练轮数
train_loss_per_epoch = []  # 记录每个epoch的训练损失
val_loss_per_epoch = []  # 记录每个epoch的验证损失
early_stopping = EarlyStopping(patience=10, delta=0.01)  # 定义早停策略

for epoch in range(epochs):
    # 训练阶段
    model.train()  # 启用Dropout和BatchNorm
    train_dataloader = DataLoader(train_dataset, batch_size=16, shuffle=True)  # 创建训练数据的DataLoader
    loss_sum = 0  # 累积损失
    num_batches = 0  # 批次数量
    for x_batch, y_batch in train_dataloader:
        y_pred = model(x_batch)  # 前向传播,得到模型预测
        loss = error(y_pred, y_batch)  # 计算损失
        loss_sum += loss.item()  # 累积损失
        num_batches += 1
        optimizer.zero_grad()  # 梯度清零
        loss.backward()  # 反向传播
        optimizer.step()  # 更新模型参数

    avg_loss = loss_sum / num_batches  # 计算每个epoch的平均损失
    train_loss_per_epoch.append(avg_loss)

    # 验证阶段
    model.eval()  # 禁用Dropout和BatchNorm
    val_loss_sum = 0  # 累积验证损失
    num_val_batches = 0
    with torch.no_grad():  # 禁用梯度计算
        for x_batch, y_batch in DataLoader(test_dataset, batch_size=16, shuffle=False):
            y_pred = model(x_batch)  # 前向传播
            val_loss = error(y_pred, y_batch)  # 计算验证集损失
            val_loss_sum += val_loss.item()  # 累积损失
            num_val_batches += 1

    val_avg_loss = val_loss_sum / num_val_batches  # 计算平均验证损失
    val_loss_per_epoch.append(val_avg_loss)

    print(f"Epoch {epoch+1}/{epochs}, Train Loss: {avg_loss:.4f}, Val Loss: {val_avg_loss:.4f}")

    # # 早停检查
    # early_stopping(val_avg_loss)
    # if early_stopping.early_stop:
    #     print(f"Early stopping at epoch {epoch+1}")
    #     break  # 如果触发早停,结束训练

# Step 5: 画出损失曲线
plt.plot(range(1, len(train_loss_per_epoch) + 1), train_loss_per_epoch, label='Train Loss')  # 绘制训练损失
plt.plot(range(1, len(val_loss_per_epoch) + 1), val_loss_per_epoch, label='Validation Loss')  # 绘制验证损失
plt.xlabel('Epochs')  # x轴标签为epoch数量
plt.ylabel('Loss')    # y轴标签为损失值
plt.title('Train and Validation Loss Over Epochs')  # 图标题
plt.legend()  # 显示图例
plt.show()  # 展示图像

# Step 6: 保存模型
torch.save(model.state_dict(), '../model/phone_model_with_early_stopping.pth')  # 保存模型的参数

# Step 7: 模型评估
# 加载保存的模型参数
model.load_state_dict(torch.load('../model/phone_model_with_early_stopping.pth'))
model.eval()  # 评估模式

test_dataloader = DataLoader(test_dataset, batch_size=16, shuffle=False)  
# 创建 DataLoader 对象,用于从测试集加载数据
# test_dataset 是转换为 TensorDataset 格式的测试集
# batch_size=16 表示每次取 16 个样本
# shuffle=False 表示测试数据不需要打乱顺序

correct_predictions = 0  
# 初始化正确预测计数器,初始值为 0

with torch.no_grad():  
    # 禁用梯度计算,因为在测试阶段我们不需要反向传播和梯度计算,可以节省内存和计算时间

    for x_batch, y_batch in test_dataloader:  
        # 遍历测试集的所有批次,每次从 DataLoader 中获取一个批次
        # x_batch 是输入特征,y_batch 是对应的真实标签

        y_pred = model(x_batch)  
        # 使用模型对当前批次的输入特征进行预测,得到预测结果 y_pred

        out = torch.argmax(y_pred, dim=1)  
        # 找到每个样本的预测类别,使用 torch.argmax 找到每个样本在 dim=1 维度上(类别维度)最大概率的索引,即模型预测的类别

        correct_predictions += (y_batch == out).sum().item()  
        # 将预测正确的样本数加到正确预测计数器中
        # (y_batch == out) 会返回一个布尔张量,表示预测是否正确
        # .sum() 计算这个布尔张量中 True(即预测正确)的数量
        # .item() 将结果从张量转换为 Python 的标量并累加到 correct_predictions

accuracy = correct_predictions / len(test_dataset)  
# 计算模型在测试集上的准确率,正确预测的数量除以测试集的总样本数

print(f"Test Accuracy: {accuracy * 100:.2f}%")  
# 打印模型在测试集上的准确率,保留两位小数,并以百分比形式显示

第三次训练输出:

python 复制代码
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Linear-1                  [16, 256]           5,376
       BatchNorm1d-2                  [16, 256]             512
           Dropout-3                  [16, 256]               0
            Linear-4                  [16, 128]          32,896
       BatchNorm1d-5                  [16, 128]             256
           Dropout-6                  [16, 128]               0
            Linear-7                   [16, 64]           8,256
       BatchNorm1d-8                   [16, 64]             128
           Dropout-9                   [16, 64]               0
           Linear-10                    [16, 4]             260
================================================================
Total params: 47,684
Trainable params: 47,684
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.16
Params size (MB): 0.18
Estimated Total Size (MB): 0.35
----------------------------------------------------------------
Epoch 1/1000, Train Loss: 1.4290, Val Loss: 1.3379
Epoch 2/1000, Train Loss: 1.3701, Val Loss: 1.2727
Epoch 3/1000, Train Loss: 1.3121, Val Loss: 1.2192
Epoch 4/1000, Train Loss: 1.2428, Val Loss: 1.1506
.....
Epoch 991/1000, Train Loss: 0.2797, Val Loss: 0.3519
Epoch 992/1000, Train Loss: 0.3372, Val Loss: 0.3389
Epoch 993/1000, Train Loss: 0.2797, Val Loss: 0.3424
Epoch 994/1000, Train Loss: 0.3425, Val Loss: 0.3157
Epoch 995/1000, Train Loss: 0.3567, Val Loss: 0.3393
Epoch 996/1000, Train Loss: 0.3751, Val Loss: 0.3931
Epoch 997/1000, Train Loss: 0.2894, Val Loss: 0.3579
Epoch 998/1000, Train Loss: 0.3667, Val Loss: 0.3269
Epoch 999/1000, Train Loss: 0.3047, Val Loss: 0.3157
Epoch 1000/1000, Train Loss: 0.2907, Val Loss: 0.3280

<Figure size 640x480 with 1 Axes>
Test Accuracy: 87.75%

第四次训练:

本次在第三次的基础上,因为第三次1000次,图片可以看出,梯度后面没啥变化了,所以新增一个设定早停策略,防止过拟合(如果验证损失20个epoch不下降,就停止训练)

python 复制代码
from sklearn.preprocessing import StandardScaler
import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset, DataLoader
from torch import nn
from torchsummary import summary
from torch import optim
import matplotlib.pyplot as plt
import numpy as np

# Step 1: 数据获取与预处理
# 从CSV文件加载数据,并分离特征和目标变量
data = pd.read_csv('../data/手机价格预测.csv')  # 从文件中读取数据

X = data.iloc[:, :-1]  # 获取特征列,去除最后一列
y = data.iloc[:, -1]   # 获取目标列,即最后一列(分类标签)

# 特征标准化
scaler = StandardScaler()  # 初始化标准化对象
X = scaler.fit_transform(X)  # 对特征进行标准化(均值为0,方差为1)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=22)

# 将训练集和测试集数据转换为Tensor格式,以供PyTorch使用
train_dataset = TensorDataset(torch.tensor(X_train, dtype=torch.float32), torch.tensor(y_train.values, dtype=torch.int64))
test_dataset = TensorDataset(torch.tensor(X_test, dtype=torch.float32), torch.tensor(y_test.values, dtype=torch.int64))

# Step 2: 定义神经网络模型
# 定义一个多层全连接神经网络,继承自nn.Module
class PhonePriceModel(nn.Module):
    def __init__(self):
        super(PhonePriceModel, self).__init__()
        # 定义网络结构,包括全连接层、批量归一化层和Dropout层
        self.layer1 = nn.Linear(in_features=20, out_features=256)  # 输入层,20维特征,256个神经元
        self.bn1 = nn.BatchNorm1d(256)  # 批量归一化层,减少训练过程中的梯度消失问题
        self.layer2 = nn.Linear(in_features=256, out_features=128)  # 隐藏层,256 -> 128
        self.bn2 = nn.BatchNorm1d(128)  # 批量归一化层
        self.layer3 = nn.Linear(in_features=128, out_features=64)  # 隐藏层,128 -> 64
        self.bn3 = nn.BatchNorm1d(64)   # 批量归一化层
        self.out = nn.Linear(in_features=64, out_features=4)  # 输出层,假设有4个分类
        self.dropout = nn.Dropout(p=0.4)  # Dropout层,p=0.4表示40%的神经元在训练过程中随机失活,防止过拟合

    def forward(self, x):
        # 定义前向传播过程
        x = self.dropout(torch.relu(self.bn1(self.layer1(x))))  # 第一层 + 批量归一化 + ReLU激活函数 + Dropout
        x = self.dropout(torch.relu(self.bn2(self.layer2(x))))  # 第二层 + 批量归一化 + ReLU激活函数 + Dropout
        x = self.dropout(torch.relu(self.bn3(self.layer3(x))))  # 第三层 + 批量归一化 + ReLU激活函数 + Dropout
        out = self.out(x)  # 输出层
        return out  # 返回模型的预测结果

# 实例化模型
model = PhonePriceModel()
summary(model, input_size=(20,), batch_size=16)  # 打印模型结构和参数数量

# Step 3: 设置优化器和损失函数
# 使用AdamW优化器,加入weight_decay防止过拟合(L2正则化)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0001, betas=(0.9, 0.99), weight_decay=0.01)

# 使用交叉熵损失函数,适用于多分类任务
error = nn.CrossEntropyLoss()

# 设定早停策略,防止过拟合(如果验证损失10个epoch不下降,就停止训练)
class EarlyStopping:
    def __init__(self, patience=10, delta=0):
        self.patience = patience  # 最大等待的epoch数量
        self.delta = delta  # 损失值变化的最小差异
        self.best_loss = np.inf  # 最佳验证损失初始值为无穷大
        self.counter = 0  # 记录等待的epoch数量
        self.early_stop = False  # 是否停止训练

    def __call__(self, val_loss):
        if val_loss < self.best_loss - self.delta:  # 如果验证损失有显著下降
            self.best_loss = val_loss  # 更新最佳验证损失
            self.counter = 0  # 重置等待计数
        else:
            self.counter += 1  # 否则等待计数+1
            if self.counter >= self.patience:  # 超过最大等待epoch,停止训练
                self.early_stop = True

# Step 4: 模型训练部分
epochs = 1000  # 定义训练轮数
train_loss_per_epoch = []  # 记录每个epoch的训练损失
val_loss_per_epoch = []  # 记录每个epoch的验证损失
early_stopping = EarlyStopping(patience=20, delta=0.01)  # 定义早停策略

for epoch in range(epochs):
    # 训练阶段
    model.train()  # 启用Dropout和BatchNorm
    train_dataloader = DataLoader(train_dataset, batch_size=16, shuffle=True)  # 创建训练数据的DataLoader
    loss_sum = 0  # 累积损失
    num_batches = 0  # 批次数量
    for x_batch, y_batch in train_dataloader:
        y_pred = model(x_batch)  # 前向传播,得到模型预测
        loss = error(y_pred, y_batch)  # 计算损失
        loss_sum += loss.item()  # 累积损失
        num_batches += 1
        optimizer.zero_grad()  # 梯度清零
        loss.backward()  # 反向传播
        optimizer.step()  # 更新模型参数

    avg_loss = loss_sum / num_batches  # 计算每个epoch的平均损失
    train_loss_per_epoch.append(avg_loss)

    # 验证阶段
    model.eval()  # 禁用Dropout和BatchNorm
    val_loss_sum = 0  # 累积验证损失
    num_val_batches = 0
    with torch.no_grad():  # 禁用梯度计算
        for x_batch, y_batch in DataLoader(test_dataset, batch_size=16, shuffle=False):
            y_pred = model(x_batch)  # 前向传播
            val_loss = error(y_pred, y_batch)  # 计算验证集损失
            val_loss_sum += val_loss.item()  # 累积损失
            num_val_batches += 1

    val_avg_loss = val_loss_sum / num_val_batches  # 计算平均验证损失
    val_loss_per_epoch.append(val_avg_loss)

    print(f"Epoch {epoch+1}/{epochs}, Train Loss: {avg_loss:.4f}, Val Loss: {val_avg_loss:.4f}")

    # 早停检查
    early_stopping(val_avg_loss)
    if early_stopping.early_stop:
        print(f"Early stopping at epoch {epoch+1}")
        break  # 如果触发早停,结束训练

# Step 5: 画出损失曲线
plt.plot(range(1, len(train_loss_per_epoch) + 1), train_loss_per_epoch, label='Train Loss')  # 绘制训练损失
plt.plot(range(1, len(val_loss_per_epoch) + 1), val_loss_per_epoch, label='Validation Loss')  # 绘制验证损失
plt.xlabel('Epochs')  # x轴标签为epoch数量
plt.ylabel('Loss')    # y轴标签为损失值
plt.title('Train and Validation Loss Over Epochs')  # 图标题
plt.legend()  # 显示图例
plt.show()  # 展示图像

# Step 6: 保存模型
torch.save(model.state_dict(), '../model/phone_model_with_early_stopping2.pth')  # 保存模型的参数

# Step 7: 模型评估
# 加载保存的模型参数
model.load_state_dict(torch.load('../model/phone_model_with_early_stopping2.pth'))
model.eval()  # 评估模式

test_dataloader = DataLoader(test_dataset, batch_size=16, shuffle=False)  
# 创建 DataLoader 对象,用于从测试集加载数据
# test_dataset 是转换为 TensorDataset 格式的测试集
# batch_size=16 表示每次取 16 个样本
# shuffle=False 表示测试数据不需要打乱顺序

correct_predictions = 0  
# 初始化正确预测计数器,初始值为 0

with torch.no_grad():  
    # 禁用梯度计算,因为在测试阶段我们不需要反向传播和梯度计算,可以节省内存和计算时间

    for x_batch, y_batch in test_dataloader:  
        # 遍历测试集的所有批次,每次从 DataLoader 中获取一个批次
        # x_batch 是输入特征,y_batch 是对应的真实标签

        y_pred = model(x_batch)  
        # 使用模型对当前批次的输入特征进行预测,得到预测结果 y_pred

        out = torch.argmax(y_pred, dim=1)  
        # 找到每个样本的预测类别,使用 torch.argmax 找到每个样本在 dim=1 维度上(类别维度)最大概率的索引,即模型预测的类别

        correct_predictions += (y_batch == out).sum().item()  
        # 将预测正确的样本数加到正确预测计数器中
        # (y_batch == out) 会返回一个布尔张量,表示预测是否正确
        # .sum() 计算这个布尔张量中 True(即预测正确)的数量
        # .item() 将结果从张量转换为 Python 的标量并累加到 correct_predictions

accuracy = correct_predictions / len(test_dataset)  
# 计算模型在测试集上的准确率,正确预测的数量除以测试集的总样本数

print(f"Test Accuracy: {accuracy * 100:.2f}%")  
# 打印模型在测试集上的准确率,保留两位小数,并以百分比形式显示

第四次的输出

python 复制代码
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Linear-1                  [16, 256]           5,376
       BatchNorm1d-2                  [16, 256]             512
           Dropout-3                  [16, 256]               0
            Linear-4                  [16, 128]          32,896
       BatchNorm1d-5                  [16, 128]             256
           Dropout-6                  [16, 128]               0
            Linear-7                   [16, 64]           8,256
       BatchNorm1d-8                   [16, 64]             128
           Dropout-9                   [16, 64]               0
           Linear-10                    [16, 4]             260
================================================================
Total params: 47,684
Trainable params: 47,684
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.16
Params size (MB): 0.18
Estimated Total Size (MB): 0.35
----------------------------------------------------------------
Epoch 1/1000, Train Loss: 1.4297, Val Loss: 1.3407
Epoch 2/1000, Train Loss: 1.3686, Val Loss: 1.2921
Epoch 3/1000, Train Loss: 1.3004, Val Loss: 1.2270
Epoch 4/1000, Train Loss: 1.2460, Val Loss: 1.1759
Epoch 5/1000, Train Loss: 1.1892, Val Loss: 1.1108
。。。。。。
Epoch 85/1000, Train Loss: 0.5395, Val Loss: 0.3130
Epoch 86/1000, Train Loss: 0.4959, Val Loss: 0.3044
Epoch 87/1000, Train Loss: 0.4969, Val Loss: 0.3064
Epoch 88/1000, Train Loss: 0.5360, Val Loss: 0.3087
Epoch 89/1000, Train Loss: 0.5321, Val Loss: 0.3122
Early stopping at epoch 89

<Figure size 640x480 with 1 Axes>
Test Accuracy: 91.75%

后续如果还想提升的话,可以修改自己的超参数慢慢的炼丹。我们只需要符合需求即可。

相关推荐
拉拉拉拉拉拉拉马19 小时前
感知机(Perceptron)算法详解
人工智能·python·深度学习·算法·机器学习
最晚的py19 小时前
参数初始化的方式
深度学习·初始化参数
jay神19 小时前
基于YOLOv8的行人车辆检测系统
人工智能·深度学习·yolo·计算机视觉·毕业设计
囊中之锥.20 小时前
《深度学习》CUDA安装配置、pytorch库、torchvision库、torchaudio库安装
人工智能·pytorch·深度学习
ttttming20 小时前
day33 简单神经网络
人工智能·深度学习·神经网络
凌峰的博客21 小时前
基于深度学习的图像安全与隐私保护研究方向调研(中)
人工智能·深度学习·安全
上天夭1 天前
模型训练篇
人工智能·深度学习·机器学习
Blossom.1181 天前
AI编译器实战:从零手写算子融合与自动调度系统
人工智能·python·深度学习·机器学习·flask·transformer·tornado
泰迪智能科技011 天前
分享图书推荐 | 数字图像处理实战
人工智能·深度学习·计算机视觉
Rabbit_QL1 天前
【深度学习原理】数值稳定性(二):梯度是如何在深度网络中消失与爆炸的
人工智能·深度学习