文章目录
-
- [1. 需求分析](#1. 需求分析)
- [2. 导入所需工具包](#2. 导入所需工具包)
- [3. 构建数据集](#3. 构建数据集)
- [4. 构建分类网络模型](#4. 构建分类网络模型)
- [5. 训练模型](#5. 训练模型)
- [6. 模型训练](#6. 模型训练)
- [7. 评估模型](#7. 评估模型)
- [8. 模型优化](#8. 模型优化)
学习目标:
- 掌握构建分类模型流程
- 动手实践整个过程
1. 需求分析
小明创办了一家手机公司,他不知道如何估算手机产品的价格。为了解决这个问题,他收集了多家公司的手机销售数据。该数据为二手手机的各个性能的数据,最后根据这些性能得到4个价格区间,作为这些二手手机售出的价格区间。主要包括:
battery_power | 电池一次可存储的电量,单位:毫安/时 |
---|---|
blue | 是否有蓝牙 |
clock_speed | 微处理器执行指令的速度 |
dual_sim | 是否支持双卡 |
fc | 前置摄像头百万像素 |
four_g | 是否有4G |
int_memory | 内存(GB) |
m_dep | 移动深度(cm) |
mobile_wt | 手机重量 |
n_cores | 处理器内核数 |
pc | 主摄像头百万像素 |
px_height | 像素分辨率高度 |
px_width | 像素分辨率宽度 |
ram | 随机存储器(兆字节) |
sc_h | 手机屏幕高度(cm) |
sc_w | 手机屏幕宽度(cm) |
talk_time | 一次充电持续时长 |
three_g | 是否有3G |
touch_screen | 是否有触屏控制 |
wifi | 是否能连wifi |
price_range | 价格区间(0,1,2,3) |
我们需要帮助小明找出手机的功能(例如:RAM等)与其售价之间的某种关系。我们可以使用机器学习的方法来解决这个问题,也可以构建一个全连接的网络。
需要注意的是: 在这个问题中,我们不需要预测实际价格,而是一个价格范围,它的范围使用 0、1、2、3 来表示,所以该问题也是一个分类问题。接下来我们还是按照四个步骤来完成这个任务:
-
准备训练集数据
-
构建要使用的模型
-
模型训练
-
模型预测评估
2. 导入所需工具包
python
# 导入相关模块
import torch
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import time
3. 构建数据集
数据共有 2000 条, 其中 1600 条数据作为训练集, 400 条数据用作测试集。 我们使用 sklearn 的数据集划分工作来完成。并使用 PyTorch 的 TensorDataset 来将数据集构建为 Dataset 对象,方便构造数据集加载对象。
python
#1. 导入相关模块
import torch
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import time
# 构建数据集
def load_dataset():
# 使用pandas 读取数据
data = pd.read_csv('data/手机价格预测.csv')
# 特征值和目标值
x,y = data.iloc[:,:-1],data.iloc[:,-1]
# 类型转换:特征值,目标值
x = x.astype(np.float32)
y = y.astype(np.int64)
# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=88)
# 构建数据集,转换为pytorch格式
train_dataset = TensorDataset(torch.from_numpy(x_train.values), torch.from_numpy(y_train.values))
test_dataset = TensorDataset(torch.from_numpy(x_test.values), torch.from_numpy(y_test.values))
#返回结果
return train_dataset, test_dataset,x_train.shape[1],len(np.unique(y))
if __name__ == '__main__':
train_dataset, test_dataset,input_dim,class_num = load_dataset()
print("输入特征数:",input_dim)
print("分类个数:",class_num)
输出结果为:
tex
输入特征数: 20
分类个数: 4
4. 构建分类网络模型
构建全连接神经网络来进行手机价格分类,该网络主要由三个线性层来构建,使用relu
激活函数。
网络共有 3 个全连接层, 具体信息如下:
- 第一层: 输入为维度为 20, 输出维度为: 128
- 第二层: 输入为维度为 128, 输出维度为: 256
- 第三层: 输入为维度为 256, 输出维度为: 4
python
# 构建网络模型
class PhonePriceModel(nn.Module):
def __init__(self,input_dim,output_dim):
super(PhonePriceModel, self).__init__()
# 1. 第一层: 输入为维度为 20, 输出维度为: 128
self.linear1 = nn.Linear(input_dim, 128)
# 2. 第二层: 输入为维度为 128, 输出维度为: 256
self.linear2 = nn.Linear(128, 256)
# 3. 第三层: 输入为维度为 256, 输出维度为: 4
self.linear3 = nn.Linear(256, output_dim)
def forward(self, x):
# 前向传播过程
x = torch.relu(self.linear1(x))
x = torch.relu(self.linear2(x))
output = self.linear3(x)
# 获取数据结果
return output
if __name__ == '__main__':
train_dataset, test_dataset,input_dim,class_num = load_dataset()
print("输入特征数:",input_dim)
print("分类个数:",class_num)
# 模型实例化
model = PhonePriceModel(input_dim,class_num)
5. 训练模型
网络编写完成之后,我们需要编写训练函数。所谓的训练函数,指的是输入数据读取、送入网络、计算损失、更新参数的流程,该流程较为固定。我们使用的是多分类交叉生损失函数、使用 SGD 优化方法。最终,将训练好的模型持久化到磁盘中。
python
# 模型训练过程
def train(train_dataset,input_dim,class_num):
# 固定随机数种子
torch.manual_seed(0)
# 初始化模型
model = PhonePriceModel(input_dim,class_num)
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化方法
optimizer = optim.SGD(model.parameters(), lr=1e-3)
# 训练轮数
num_epochs = 50
# 遍历轮数
for epoch_idx in range(num_epochs):
# 初始化数据加载器
dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)
# 训练时间
start = time.time()
# 计算损失
total_loss = 0.0
total_num = 1
# 遍历每个batch数据进行处理
for x,y in dataloader:
# 将数据送入网络中进行预测
output = model(x)
# 计算损失
loss = criterion(output, y)
#梯度清零
optimizer.zero_grad()
# 方向传播
loss.backward()
# 参数更新
optimizer.step()
# 损失计算
total_num += 1
total_loss += loss.item()
# 打印损失变换结果
print('epoch: %4s loss: %.2f, time: %.2fs' % (epoch_idx + 1, total_loss / total_num, time.time() - start))
# 保存模型
torch.save(model.state_dict(), 'model/phone.ptn')
6. 模型训练
python
if __name__ == '__main__':
train_dataset, test_dataset,input_dim,class_num = load_dataset()
print("输入特征数:",input_dim)
print("分类个数:",class_num)
# 模型训练过程
train(train_dataset,input_dim,class_num)
输出结果:
tex
epoch: 1 loss: 13.31, time: 0.25s
epoch: 2 loss: 0.96, time: 0.24s
epoch: 3 loss: 0.90, time: 0.24s
epoch: 4 loss: 0.89, time: 0.25s
epoch: 5 loss: 0.86, time: 0.26s
...
epoch: 46 loss: 0.68, time: 0.25s
epoch: 47 loss: 0.69, time: 0.26s
epoch: 48 loss: 0.68, time: 0.28s
epoch: 49 loss: 0.69, time: 0.24s
epoch: 50 loss: 0.69, time: 0.24s
7. 评估模型
使用训练好的模型,对未知的样本的进行预测的过程。我们这里使用前面单独划分出来的验证集来进行评估。
python
# 4 评估模型
def test(test_dataset,input_dim,class_num):
# 加载模型和训练好的网络参数
model = PhonePriceModel(input_dim,class_num)
model.load_state_dict(torch.load('model/phone.ptn',weights_only=False))
# 构建加载器
dataloader = DataLoader(test_dataset, batch_size=8, shuffle=True)
# 评估测试集
correct = 0
# 遍历测试集中的数据
for x,y in dataloader:
# 将其送入网络中
output = model(x)
# 获取类别结果
y_pred = torch.argmax(output, dim=1)
# 获取预测正确的个数
correct += (y_pred == y).sum().sum()
# 求预测精度
print('Acc: %.5f' % (correct.item() / len(test_dataset)))
if __name__ == '__main__':
train_dataset, test_dataset,input_dim,class_num = load_dataset()
print("输入特征数:",input_dim)
print("分类个数:",class_num)
# 评估模型
test(test_dataset,input_dim,class_num)
输出结果:
tex
Acc: 0.62500
8. 模型优化
我们前面的网络模型在测试集的准确率为: 0.54750, 我们可以通过以下方面进行调优:
- 优化方法由 SGD 调整为 Adam
- 学习率由 1e-3 调整为 1e-4
- 对数据数据进行标准化
- Dropout 正则化
- 调整训练轮次
python
# 使用Adam方法优化网络
#1. 导入相关模块
import torch
from tensorboard import summary
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import time
#2. 构建数据集
def load_dataset():
# 使用pandas 读取数据
data = pd.read_csv('data/手机价格预测.csv')
# 特征值和目标值
x,y = data.iloc[:,:-1],data.iloc[:,-1]
# 类型转换:特征值,目标值
x = x.astype(np.float32)
y = y.astype(np.int64)
# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=88)
# 数据标准化
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.fit_transform(x_test)
x_train = torch.tensor(x_train,dtype=torch.float32)
x_test = torch.tensor(x_test,dtype=torch.float32)
# 构建数据集,转换为pytorch格式
train_dataset = TensorDataset(x_train, torch.from_numpy(y_train.values))
test_dataset = TensorDataset(x_test, torch.from_numpy(y_test.values))
#返回结果
return train_dataset, test_dataset,x_train.shape[1],len(np.unique(y))
#2. 构建网络模型
class PhonePriceModel(nn.Module):
def __init__(self,input_dim,output_dim,p_dropout=0.4):
super(PhonePriceModel, self).__init__()
# 第一层: 输入为维度为 20, 输出维度为: 128
self.linear1 = nn.Linear(input_dim, 128)
# Dropout优化
self.dropout = nn.Dropout(p_dropout)
# 第二层: 输入为维度为 128, 输出维度为: 256
self.linear2 = nn.Linear(128, 256)
# Dropout优化
self.dropout = nn.Dropout(p_dropout)
# 第三层: 输入为维度为 256, 输出维度为: 4
self.linear3 = nn.Linear(256, output_dim)
def forward(self, x):
# 前向传播过程
x = torch.relu(self.linear1(x))
x = torch.relu(self.linear2(x))
output = self.linear3(x)
# 获取数据结果
return output
# 3. 模型训练过程
def train(train_dataset,input_dim,class_num):
# 固定随机数种子
torch.manual_seed(0)
# 初始化模型
model = PhonePriceModel(input_dim,class_num)
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化方法
# optimizer = optim.SGD(model.parameters(), lr=1e-3)
# Adam优化方法 调整学习率为 lr=1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, betas=(0.9, 0.99))
# 训练轮数 100 - 0.9075 50 - 0.9125
num_epochs = 50
# 遍历轮数
for epoch_idx in range(num_epochs):
# 初始化数据加载器
dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)
# 训练时间
start = time.time()
# 计算损失
total_loss = 0.0
total_num = 1
# 遍历每个batch数据进行处理
for x,y in dataloader:
# 将数据送入网络中进行预测
output = model(x)
# 计算损失
loss = criterion(output, y)
#梯度清零
optimizer.zero_grad()
# 方向传播
loss.backward()
# 参数更新
optimizer.step()
# 损失计算
total_num += 1
total_loss += loss.item()
# 打印损失变换结果
print('epoch: %4s loss: %.2f, time: %.2fs' % (epoch_idx + 1, total_loss / total_num, time.time() - start))
# 保存模型
torch.save(model.state_dict(), 'model/phone2.ptn')
# 4 评估模型
def test(test_dataset,input_dim,class_num):
# 加载模型和训练好的网络参数
model = PhonePriceModel(input_dim,class_num)
model.load_state_dict(torch.load('model/phone2.ptn',weights_only=False))
# 构建加载器
dataloader = DataLoader(test_dataset, batch_size=8, shuffle=True)
# 评估测试集
correct = 0
# 遍历测试集中的数据
for x,y in dataloader:
# 将其送入网络中
output = model(x)
# 获取类别结果
y_pred = torch.argmax(output, dim=1)
# 获取预测正确的个数
correct += (y_pred == y).sum().sum()
# 求预测精度
print('Acc: %.5f' % (correct.item() / len(test_dataset)))
if __name__ == '__main__':
train_dataset, test_dataset,input_dim,class_num = load_dataset()
print("输入特征数:",input_dim)
print("分类个数:",class_num)
# 模型训练
train(train_dataset,input_dim,class_num)
test(test_dataset,input_dim,class_num)
这里我们调整 Adam方法优化梯度下降,学习率调整为1e-4
,样本数据采用标准化处理。采用Dropout正则化。最后输出结果:
tex
Acc: 0.91250