一、需求分析

        小明创办了一家手机公司,他不知道如何估算手机产品的价格。为了解决这个问题,他收集了多家公司的手机销售数据。该数据为二手手机的各个性能的数据,最后根据这些性能得到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)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐