云计算百科
云计算领域专业知识百科平台

手机价格多分类_全连接神经网络教程

【PyTorch】三层全连接神经网络实现手机价格多分类(附完整代码 + 训练结果)

本文用 PyTorch 搭建一个三层全连接神经网络,对手机价格进行四分类预测。代码覆盖了数据处理 → 模型训练 → 模型评估的完整流程,并把训练好的模型保存到本地、评估时再加载回来。全程 CPU 即可运行,适合入门教学。


一、任务背景

给定 2000 条手机配置数据(电池、内存、RAM、摄像头、屏幕等 20 个特征),预测该手机属于哪一个价格档位(price_range,共 4 类:0 / 1 / 2 / 3)。

这是一个典型的多分类任务。本文用一个 3 层全连接网络(两个隐藏层 + 一个输出层),隐藏层使用 ReLU 激活函数,输出层配合 CrossEntropyLoss 完成分类。


二、数据集介绍

数据集来自 Kaggle 经典的 Mobile Price Classification,共 2000 行 × 21 列,即 20 个特征 + 1 个目标变量。

2.1 连续数值特征(14 个)

特征含义取值范围
battery_power 电池容量 (mAh) 501 – 1998
clock_speed 处理器主频 (GHz) 0.5 – 3.0
fc 前置摄像头 (MP) 0 – 19
pc 后置摄像头 (MP) 0 – 20
int_memory 内存 (GB) 2 – 64
ram 运行内存 (MB) 256 – 3998
m_dep 机身厚度 (cm) 0.1 – 1.0
mobile_wt 重量 (g) 80 – 200
n_cores 处理器核心数 1 – 8
px_height 屏幕高度 (px) 0 – 1960
px_width 屏幕宽度 (px) 500 – 1998
sc_h 屏幕高度 (cm) 5 – 19
sc_w 屏幕宽度 (cm) 0 – 18
talk_time 通话时长 (h) 2 – 20

2.2 二值特征(6 个,取值 0/1)

blue(蓝牙)、dual_sim(双卡)、four_g(4G)、three_g(3G)、touch_screen(触摸屏)、wifi。

2.3 目标变量分布

price_range 四个类别各 500 条,恰好 25% 完全均衡,因此建模时无需处理类别不平衡,直接用准确率评估即可。

⚠️ 注意:各特征量纲差异极大(ram 最大 3998,而 m_dep 只有 0.1~1.0),训练前必须做标准化,否则大数值特征会在梯度下降中主导权重更新。


三、环境准备

依赖版本说明
Python 3.8+ 本文用 3.12
PyTorch 2.x CPU 版即可
scikit-learn 1.x 划分数据集、标准化、评估
pandas / numpy 数据读取与处理

安装命令:

pip install torch scikit-learn pandas numpy


四、项目结构

day_05/
├── data/
│ └── 手机价格预测.csv # 数据集
├── model/ # 训练后自动生成
│ └── phone_price_net.pt # 保存的模型权重
└── model.py # 本文完整代码


五、网络结构设计

输入层(20) → 全连接层(64, ReLU) → 全连接层(32, ReLU) → 输出层(4)

  • 输入维度:20(特征数)
  • 隐藏层 1:64 个神经元,ReLU 激活
  • 隐藏层 2:32 个神经元,ReLU 激活
  • 输出维度:4(对应 4 个价格档位)

输出层不加激活函数:nn.CrossEntropyLoss 内部已经集成了 softmax,所以输出层直接接线性层即可,标签传原始类别编号(long 类型)。

超参数:学习率 1e-3、Adam 优化器、batch_size=64、epochs=50、测试集比例 20%、随机种子 42。


六、完整代码

"""
手机价格多分类 —— 全连接神经网络(PyTorch)

流程:数据处理 -> 模型训练 -> 模型评估
结构:3 层全连接网络(20 -> 64 -> 32 -> 4),隐藏层用 ReLU 激活
说明:
– 数据从 data/ 目录读取
– 训练好的模型保存到 model/ 目录
– 评估阶段从 model/ 目录加载模型
运行:python model.py(路径已用 __file__ 自适应,任意目录均可运行)
"""

import os

import matplotlib
matplotlib.use("Agg") # 无界面后端:直接保存图片,不弹出窗口
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report
from torch.utils.data import DataLoader, TensorDataset

# ================= 路径与超参数 =================
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # model.py 所在目录
DATA_PATH = os.path.join(BASE_DIR, "data", "手机价格预测.csv")
MODEL_DIR = os.path.join(BASE_DIR, "model")
MODEL_PATH = os.path.join(MODEL_DIR, "phone_price_net.pt")

INPUT_DIM = 20 # 输入特征数
HIDDEN_1 = 64 # 隐藏层 1 神经元数
HIDDEN_2 = 32 # 隐藏层 2 神经元数
OUTPUT_DIM = 4 # 类别数(price_range: 0/1/2/3)

LEARNING_RATE = 1e-3
EPOCHS = 50
BATCH_SIZE = 64
TEST_SIZE = 0.2
RANDOM_SEED = 42

# ================= 1. 网络结构 =================
class PhonePriceNet(nn.Module):
"""三层全连接网络:fc1 -> fc2 -> fc3,隐藏层用 ReLU 激活"""

def __init__(self, input_dim, hidden_1, hidden_2, output_dim):
super().__init__()
self.fc1 = nn.Linear(input_dim, hidden_1) # 第 1 层
self.fc2 = nn.Linear(hidden_1, hidden_2) # 第 2 层
self.fc3 = nn.Linear(hidden_2, output_dim) # 第 3 层(输出层)

def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x) # 输出层不加激活,交给 CrossEntropyLoss
return x

# ================= 2. 数据处理 =================
def prepare_data():
"""读取 CSV、切分特征/标签、标准化、划分训练/测试集并封装为 DataLoader"""
df = pd.read_csv(DATA_PATH)

X = df.iloc[:, :1].values.astype(np.float32) # 前 20 列:特征
y = df.iloc[:, 1].values.astype(np.int64) # 最后 1 列:标签

# 分层划分:保证训练/测试集中各类别比例一致
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=y
)

# 标准化:只用训练集计算均值/标准差,再作用到测试集(避免数据泄露)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# 转成 Tensor 并封装为 DataLoader
train_loader = DataLoader(
TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train)),
batch_size=BATCH_SIZE, shuffle=True,
)
test_loader = DataLoader(
TensorDataset(torch.from_numpy(X_test), torch.from_numpy(y_test)),
batch_size=BATCH_SIZE, shuffle=False,
)
return train_loader, test_loader

# ================= 3. 模型训练 =================
def train_model(train_loader):
"""训练模型,并把训练好的权重保存到 model/ 目录"""
torch.manual_seed(RANDOM_SEED) # 固定随机种子,保证结果可复现

model = PhonePriceNet(INPUT_DIM, HIDDEN_1, HIDDEN_2, OUTPUT_DIM)
criterion = nn.CrossEntropyLoss() # 多分类损失
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)

loss_history = [] # 记录每个 epoch 的平均损失
for epoch in range(1, EPOCHS + 1):
model.train()
total_loss = 0.0
for xb, yb in train_loader:
optimizer.zero_grad() # 清空梯度
pred = model(xb) # 前向传播
loss = criterion(pred, yb) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
total_loss += loss.item() * xb.size(0)
avg_loss = total_loss / len(train_loader.dataset)
loss_history.append(avg_loss) # 记录本轮损失

if epoch % 10 == 0:
print(f" Epoch {epoch:3d}/{EPOCHS} loss = {avg_loss:.4f}")

# 保存模型
os.makedirs(MODEL_DIR, exist_ok=True)
torch.save(model.state_dict(), MODEL_PATH)
print(f" 模型已保存 -> {MODEL_PATH}")

# 绘制并保存 loss 曲线
plot_loss_curve(loss_history)
return model

# ================= 3.5 绘制损失曲线 =================
def plot_loss_curve(loss_history):
"""绘制训练损失曲线并保存为 PNG(model 目录下 loss_curve.png)"""
plt.figure(figsize=(8, 5))
plt.plot(range(1, len(loss_history) + 1), loss_history,
marker="o", markersize=3, linewidth=1.5, color="#1f77b4")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.title("Training Loss Curve")
plt.grid(True, linestyle="–", alpha=0.5)
plt.tight_layout()
loss_path = os.path.join(MODEL_DIR, "loss_curve.png")
plt.savefig(loss_path, dpi=150)
plt.close()
print(f" loss 曲线已保存 -> {loss_path}")

# ================= 4. 模型评估 =================
def evaluate_model(test_loader):
"""从 model/ 目录加载训练好的模型,在测试集上评估"""
# 先重建同结构的网络,再加载保存的权重
model = PhonePriceNet(INPUT_DIM, HIDDEN_1, HIDDEN_2, OUTPUT_DIM)
model.load_state_dict(torch.load(MODEL_PATH, weights_only=True))
model.eval()

y_true, y_pred = [], []
with torch.no_grad(): # 评估阶段不计算梯度
for xb, yb in test_loader:
pred = model(xb)
y_pred.extend(torch.argmax(pred, dim=1).tolist())
y_true.extend(yb.tolist())

acc = accuracy_score(y_true, y_pred)
print(f" 测试集准确率 = {acc * 100:.2f}%")
print("\\n 分类报告:")
print(classification_report(y_true, y_pred))
return acc

# ================= 主函数 =================
def main():
print("=" * 56)
print("手机价格多分类 —— 三层全连接神经网络")
print("=" * 56)

print("\\n[1/3] 数据处理 …")
train_loader, test_loader = prepare_data()
print(f" 训练样本 {len(train_loader.dataset)} 条,测试样本 {len(test_loader.dataset)} 条")

print("\\n[2/3] 模型训练 …")
train_model(train_loader)

print("\\n[3/3] 模型评估 …")
evaluate_model(test_loader)

print("\\n完成!")

if __name__ == "__main__":
main()


七、代码详解

7.1 数据处理 prepare_data()

  • df.iloc[:, :-1] 取前 20 列作特征,df.iloc[:, -1] 取最后一列作标签;
  • 标签转成 np.int64(分类标签必须是整数,配合 CrossEntropyLoss);
  • train_test_split(…, stratify=y) 分层抽样,保证训练/测试集里 4 个类别的比例都与原始数据一致;
  • StandardScaler 标准化:只用训练集 fit,再 transform 到测试集,避免数据泄露;
  • 最后用 TensorDataset + DataLoader 封装成可批量迭代的加载器。

7.2 模型训练 train_model()

标准五步训练循环:

optimizer.zero_grad() # 1. 清空梯度
pred = model(xb) # 2. 前向传播
loss = criterion(pred, yb) # 3. 计算损失
loss.backward() # 4. 反向传播
optimizer.step() # 5. 更新参数

损失按样本数加权平均(loss.item() * xb.size(0)),避免最后一个 batch 样本不足导致的偏差。训练完成后用 torch.save 把 state_dict() 存到 model/ 目录。

7.3 模型评估 evaluate_model()

  • 重建同结构网络,再用 load_state_dict 加载保存的权重 —— 这就是"模型保存/加载"的标准用法;
  • model.eval() 关闭 Dropout/BN 等训练专用行为;
  • torch.no_grad() 关闭梯度计算,节省显存、加速推理;
  • torch.argmax(pred, dim=1) 取出每行得分最高的类别索引作为预测结果;
  • 输出准确率 + classification_report(各类别的精确率、召回率、F1)。

7.4 主函数 main()

用 if __name__ == "__main__" 保护入口,依次调用三个函数,形成清晰的「三段式」流程。


八、运行结果与分析

训练样本 1600 条,测试样本 400 条

Epoch 10/50 loss = 0.2512
Epoch 20/50 loss = 0.0849
Epoch 30/50 loss = 0.0402
Epoch 40/50 loss = 0.0216
Epoch 50/50 loss = 0.0126

测试集准确率 = 93.50%

损失从 0.2512 一路下降到 0.0126,收敛良好。测试集准确率 93.50%,各类别 F1 都在 0.92 以上,模型泛化能力强。

训练损失曲线:
请添加图片描述

分类报告:

类别precisionrecallf1-score样本数
0 0.94 0.95 0.95 100
1 0.93 0.91 0.92 100
2 0.94 0.91 0.92 100
3 0.93 0.97 0.95 100
macro avg 0.94 0.94 0.93 400

四个类别表现均衡,没有出现某一类被"偏爱"或"忽略"的情况。


九、关键知识点总结

  • 分类 vs 回归:多分类任务要用 CrossEntropyLoss + 输出维度等于类别数,标签用整数;不要用 MSELoss(那是回归)。
  • 输出层激活:CrossEntropyLoss 内置 softmax,输出层无需手动加激活函数。
  • 标准化:特征量纲差异大时必须标准化,且 fit 只能用训练集。
  • 分层抽样:stratify=y 保证类别均衡划分。
  • 模型保存/加载:torch.save(model.state_dict(), path) 保存,load_state_dict(torch.load(path)) 加载,加载前要先重建同结构网络。
  • 评估模式:推理时 model.eval() + torch.no_grad()。
  • 路径自适应:用 os.path.dirname(os.path.abspath(__file__)) 定位文件目录,避免相对路径依赖运行位置。

  • 十、结语

    本文用约 160 行代码完成了一个手机价格四分类任务,完整覆盖了数据预处理、网络搭建、训练、保存、加载与评估。代码结构清晰、注释齐全,可直接复制运行,也可以在此基础上修改隐藏层维度、优化器、学习率等超参数做对比实验。

    如果对你有帮助,欢迎点赞、收藏、评论交流 😊

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 手机价格多分类_全连接神经网络教程
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!