价格分析案例和本章内容总结

案例需求分析

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

1734255905857

我们需要帮助小明找出手机的功能(例如:RAM等)与其售价之间的某种关系。我们可以使用机器学习的方法来解决这个问题,也可以构建一个全连接的网络。 需要注意的是: 在这个问题中,我们不需要预测实际价格,而是一个价格范围,它的范围使用 0、1、2、3 来表示,所以该问题也是一个分类问题。接下来我们还是按照四个步骤来完成这个任务:

  • 准备训练集数据
  • 构建要使用的模型
  • 模型训练
  • 模型预测评估

构建数据集

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

转为DataLoader 防止机器内存不够,按批次传给神经网络

构建分类网络模型

构建全连接神经网络来进行手机价格分类,该网络主要由三个线性层来构建,使用relu激活函数。 网络共有 3 个全连接层, 具体信息如下: 第一层: 输入为维度为 20, 输出维度为: 128 第二层: 输入为维度为 128, 输出维度为: 256 第三层: 输入为维度为 256, 输出维度为: 4

1734256199295

模型训练

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

模型评估

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

网络性能优化

我们前面的网络模型在测试集的准确率为: 0.64250, 我们可以通过以下方面进行调优:

  1. 对输入数据进行标准化
  2. 调整优化方法
  3. 调整学习率
  4. 增加Dropout/批量归一化层
  5. 增加网络层数、神经元个数
  6. 增加训练轮数
  7. 等等…

进行下如下调整:

  1. 优化方法由 SGD 调整为 Adam
  2. 学习率由 1e-3 调整为 1e-4
  3. 对数据进行标准化
  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多分类预测流程,主要分为四个步骤:

  1. 数据处理:读取CSV数据,划分训练/测试集,进行特征标准化,最后转换为Tensor并封装为DataLoader。
  2. 构建模型:定义了一个包含两个隐藏层(带ReLU激活和Dropout防过拟合)和一个输出层的全连接神经网络。
  3. 模型训练:使用交叉熵损失函数和Adam优化器,进行50个Epoch的迭代训练(前向传播、反向传播、参数更新),并保存模型权重。
  4. 模型预测:加载保存的权重,切换为评估模式(关闭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遍能彻底理解代码即可