ANN案列_手机价格分类_需求介绍
一、需求分析
小明创办了一家手机公司,他不知道如何估算手机产品的价格。为了解决这个问题,他收集了多家公司的手机销售数据。该数据为二手手机的各个性能的数据,最后根据这些性能得到4个价格区间,作为这些二手手机售出的价格区间。主要包括:

此项目不使用机器学习(也可以):机器学习需要提取特征,太慢了
我们使用神经网络来做
我们需要帮助小明找出手机的功能(例如:RAM等)与其售价之间的某种关系。我们可以使用机器学习的方法来解决这个问题,也可以构建一个全连接的网络。
需要注意的是: 在这个问题中,我们不需要预测实际价格,而是一个价格范围,它的范围使用 0、1、2、3 来表示,所以该问题也是一个分类问题。接下来我们还是按照四个步骤来完成这个任务:
(1)准备训练集数据
(2)构建要使用的模型
(3)模型训练
(4)模型预测评估
二、导入所需的工具包
# 导包
import torch # PyTorch框架, 封装了张量的各种操作
from torch.utils.data import TensorDataset # 数据集对象. 数据 -> Tensor -> 数据集 -> 数据加载器
from torch.utils.data import DataLoader # 数据加载器.
import torch.nn as nn # neural network, 封装了神经网络的各种操作
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 # 时间模块
三、构建数据集
# todo 1. 定义函数, 构建数据集.
def create_dataset():
# 1. 加载csv文件数据集.
data = pd.read_csv('./data/手机价格预测.csv')
# print(f'data: {data.head()}')
# print(f'data: {data.shape}') # (2000, 21)
# 2. 获取x特征列 和 y标签列.
x, y = data.iloc[:, :-1], data.iloc[:, -1]
# print(f'x: {x.head()}, {x.shape}') # (2000, 20)
# print(f'y: {y.head()}, {y.shape}') # (2000, )
# 3. 把特征列转成浮点型,支持梯度计算。
x = x.astype(np.float32)
# print(f'x: {x.head()}, {x.shape}') # (2000, 20)
# 4. 切分训练集和测试集.
# 参1: 特征, 参2: 标签, 参3: 测试集所占比例, 参4: 随机种子, 参5: 样本的分布(即: 参考y的类别进行抽取数据)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=3, stratify=y)
# 5. 把数据集封装成 张量数据集. 思路: 数据 -> 张量Tensor -> 数据集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))
# print(f'train_dataset: {train_dataset}, test_dataset: {test_dataset}')
# 6. 返回结果 20(充当 输入特征数) 4(充当 输出标签数)
return train_dataset, test_dataset, x_train.shape[1], len(np.unique(y))
四、获取数据的结果
# todo 5. 测试
if __name__ == '__main__':
train_dataset, test_dataset, input_dim, output_dim = create_dataset()
print(f'训练集 数据集对象: {train_dataset}')
print(f'测试集 数据集对象: {test_dataset}')
print(f'输入特征数: {input_dim}') # 20
print(f'输出标签数: {output_dim}') # 4
五、构建分类网络模型
构建全连接神经网络来进行手机价格分类,该网络主要由三个线性层来构建,使用relu激活函数。
网络共有 3 个全连接层, 具体信息如下:
第一层: 输入为维度为 20, 输出维度为: 128
第二层: 输入为维度为 128, 输出维度为: 256
第三层: 输入为维度为 256, 输出维度为: 4
# todo 2. 搭建神经网络.
class PhonePriceModel(nn.Module):
# 1. 在init魔法方法中, 初始化父类成员, 及搭建神经网络.
def __init__(self, input_dim, output_dim): # 输入: 20, 输出: 4
# 1.1 初始化父类成员.
super().__init__()
# 1.2 搭建神经网络.
# 隐藏层1
self.linear1 = nn.Linear(input_dim, 128)
# 隐藏层2
self.linear2 = nn.Linear(128, 256)
# 输出层
self.output = nn.Linear(256, output_dim)
# 2. 定义前向传播方法 forward()
def forward(self, x):
# 2.1 隐藏层1: 加权求和 + 激活函数(relu)
# x = self.linear1(x)
# x = torch.relu(x)
x = torch.relu(self.linear1(x))
# 2.2 隐藏层2: 加权求和 + 激活函数(relu)
x = torch.relu(self.linear2(x))
# 2.3 输出层: 加权求和 + 激活函数(softmax) -> 这里只需要做 加权求和.
# 正常写法, 但是不需要, 后续用 多分类交叉熵损失函数 CrossEntropyLoss() 替代
# CrossEntropyLoss() = softmax() + 损失计算
# x = torch.softmax(self.output(x), dim=1)
x = self.output(x)
# 2.4 返回处理结果
return x
六、模型训练
网络编写完成之后,我们需要编写训练函数。所谓的训练函数,指的是输入数据读取、送入网络、计算损失、更新参数的流程,该流程较为固定。我们使用的是多分类交叉生损失函数、使用 SGD 优化方法。最终,将训练好的模型持久化到磁盘中。
# todo 3. 模型训练.
def train(train_dataset, input_dim, output_dim):
# 1. 创建数据加载器, 流程: 数据 -> 张量 -> 数据集 -> 数据加载器
# 参1: 数据集对象(1600条), 参2: 每批次的数据条数, 参3: 是否打乱数据(训练集: 打乱, 测试集: 不打乱)
train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
# 2. 创建神经网络模型.
model = PhonePriceModel(input_dim, output_dim)
# 3. 定义损失函数, 因为是多分类, 这里用的是: 多分类交叉熵损失函数.
criterion = nn.CrossEntropyLoss()
# 4. 创建优化器对象.
optimizer = optim.SGD(model.parameters(), lr=0.001)
# 5. 模型训练.
# 5.1 定义变量, 记录训练的 总轮数.
epochs = 50
# 5.2 开始(每轮的)训练.
for epoch in range(epochs):
# 5.2.1 定义变量, 记录每次训练的损失值, 训练批次数.
total_loss, batch_num = 0.0, 0
# 5.2.2 定义变量, 表示训练开始的时间.
start = time.time()
# 5.2.3 开始本轮的 各个批次的训练.
for x, y in train_loader:
# 5.2.4 切换模型(状态)
model.train() # 训练模式. model.eval() # 测试模式.
# 5.2.5 模型预测.
y_pred = model(x)
# 5.2.6 计算损失.
loss = criterion(y_pred, y)
# 5.2.7 梯度清零, 反向传播, 优化参数.
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 5.2.8 累加损失值.
total_loss += loss.item() # 把本轮的每批次(16条)的 平均损失累计起来. 第1批次的平均损失 + 第2批次的平均损失 + ...
batch_num += 1
# 5.2.4 至此, 本轮训练结束, 打印训练信息.
print(f'epoch: {epoch + 1}, loss: {total_loss / batch_num:.4f}, time: {time.time() - start:.2f}s')
# 6. 走到这里, 说明多轮训练结束, 保存模型(参数)
# 参1: 模型对象的参数(权重矩阵, 偏置矩阵) 参2: 模型保存的文件名.
# print(f'\n\n模型的参数信息: {model.state_dict()}\n\n')
torch.save(model.state_dict(), './model/phone.pth') # 后缀名用: pth, pkl, pickle均可.

七、编写评估函数
使用训练好的模型,对未知的样本的进行预测的过程。我们这里使用前面单独划分出来的验证集来进行评估。
# todo 4. 模型测试.
def evaluate(test_dataset, input_dim, output_dim):
# 1. 创建神经网络分类对象.
model = PhonePriceModel(input_dim, output_dim)
# 2. 加载模型参数.
model.load_state_dict(torch.load('./model/phone.pth'))
# 3. 创建测试集的 数据加载器对象.
# 参1: 数据集对象(400条), 参2: 每批次的数据条数, 参3: 是否打乱数据(训练集: 打乱, 测试集: 不打乱)
test_loader = DataLoader(test_dataset, batch_size=8, shuffle=False)
# 4. 定义变量, 记录预测正确的样本个数.
correct = 0
# 5. 从数据加载器中, 获取到每批次的数据.
for x, y in test_loader:
# 5.1 切换模型状态 -> 测试模式.
model.eval()
# 5.2 模型预测.
y_pred = model(x)
# print(f'y_pred: {y_pred}') # [[0分类概率, 1分类概率, 2分类概率, 3分类概率], [...]...]
# 5.3 根据加权求和, 得到类别, 用argmax()获取最大值对应的下标, 就是类别.
y_pred = torch.argmax(y_pred, dim=1) # dim=1 表示逐行处理.
# print(f'y_pred: {y_pred}') # [第1条数据的预测分类, ...]
# print(f'y: {y}')
# 5.4 统计预测正确的样本个数.
# print(y_pred == y) # tensor([ True, True, True, True, False, False, True, False])
# print((y_pred == y).sum()) # True:1, False:0
correct += (y_pred == y).sum()
# 6.走到这里, 模型预测结束, 打印准确率即可.
print(f'准确率(Accuracy): {correct / len(test_dataset):.4f}')
八、优化
我们前面的网络模型在测试集的准确率为: 0.64250, 我们可以通过以下方面进行调优:
(1)优化方法由 SGD
(2)调整为 Adam 学习率由 1e-3
(3)调整为 1e-4 对数据进行标准化
(4)增加网络深度, 即: 增加网络参数量
(5)调整训练轮次
(6)。。。。
import torch
import torch.nn as nn
import pandas as pd
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.optim as optim
import numpy as np
import time
from sklearn.preprocessing import StandardScaler
# 构建数据集
def create_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_valid, y_train, y_valid = train_test_split(x, y, train_size=0.8, random_state=88, stratify=y)
# 优化①:数据标准化
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_valid = transfer.transform(x_valid)
# 构建数据集,转换为pytorch的形式
train_dataset = TensorDataset(torch.from_numpy(x_train), torch.tensor(y_train.values))
valid_dataset = TensorDataset(torch.from_numpy(x_valid), torch.tensor(y_valid.values))
# 返回结果
return train_dataset, valid_dataset, x_train.shape[1], len(np.unique(y))
# 构建网络模型
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, 输出维度为: 512
self.linear3 = nn.Linear(256, 512)
# 4. 第四层: 输入为维度为 512, 输出维度为: 128
self.linear4 = nn.Linear(512, 128)
# 5. 输出层: 输入为维度为 128, 输出维度为: 4
self.linear5 = nn.Linear(128, output_dim)
def forward(self, x):
# 前向传播过程
x = torch.relu(self.linear1(x))
x = torch.relu(self.linear2(x))
x = torch.relu(self.linear3(x))
x = torch.relu(self.linear4(x))
# 后续CrossEntropyLoss损失函数中包含softmax过程, 所以当前步骤不进行softmax操作
output = self.linear5(x)
# 获取数据结果
return output
# 编写训练函数
def train(train_dataset, input_dim, class_num):
# 固定随机数种子
torch.manual_seed(0)
# 初始化数据加载器
dataloader = DataLoader(train_dataset, shuffle=True, batch_size=8)
# 初始化模型
model = PhonePriceModel(input_dim, class_num)
# 损失函数 CrossEntropyLoss = softmax + 损失计算
criterion = nn.CrossEntropyLoss()
# 优化③:使用Adam优化方法, 优化④:学习率变为1e-4
optimizer = optim.Adam(model.parameters(), lr=1e-4)
# 遍历每个轮次的数据
num_epoch = 50
for epoch_idx in range(num_epoch):
# 训练时间
start = time.time()
# 计算损失
total_loss = 0.0
total_num = 0
# 遍历每个batch数据进行处理
for x, y in dataloader:
model.train()
output = model(x)
# 计算损失
loss = criterion(output, y)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 参数更新
optimizer.step()
# 损失计算
total_num += len(y)
total_loss += loss.item() * len(y)
# 打印损失变换结果
print('epoch: %4s loss: %.2f, time: %.2fs' %
(epoch_idx + 1, total_loss / total_num, time.time() - start))
# 模型保存
torch.save(model.state_dict(), './model/phone-price-model2.pth')
def evaluate(valid_dataset, input_dim, class_num):
# 加载模型和训练好的网络参数
model = PhonePriceModel(input_dim, class_num)
# load_state_dict:将加载的参数字典应用到模型上
# load:加载用来保存模型参数的文件
model.load_state_dict(torch.load('./model/phone-price-model2.pth'))
# 构建加载器
dataloader = DataLoader(valid_dataset, batch_size=8, shuffle=False)
# 评估测试集
correct = 0
# 遍历测试集中的数据
for x, y in dataloader:
# 将其送入网络中
# model.eval()
output = model(x)
# 获取预测类别结果
y_pred = torch.argmax(output, dim=1)
# 获取预测正确的个数
correct += (y_pred == y).sum()
# 求预测精度
print('Acc: %.5f' % (correct / len(valid_dataset)))
if __name__ == '__main__':
train_dataset, valid_dataset, input_dim, class_num = create_dataset()
train(train_dataset, input_dim, class_num)
evaluate(valid_dataset, input_dim, class_num)
更多推荐


所有评论(0)