用于构建Fitbit模型的神经网络架构
在这篇文章中,我将向你展示如何使用来自Kaggle的真实世界数据集FitBit健身追踪器数据集轻松构建和训练你自己的神经网络模型。
我们将使用一些熟悉的日常用户活动作为特征来构建和训练神经网络模型,例如总步数、总距离和高度活跃分钟数。你还将能够使用新数据集预测总卡路里消耗。
我假设你已经具备了Python、PyTorch和一些神经网络基本概念的基础知识。除此之外,你已经准备好了。我建议你跟着这篇帖子一起学习,到这篇帖子结束时,你将能够构建并训练你自己的神经网络,并可以进一步自定义以适应其他应用场景。
你可以通过遵循6个简单的步骤轻松实现这一切。
让我们开始吧。
步骤 1:导入所需的库第一步是导入下面给出的所需类、函数和模块。在本文后面的具体实现中会提供更多关于各个项目的详细信息。在运行下面的导入代码之前,请确保已经安装了最新版本的Python和PyTorch。
导入 torch
导入 pandas as pd
导入 numpy as np
导入 torch.nn as nn
从 torch.utils.data 导入 DataLoader, TensorDataset
从 sklearn.model_selection 导入 train_test_split
导入 matplotlib.pyplot as plt
步骤2:加载和准备数据
首先,从这里下载FitBit健身追踪器数据集here。使用panda函数加载数据文件dailyActivity_merged.csv到DataFrame变量中,如下所示,并验证数据是否加载成功。
# 可以使用你保存 csv 文件的文件路径
fitbit_df = pd.read_csv("/content/dailyActivity_merged.csv")
# 显示 csv 文件的前 5 行
fitbit_df.head()
Fitbit健身数据集CSV文件的前5行
上述 DataFrame 中的特征数量相当多。为了训练目的,我们将特征数量减少到 6。然而,不建议在生产应用中减少特征数量。通过增加更多特征,模型可以更好地学习并进一步提高准确性。
features = ['TotalSteps', 'TotalDistance', 'VeryActiveDistance', 'ModeratelyActiveDistance', 'VeryActiveMinutes', 'FairlyActiveMinutes', 'Calories']
fitbit_df = fitbit_df[features]
# 所有的特征值必须是数值型。网络无法处理非数值型数据。
fitbit_df.head()
只包含6个特征的DataFrame
注意: 由于数据来自Kaggle,已经进行了预处理。在这里我不需要执行任何进一步的预处理步骤。但是,如果你想用这个模型来训练其他原始数据,你必须在进一步处理数据进行训练之前对数据进行一些预处理操作。数据质量对于任何模型提供高质量的结果非常重要。他们常说“垃圾输入,垃圾输出”。
接下来,我们将 fitbit_df 的字段拆分为输入 X 和输出 y,并将其值一起拆分。同时,将这两个变量都转换为 PyTorch 张量。将所有数据转换为 PyTorch 张量可以避免后续可能出现的数据类型不匹配问题。
X = torch.tensor(fitbit_df.drop(columns=["Calories"], axis=1).to_numpy(), dtype=torch.float)
y = torch.tensor(fitbit_df["Calories"].to_numpy(), dtype=torch.long)
# 验证 X, y 的值
print("输入张量:", X[:5], "\n")
print("输出张量:", y[:5], "\n\n")
print("输入张量的形状:", X.shape)
print("输出张量的形状:", y.shape)
现在,为了准备模型的训练、验证和测试数据,我们将把这个单一的数据集分成三个数据集,训练集占80%,验证集占10%,测试集占10%。我们将使用sklearn.model_selection模块中的train_test_split函数来执行拆分操作。
X_train, X_eval, y_train, y_eval = train_test_split(X, y, test_size=0.2, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(X_eval, y_eval, test_size=0.5, random_state=42)
# 划分前的数据集形状
print("输入张量的形状: ", X.shape)
print("输出张量的形状: ", y.shape, "\n")
# 划分后的训练集、验证集和测试集的形状。总和与原始数据集相同。
# 每个数据集仍然有6个特征,这是预期的结果
print("训练输入: ", X_train.shape, "训练输出: ", y_train.shape)
print("验证输入: ", X_val.shape, "验证输出: ", y_val.shape)
print("测试输入: ", X_test.shape, "测试输出: ", y_test.shape)
特征归一化: 如果你注意到特征 TotalSteps 的值范围是四位数,而特征如 TotalDistance 和 VeryActiveDistance 的值范围是只有一位数。这种范围的巨大差异会影响训练过程的平滑性,甚至可能导致发散,如果不解决这个问题。因此,我们通过执行数据归一化操作来解决这个问题。
一种常见的特征归一化方法是应用均值为零和标准差如下的处理。
# 计算均值
mean_train = X_train.mean(dim=0)
mean_val = X_val.mean(dim=0)
mean_test = X_test.mean(dim=0)
# 计算标准差
std_train = X_train.std(dim=0)
std_val = X_val.std(dim=0)
std_test = X_test.std(dim=0)
# 将均值和标准差应用于输入数据集
X_train = (X_train - mean_train) / std_train
X_val = (X_val - mean_val) / std_val
X_test = (X_test - mean_test) / std_test
print("标准化后的 X_train: ", X_train[:5], "\n")
print("标准化后的 X_val: ", X_val[:5], "\n")
print("标准化后的 X_test: ", X_test[:5], "\n")
训练、验证和测试输入数据的归一化值
我们将为每个训练、验证和测试张量数据的 (输入, 输出) 对创建一个 TensorDataset。TensorDataset 是 PyTorch 的一个特性,它可以包装张量,并在许多场景中非常有用,例如创建 DataLoader,这是我们接下来要创建的内容。
train_dataset = TensorDataset(X_train, y_train)
val_dataset = TensorDataset(X_val, y_val)
test_dataset = TensorDataset(X_test, y_test)
# 显示 train_dataset 的样子
print(train_dataset[0])
Train_dataset 包含 2 个张量,一个属于 X_train,另一个属于 y_train
DataLoader 结合了数据集和采样器,并提供了给定数据集的可迭代对象。如果训练数据非常庞大,逐个迭代单个训练数据将花费大量时间,同样地,由于处理资源的限制,一次性训练所有数据在实际操作中是不可能的。DataLoader 通过允许定义批次大小来解决这个问题,这可以使模型一次训练一批多个训练数据。我们将为训练集、验证集和测试集分别创建一个 DataLoader,并将 batch_size 设置为 10。设置 shuffle=True 可以打乱批次中数据的出现顺序,从而提高训练和学习的效果。
# 为每个训练、验证和测试数据集创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True, drop_last=True)
val_loader = DataLoader(val_dataset, batch_size=10, shuffle=False, drop_last=True)
test_loader = DataLoader(test_dataset, batch_size=len(test_dataset.tensors[0]), shuffle=False, drop_last=True)
# 显示 train_loader 的第一个批次
for X, y in train_loader:
print("train_loader 输入的第一个批次的形状:", X.shape)
print("train_loader 输出的第一个批次的形状:", y.shape)
break
DataLoader(train_loader)中单个批次的形状
步骤3:定义模型我们现在开始定义一个来自PyTorch nn.Module的功能模型。并没有特定的规则来规定每一层的层数和每个节点的数量。通常,需要经过多轮的尝试和错误来找到合适的数字。一个经验法则是,网络应该足够小以实现快速处理,同时又足够大以高精度地解决预期问题。
我们的模型由3层、3个激活函数和1个输出层组成。
- 模型的第一层 layer1 从输入数据中获取 in_feature 值。它应该是特征总数或输入向量的维度,这里是6。在我们的情况下,in_features = X_train.shape[1]。
- 第一层 layer1 有64个节点。layer1 后面跟着第一个激活函数或激活层(在深度学习文献中可以互换使用)activation1。我们使用ReLU作为激活函数,它在性能方面是一个流行的选择,并且可以避免梯度消失问题。ReLU为前一层的线性输出提供了非线性,这对于模型在训练期间更好地学习非常重要。
- 第二层 layer2 有64个节点。在 layer2 之后添加了另一个激活层 activation2。
- 在 activation2 之后是第三层 layer3,它有32个节点。在 layer3 之后添加了另一个激活层 activation3。
- 输出层有一个节点,因为我们处理的是一个回归任务。
class fitbit_model(nn.Module):
def __init__(self, input_features, output_class):
super(fitbit_model, self).__init__()
self.layer1 = nn.Linear(in_features=input_features, out_features=64)
self.activation1 = nn.ReLU()
self.layer2 = nn.Linear(in_features=64, out_features=64)
self.activation2 = nn.ReLU()
self.layer3 = nn.Linear(in_features=64, out_features=32)
self.activation3 = nn.ReLU()
self.output = nn.Linear(in_features=32, out_features=output_class)
def forward(self, x):
x = self.activation1(self.layer1(x))
x = self.activation2(self.layer2(x))
x = self.activation3(self.layer3(x))
return self.output(x)
-
所有层都在 fitbit_model 类的构造函数中定义。
-
super(fitbit_model, self).init() ,这是父类 nn.Module 的构造函数,用于启动 fitbit_model 模型。
- forward 函数将接受输入张量并返回模型的输出张量。
接下来,通过向 fitbit_model 构造函数提供输入特征值和输出类别值来初始化模型。最初,权重和偏差参数会由模型自身自动初始化(分配一些随机值)(这些值已在下方显示)。在训练过程中,这些参数值将会被更新。
输入特征 = X_train.shape[1]
输出类别 = 1
模型 = fitbit_model(输入特征, 输出类别)
print("模型结构\n")
print(模型)
print("\n模型初始权重值,层1")
print(模型.layer1.weight[:2])
print("层1权重形状:", 模型.layer1.weight.shape)
print("\n模型初始偏置值,层1")
print(模型.layer1.bias[:2])
print("层1偏置形状:", 模型.layer1.bias.shape)
Fitbit 模型架构
步骤4:训练和评估模型我们将从定义损失函数、优化器、总 epoch 数以及其他变量开始准备。
# 损失函数和优化器
loss_fn = nn.HuberLoss(delta=0.5)
optimizer = torch.optim.Adam(params=model.parameters(), lr=0.001)
# 收集指标
train_losses = []
val_losses = []
# 总共的 epoch 数量
n_epochs = 100
-
loss_fn = nn.HuberLoss(delta=0.5) :Huber Loss 是一种用于稳健回归的损失函数。它是 MSE 和 MAE 的结合。delta 是定义 MAE 和 MSE 范围的超参数,其值会根据任务的不同而变化。
-
optimizer = torch.optim.Adam(params=model.parameters(), lr=0.001) :Adam 是最常用的优化算法之一,它会根据计算出的梯度来更新参数。它接受 [(model.parameters) — 可学习参数 (W 和 b)] 和学习率,学习率决定了模型学习的速度,以便实现平滑收敛。因此,学习率的值应谨慎选择。
- n_epochs = 100 :整个训练和验证周期将运行 100 次
最后,让我们开始训练和评估我们的模型,共100个 epochs。
**# 训练和评估模型**
for epoch in range(n_epochs):
**# 训练模型**
model.train()
train_loss = 0.0
for x_batch, y_batch in train_loader:
# 正向传播
y_pred = model(x_batch)
y_pred = torch.squeeze(y_pred)
y_batch = y_batch.float()
loss = loss_fn(y_pred, y_batch)
# 反向传播
optimizer.zero_grad()
loss.backward()
# 更新权重
optimizer.step()
train_loss += loss.item()
train_losses.append(train_loss / len(train_loader))
**# 评估模型**
model.eval()
val_loss = 0.0
with torch.inference_mode():
for x_batch, y_batch in val_loader:
y_pred = model(x_batch)
y_pred = torch.squeeze(y_pred)
y_batch = y_batch.float()
loss = loss_fn(y_pred, y_batch)
val_loss += loss.item()
val_losses.append(val_loss/len(val_loader))
print(f'Epoch [{epoch+1}/{n_epochs}]: Train Loss: {train_losses[-1]:.2f}, Val Loss: {val_losses[-1]:.2f}')
-
model.train() : 将模型设置为训练模式。在训练模式下,PyTorch 启用诸如 dropout 和批量归一化等功能,这些功能通常在训练时使用,但在推理时不会使用。
-
在训练过程中,训练数据将从 train_loader 中批量获取并输入到模型中进行推理。
-
loss = loss_fn(y_pred, y_batch) : 计算预测输出值与数据集真实值之间的差异作为损失。
-
optimizer.zero_grad() : 在开始反向传播之前将梯度设置为零,因为 PyTorch 在后续的反向传播中会累积梯度。因此必须清除。
-
loss.backward() : 在这一步中,计算损失相对于所有可学习参数的梯度。
-
optimizer.step(): 计算梯度后,optimizer.step() 方法将更新所有参数。
-
train_losses.append(train_loss / len(train_loader)) : 每个批次的训练损失将被计算并累积。这将在后续的图表分析中使用。
-
model.eval() : 将模型设置为评估模式,在评估模式下,模型会禁用 dropout 等操作,从而专注于进行推理和测试。
-
with torch.inference_mode() : 在这种模式下,autograd 不会跟踪梯度,因为在进行推理时不需要计算梯度。计算梯度仅在训练时需要。
- val_losses.append(val_loss/len(val_loader)) : 每个批次的验证损失将被计算并累积到 val_losses 数组中。这将在后续的图表分析中使用。
运行上述训练和评估代码段后,你会看到随着 epoch 数的增加,损失值开始减小。
模型训练和评估进度结果
步骤5:分析训练和评估结果
我们使用 matplotlib.pyplot 库函数来绘制结果并进行相应分析。
plt.figure(figsize=(12,6))
plt.plot(train_losses, label="训练损失")
plt.plot(val_losses, label="验证损失")
plt.ylabel("每个Epoch的损失")
plt.xlabel("Epoch")
plt.legend()
plt.show()
每个 epoch 的训练和评估损失结果
- 在20个周期之后,训练损失和验证损失都大幅减少了。这一结果被认为相当令人印象深刻。为了获得更好的结果,你可以尝试增加训练数据和周期的数量。
步骤6:对新数据进行预测
最后,既然模型已经成功构建和训练,你肯定很好奇我们的模型在新数据集上的表现如何。这也是本文的一个主要目标。让我们试一试。
我们将使用一个之前准备但直到现在都未使用的新的数据集叫做 test_dataset。现在,让我们用它来进行预测。
# 预测测试数据集
model.eval()
pred_val = []
target_val = []
with torch.inference_mode():
for x_test, y_test in test_loader:
y_test_pred = model(x_test)
pred_val.append(y_test_pred)
target_val.append(y_test)
让我们使用 matplotlib.pyplot
库函数绘制散点图来分析在新测试数据集上的预测结果。
labels = torch.cat(target_val).flatten().tolist()
predictions = torch.cat(pred_val).flatten().tolist()
plt.scatter(labels, predictions)
max_lim = max(max(predictions), max(labels))
max_lim += max_lim * 0.1
plt.xlim(0, max_lim)
plt.ylim(0, max_lim)
plt.plot([0,max_lim], [0,max_lim], "b-")
plt.xlabel("真实值")
plt.ylabel("预测值")
plt.tight_layout()
散点图显示实际值与预测值的关系
在这个散点图中,你可以看到我们的模型预测值和数据集的真实值非常接近。因此这证明了我们构建并训练的模型在这新的测试数据集上表现得相当好。
就这样,恭喜你!你已经成功地使用PyTorch和Fitbit训练数据集构建并训练了自己的神经网络。如果你跟着代码一起编写,我相信你可以很轻松地为不同的应用场景构建并训练任何数据的模型。
参考链接:Google Colab Notebook
感谢阅读!
共同学习,写下你的评论
评论加载中...
作者其他优质文章