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

我们需要帮助小明找出手机的功能(例如:RAM等)与其售价之间的某种关系。我们可以使用机器学习的方法来解决这个问题,也可以构建一个全连接的网络。 需要注意的是: 在这个问题中,我们不需要预测实际价格,而是一个价格范围,它的范围使用 0、1、2、3 来表示,所以该问题也是一个分类问题。接下来我们还是按照四个步骤来完成这个任务:
- 准备训练集数据
- 构建要使用的模型
- 模型训练
- 模型预测评估
构建数据集
数据共有 2000 条, 其中 1600 条数据作为训练集, 400 条数据用作测试集。 我们使用 sklearn 的数据集划分工作来完成。并使用 PyTorch 的 TensorDataset 来将数据集构建为 Dataset 对象,方便构造数据集加载对象。
转为
DataLoader防止机器内存不够,按批次传给神经网络
构建分类网络模型
构建全连接神经网络来进行手机价格分类,该网络主要由三个线性层来构建,使用relu激活函数。 网络共有 3 个全连接层, 具体信息如下: 第一层: 输入为维度为 20, 输出维度为: 128 第二层: 输入为维度为 128, 输出维度为: 256 第三层: 输入为维度为 256, 输出维度为: 4

模型训练
网络编写完成之后,我们需要编写训练函数。所谓的训练函数,指的是输入数据读取、送入网络、计算损失、更新参数的流程,该流程较为固定。我们使用的是多分类交叉生损失函数、使用 SGD 优化方法。最终,将训练好的模型持久化到磁盘中。
模型评估
使用训练好的模型,对未知的样本的进行预测的过程。我们这里使用前面单独划分出来的验证集来进行评估。
网络性能优化
我们前面的网络模型在测试集的准确率为: 0.64250, 我们可以通过以下方面进行调优:
- 对输入数据进行标准化
- 调整优化方法
- 调整学习率
- 增加Dropout/批量归一化层
- 增加网络层数、神经元个数
- 增加训练轮数
- 等等…
进行下如下调整:
- 优化方法由 SGD 调整为 Adam
- 学习率由 1e-3 调整为 1e-4
- 对数据进行标准化
- 增加网络深度, 即: 增加网络参数量
完整代码
import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler # 标准化处理
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader # 数据加载器
import numpy as np
import torch.nn as nn
# 1- 数据处理:文件->DataFrame->Tensor张量->Dataset->DataLoader
def create_dataset():
# 1- 读取文件
df = pd.read_csv("data/手机价格预测.csv",encoding="UTF-8")
# 2- 拆分得到特征数据和目标值
x = df.iloc[:, :-1]
y = df.iloc[:, -1]
# 3- 划分训练集和测试集
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=201)
# 特征标准化处理
transformer = StandardScaler()
x_train = transformer.fit_transform(x_train)
x_test = transformer.transform(x_test)
# 4- DataFrame->Tensor张量
x_train = torch.tensor(x_train,dtype=torch.float32)
x_test = torch.tensor(x_test,dtype=torch.float32)
# values:只取数据内容,字段名称等不需要
# int64是long类型
y_train = torch.tensor(y_train.values,dtype=torch.int64)
y_test = torch.tensor(y_test.values,dtype=torch.int64)
# 5- Tensor张量->Dataset
train_dataset = TensorDataset(x_train,y_train)
test_dataset = TensorDataset(x_test,y_test)
# 6- 其他数据信息
# 6.1- 获得特征个数
feature_nums = x.shape[1]
# 6.2- 获得目标值个数:去重
target_nums = len(np.unique(y))
return train_dataset,test_dataset,feature_nums,target_nums
# 2- 自定义网络结构
class PhonePriceModel(nn.Module):
def __init__(self, feature_nums, target_nums):
# 1- 初始化父类
super().__init__()
# 2- 设置属性值
self.feature_nums = feature_nums
self.target_nums = target_nums
# 3- 搭建网络结构
# 3.1- 第一层隐藏层
# 注意:in_features需要和数据中特征个数完全一致
self.linear1 = nn.Linear(in_features=self.feature_nums,out_features=512)
# 在当前数据中,加完反倒下降了,所以这个具体看情况
# self.dropout1 = nn.Dropout(p=0.3)
# 3.2- 第二层隐藏层
self.linear2 = nn.Linear(in_features=512, out_features=512)
self.linear3 = nn.Linear(in_features=512, out_features=512)
# self.linear4 = nn.Linear(in_features=512, out_features=512)
# self.linear5 = nn.Linear(in_features=512, out_features=512)
# self.linear6 = nn.Linear(in_features=512, out_features=512)
# self.linear7 = nn.Linear(in_features=512, out_features=512)
self.linear8 = nn.Linear(in_features=512, out_features=256)
# 3.3- 输出层
self.output = nn.Linear(in_features=256,out_features=self.target_nums)
def forward(self, data):
# 1- 经过第一层隐藏层
data = torch.relu(self.linear1(data))
# 2- 经过第二层隐藏层
data = torch.relu(self.linear2(data))
data = torch.relu(self.linear3(data))
# data = torch.relu(self.linear4(data))
# data = torch.relu(self.linear5(data))
# data = torch.relu(self.linear6(data))
# data = torch.relu(self.linear7(data))
data = torch.relu(self.linear8(data))
#data = self.dropout1(torch.relu(self.linear8(data)))
# 3- 经过输出层得到结果
# 为什么这里不明确的调用softmax激活函数?因为后续训练的时候会调用CrossEntropyLoss,里面自带softmax
return self.output(data)
# 3- 模型训练
def train_model(train_dataset, feature_nums, target_nums):
# 1- 得到数据加载器,Dataset->DataLoader:为了防止内存溢出
torch.manual_seed(201) # 设置随机数种子,让数据的划分固定下来
"""
参数解释:
dataset:数据集合
batch_size:同一时刻送入到神经网络中的样本条数。训练效果会更好,提高模型的处理吞吐能力
shuffle:对数据打散
"""
dataloader = DataLoader(dataset=train_dataset, batch_size=8, shuffle=True)
# 2- 创建网络模型实例对象
model = PhonePriceModel(feature_nums, target_nums)
# 3- 创建损失函数对象:多分类问题需要使用交叉熵损失函数
loss = nn.CrossEntropyLoss()
# 4- 创建优化器对象
"""
SGD:随机梯度下降算法。每次随机选一条样本计算梯度值
params:告诉梯度下降算法,要帮我对什么参数进行优化(梯度下降),这里就是w和b
lr:学习率
"""
#optimzer = torch.optim.SGD(params=model.parameters(), lr=1e-3)
optimzer = torch.optim.Adam(params=model.parameters(),lr=1e-4,betas=(0.9, 0.99))
# 5- 循环训练
epochs = 50 # 对总样本,总共训练多少个轮次
model.train() # 切换模式为训练模式。也就是允许神经网络随机失活
"""
总样本条数 100
每个批次中有8条样本
那么:每个轮次中有13个批次
"""
for epoch in range(epochs):
# 外层循环控制轮次
# 【可选】记录损失值的变化过程:也就是计算平均损失 = total_loss_value / total_sample_num
total_loss_value = 0.0 # 每个轮次中总的损失值
total_sample_num = 0 # 每个轮次中已经训练的样本条数
for x,y in dataloader:
# x样本中的特殊数据;y样本中的真实值
# 内层循环控制批次
# 这段代码完美遵循了 PyTorch 自动微分的标准范式:
# 前向传播记录计算图 -> 梯度清零 -> 反向传播自动求导 -> 优化器更新参数。
# 5.1- 前向传播
pred_y = model(x)
# 5.2- 计算损失值
loss_value = loss(pred_y,y)
# 【可选】更新损失值
# item()将张量变成标量
total_loss_value += loss_value.item()
total_sample_num += len(x)
# 5.3- 固定代码
# 梯度清零:默认会对梯度值进行累积
optimzer.zero_grad()
# 反向传播:注意只有标量张量能进行反向传播
loss_value.sum().backward()
# 更新参数:也就是更新w和b
optimzer.step()
print(f"第{epoch + 1}次,总的平均损失是:{total_loss_value / total_sample_num}")
# 6- 保存训练好的模型
torch.save(model.state_dict(), "model/phone_price_model.pkl")
# 4- 模型预测:代码层面就是简化版的模型训练代码
def predict_model(test_dataset, feature_nums, target_nums):
# 1- 创建数据加载器
# 注意:模型预测的时候shuffle要设置为False,也就是不让数据打散,为了让预测结果稳定
dataloader = DataLoader(dataset=test_dataset,batch_size=8,shuffle=False)
# 2- 创建算法模型
model = PhonePriceModel(feature_nums,target_nums)
# 3- 加载训练好的模型
model.load_state_dict(torch.load("model/phone_price_model.pkl"))
# 4- 预测
model.eval() # 切换为预测模式。禁止随机失活Dropout
correct_cnt = 0 # 预测正确的总样本条数
for x,y in dataloader:
# 4.1- 前向传播:预测
pred_y = model(x)
"""
1- pred_y:只是输出层中线性求和的结果,并没有经过softmax的处理
2- 可以手动调用softmax,将线性结果处理成概率值。torch.softmax(pred_y,dim=-1)
"""
print(f"pred_y-->{pred_y}")
print(f"pred_y经过softmax的处理-->{torch.softmax(pred_y,dim=-1)}")
# 4.2- 获得预测概率值最高的索引。实际就是获得了预测目标值的类别
pred_id = torch.argmax(pred_y,dim=-1)
print(f"argmax的结果-->{pred_id}")
# 4.3- 统计预测正确的数据条数
print(f"真实值-->{y}")
print(f"判断结果-->{(pred_id==y)}")
correct_cnt = correct_cnt + (pred_id==y).sum()
# 5- 计算准确率
acc_rate = correct_cnt/len(test_dataset)
print(f"预测的准确率是:{acc_rate},预测正确的样本条数:{correct_cnt},预测的总样本条数:{len(test_dataset)}")
if __name__ == '__main__':
train_dataset,test_dataset,feature_nums,target_nums = create_dataset()
# print(train_dataset)
# print(feature_nums)
# print(target_nums)
#
# for i in train_dataset:
# print(i)
# break
train_model(train_dataset,feature_nums,target_nums)
predict_model(test_dataset,feature_nums,target_nums)代码总结
这段代码实现了一个完整的PyTorch多分类预测流程,主要分为四个步骤:
- 数据处理:读取CSV数据,划分训练/测试集,进行特征标准化,最后转换为Tensor并封装为DataLoader。
- 构建模型:定义了一个包含两个隐藏层(带ReLU激活和Dropout防过拟合)和一个输出层的全连接神经网络。
- 模型训练:使用交叉熵损失函数和Adam优化器,进行50个Epoch的迭代训练(前向传播、反向传播、参数更新),并保存模型权重。
- 模型预测:加载保存的权重,切换为评估模式(关闭Dropout),在测试集上进行预测并计算整体准确率。
深度学习总结
1- 深度学习的底层是Tensor张量;开发深度学习的程序需要用到PyTorch
2- PyTorch基础使用:张量的使用
创建张量:torch.tensor
numpy转成tensor:torch.tensor
张量数值计算_矩阵乘法:matmul 或者 @
运算函数:sum()、argmax()
索引操作:行列索引、范围索引
形状操作:reshape、squeeze、unsqueeze、transpose、permute
张量拼接:cat/concat、stack
3- 自动微分
前向传播:样本数据输入到网络模型中,对模型进行训练,得到预测结果
反向传播:通过让损失函数取最小值,倒逼w和b得到最优的参数组合。
反向传播需要算梯度值,梯度值就是求导,通过自动微分求导数
4- 神经网络
4.1- 参数初始化的7种方式。w初始化如何选择;b初始化如何选择
4.2- 激活函数的种类:sigmoid、tanh、relu、softmax。什么情况下选什么激活函数
4.3- 神经网络的搭建流程:nn.Module、__init__、forward
5- 损失函数:多分类问题使用CrossEntropyLoss交叉熵损失函数
6- 神经网络优化方法:Momentum动量法、Adagrad、RMSProp、Adam各自是对什么进行了优化
7- 学习率衰减优化方法【了解】:等间隔、指定间隔、指数
8- 正则化方法
8.1- Dropout随机失活:每个神经元失活的可能性是多少
8.2- BN批量归一化【了解】
9- 能够经过3-5遍能彻底理解代码即可