在机器学习的实际应用中,我们有时会遇到这样的回归任务:输入一个高维特征向量(例如20维),需要预测一个处于狭窄范围内的连续值。本文将以一个具体案例为例,讲解如何使用PyTorch构建一个多层感知机(MLP),实现从20维输入到1维输出的映射,并满足严格的误差限制(预测误差小于1)。
一、构造仿真数据
由于我们没有现成的真实数据集,因此首先需要构造一个符合任务要求的仿真数据集。我们的目标是生成30000个样本,每个样本包含20个特征(x)和1个目标值(y),且所有y值的差距在个位数以内。
-
生成特征X:使用NumPy的随机函数生成一个形状为 (30000, 20) 的数组,数据类型设为float32以匹配PyTorch的常用精度。
import numpy as np
X = np.random.randn(30000, 20).astype(np.float32)
-
定义复杂映射关系生成y:为了让任务更具挑战性,我们设计一个由20个特征非线性组合而成的函数来生成原始y值,确保输出与所有输入维度都相关。
y = (
1.2 * np.sin(X[:, 0] * X[:, 1])
+ 0.8 * np.cos(X[:, 2] ** 2 - X[:, 3])
+ 0.5 * np.sin(X[:, 4] + X[:, 5] * X[:, 6])
+ 0.7 * np.cos(X[:, 7] * X[:, 8])
+ 0.9 * np.sin(X[:, 9] ** 3)
+ 0.6 * np.cos(X[:, 10] * X[:, 11] - X[:, 12])
+ 0.4 * np.sin(X[:, 13] + X[:, 14])
+ 0.5 * np.cos(X[:, 15] * X[:, 16])
+ 0.3 * np.sin(X[:, 17] ** 2 - X[:, 18])
+ 0.2 * X[:, 19]
)
-
归一化并缩放y值:将y值线性变换到[20, 25]区间内,使其满足“差距为个位数”的条件。
y = (y - y.min()) / (y.max() - y.min()) # 映射到[0,1]
y = 20 + 5 * y # 映射到[20,25]
y = y.reshape(-1, 1).astype(np.float32) # 调整为列向量
-
保存数据:将生成的数据对(X, y)保存到文本文件data.txt中,每一行的格式为(x0, x1, ..., x19, y)。
with open("data.txt", "w") as f:
for i in range(len(X)):
f.write(f"({', '.join(map(str, X[i]))}, {y[i][0]})\n")
print("数据已保存到 data.txt")
二、读取与划分数据
在另一个Python程序中,我们需要读取保存的数据,并将其划分为训练集和验证集。
首先,定义一个从文本文件加载数据的函数:
import ast
import numpy as np
import torch
from torch.utils.data import DataLoader, TensorDataset
def load_data_from_txt(file_path):
X = []
y = []
with open(file_path, "r") as f:
for line in f:
line = line.strip()
# 使用ast.literal_eval安全地解析元组形式的字符串
tuple_data = ast.literal_eval(line)
X.append(tuple_data[:-1]) # 前20个元素为特征
y.append(tuple_data[-1]) # 最后一个元素为标签
return np.array(X), np.array(y).reshape(-1, 1)
然后,加载数据并划分(80%训练,20%验证),最后转换为PyTorch Tensor并创建DataLoader。
# 加载数据
X, y = load_data_from_txt("data.txt")
# 划分训练集和验证集
train_size = int(0.8 * len(X))
X_train, X_val = X[:train_size], X[train_size:]
y_train, y_val = y[:train_size], y[train_size:]
# 转换为Tensor
X_train_tensor = torch.from_numpy(X_train).float()
y_train_tensor = torch.from_numpy(y_train).float()
X_val_tensor = torch.from_numpy(X_val).float()
y_val_tensor = torch.from_numpy(y_val).float()
# 设置批大小并创建训练集DataLoader
batch_size = 128
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
三、设计网络模型
考虑到y值被限制在[20,25]的狭窄区间,一个有效的策略是让模型学习目标值与区间中值(例如22.5)的偏差,这有助于模型更快地收敛到目标范围。我们设计一个具有三层隐藏层的MLP,使用ReLU激活函数。
import torch.nn as nn
class MLP(nn.Module):
def __init__(self, input_dim=20, inner_dim=64, base_y=22.5):
super(MLP, self).__init__()
self.model = nn.Sequential(
nn.Linear(input_dim, inner_dim),
nn.ReLU(),
nn.Linear(inner_dim, inner_dim),
nn.ReLU(),
nn.Linear(inner_dim, inner_dim),
nn.ReLU(),
nn.Linear(inner_dim, 1, bias=False) # 输出偏差量
)
self.base_y = base_y # 基准值
def forward(self, x):
# 确保输入形状为 [batch_size, 20]
if x.dim() == 1:
x = x.view(1, -1)
elif x.dim() == 2 and x.shape[1] != 20:
x = x.view(-1, 20)
# 输出为 基准值 + 预测偏差
return self.model(x) + self.base_y
四、配置训练参数
我们采用均方误差损失(MSE),因为它直接衡量预测值与真实值之间的差距。优化器选择带动量(Momentum)的随机梯度下降(SGD),动量有助于加速收敛并稳定训练过程。
import torch.optim as optim
model = MLP(base_y=22.5)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.002, momentum=0.9)
epochs = 50
五、训练与验证
标准的训练循环包括前向传播、损失计算、反向传播和参数更新。每个epoch结束后,在验证集上评估模型性能。
train_losses = []
val_losses = []
for epoch in range(epochs):
model.train()
running_train_loss = 0.0
for batch_X, batch_y in train_loader:
# 前向传播
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_train_loss += loss.item()
# 计算平均训练损失
avg_train_loss = running_train_loss / len(train_loader)
train_losses.append(avg_train_loss)
# 验证阶段
model.eval()
with torch.no_grad():
val_outputs = model(X_val_tensor)
val_loss = criterion(val_outputs, y_val_tensor).item()
val_losses.append(val_loss)
# 打印日志
print(f'Epoch [{epoch+1:03d}/{epochs}], Train Loss: {avg_train_loss:.4f}, Val Loss: {val_loss:.4f}')
print('训练完成。')
六、结果分析
经过训练,验证集损失(MSE)可以稳定在0.11左右。由于MSE是平方误差,其平方根(RMSE)约为0.33,这意味着预测值与真实值的平均误差在0.33左右,成功满足了“误差小于1”的严苛要求。这证明了所设计的网络结构、基准值策略以及训练流程对于解决此类狭窄区间的回归问题是有效的。
本项目的完整源代码,包括更详细的训练过程监控(如损失曲线绘制)和模型测试示例,已开源。