一、问题总述:
对于预测事件发生概率的回归问题,可以使用多种神经网络模型:多层感知机、卷积神经网络、循环什么网络、Transformer架构等。
-
多层感知机(Multilayer Perceptrons, MLP):
- MLP 是一种基本的前馈神经网络,由输入层、隐藏层和输出层组成。
- 它们可以学习输入特征和输出概率之间的复杂非线性关系。
- 对于结构化数据,如表格数据,MLP 可以有效地捕捉特征间的交互。
-
卷积神经网络(Convolutional Neural Networks, CNN):
- CNN 特别适合处理具有空间或时间结构的数据,如图像、音频或时间序列数据。
- 通过卷积层,CNN 能够捕捉局部特征和模式,这对于识别数据中的复杂结构很有用。
- 在回归问题中,CNN 可以用于预测基于这些结构特征的概率。
-
循环神经网络(Recurrent Neural Networks, RNN):
- RNN 能够处理序列数据,如文本、时间序列或任何形式的顺序数据。
- 它们通过循环连接来捕捉序列中的时间依赖性。
- 在预测事件发生概率时,RNN 可以考虑到序列中先前事件的影响。
-
Transformer 架构:
- Transformer 模型基于自注意力机制,能够处理长距离依赖关系。
- 它们在处理序列数据时非常有效,尤其是在自然语言处理任务中。
- 在回归问题中,Transformer 可以用于捕捉序列中不同位置之间的复杂关系,从而预测事件发生的概率。
那么我们是否可以将他们全部利用起来来对数据进行处理呢?
二、模型搭建:
1、数据处理:
本项目对Kaggle平台—洪水数据集进行回归预测。
数据集地址:https://www.kaggle.com/datasets/naiyakhalid/flood-prediction-dataset/data
数据集情况:
- flood.csv数据集包含与洪水预测相关的多个特征,包括环境因素和社会经济指标。该数据集有50000行和21列。
- 该数据集包括 21 个数字变量,例如“季风强度”、“地形排水”、“河流管理”、“森林砍伐”、“城市化”、“气候变化”、“水坝质量”、“淤积”、“农业实践”、“侵占”、“无效的灾害准备”、“排水系统”、“沿海脆弱性”、“山体滑坡”、“流域”、“基础设施恶化”、“人口得分”、“湿地损失”、“规划不足”、“政治因素”和“洪水概率”。
- 这些数字列中没有缺失值。数据集不包含分类变量。所有列都是 int64 类型。这使得数据集通常适用于无需大量预处理的回归模型。
数据的处理:
使用随机因子42,按照训练集:测试集=8:2的比例随机划分
# 1. 数据加载和预处理
class FloodDataset(Dataset):
def __init__(self, features, labels):
self.features = torch.FloatTensor(features)
self.labels = torch.FloatTensor(labels)
def __len__(self):
return len(self.features)
def __getitem__(self, idx):
return self.features[idx], self.labels[idx]
# 2. 数据处理函数
def prepare_data(file_path, batch_size=128):
# 读取数据
df = pd.read_csv(file_path)
# 分离特征和标签
X = df.iloc[:, :20].values
y = df.iloc[:, 20].values
# 0-1标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 创建数据加载器
train_dataset = FloodDataset(X_train, y_train)
test_dataset = FloodDataset(X_test, y_test)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size*4)
return train_loader, test_loader
2、注意力机制:
使用最简单的多头注意力机制即可:
class MultiHeadAttention(nn.Module):
def __init__(self, hidden_dim, num_heads, kqv_bias=False):
super(MultiHeadAttention, self).__init__()
self.num_heads = num_heads
self.head_dim = hidden_dim // num_heads
self.query = nn.Linear(hidden_dim, hidden_dim, bias=kqv_bias)
self.key = nn.Linear(hidden_dim, hidden_dim, bias=kqv_bias)
self.value = nn.Linear(hidden_dim, hidden_dim, bias=kqv_bias)
self.proj = nn.Linear(hidden_dim, hidden_dim)
self.dropout = nn.Dropout(0.1)
self.scale = self.head_dim ** -0.5
def forward(self, x):
batch_size, seq_len, hidden_dim = x.shape
# 多头切分
q = self.query(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
k = self.key(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
v = self.value(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# 注意力计算
scores = torch.matmul(q, k.transpose(-2, -1)) * self.scale
attn = F.softmax(scores, dim=-1)
attn = self.dropout(attn)
# 注意力输出
out = torch.matmul(attn, v)
out = out.transpose(1, 2).contiguous().view(batch_size, seq_len, hidden_dim)
out = self.proj(out)
return out
3、整体模型:
class FloodPredictionModel(nn.Module):
def __init__(self, hidden_dim=256, num_heads=8, kqv_bias=False):
super(FloodPredictionModel, self).__init__()
# CNN层
self.conv1 = nn.Conv1d(1, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv1d(32, 64, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm1d(32)
self.bn2 = nn.BatchNorm1d(64)
self.relu = nn.ReLU(inplace=True)
# LSTM层
self.lstm = nn.LSTM(64, hidden_dim, num_layers=2, batch_first=True, bidirectional=True)
# Layer Normalization
self.norm1 = nn.LayerNorm(hidden_dim * 2)
self.norm2 = nn.LayerNorm(hidden_dim * 2)
# 多头注意力层
self.attention = MultiHeadAttention(hidden_dim * 2, num_heads, kqv_bias)
# 前馈网络
self.feed_forward = nn.Sequential(
nn.Linear(hidden_dim * 2, hidden_dim * 4),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(hidden_dim * 4, hidden_dim * 2)
)
# 输出层
self.fc = nn.Linear(hidden_dim * 2, 1)
def forward(self, x):
# [batch_size, 1, seq_len]
x = x.unsqueeze(1)
x = self.relu(self.bn1(self.conv1(x)))
x = self.relu(self.bn2(self.conv2(x)))
# [batch_size, seq_len=20, channels=64]
x = x.transpose(1, 2)
# LSTM处理
# [batch_size, seq_len, hidden_dim*2]
lstm_out, _ = self.lstm(x)
# Pre-LayerNorm + 多头注意力 + 残差连接
norm_lstm = self.norm1(lstm_out)
attn_out = self.attention(norm_lstm)
out1 = lstm_out + attn_out
# Pre-LayerNorm + 前馈网络 + 残差连接
norm_out = self.norm2(out1)
ff_out = self.feed_forward(norm_out)
out2 = out1 + ff_out
# 池化得到序列表示
# [batch_size, seq_len, hidden_dim*2]->[batch_size, hidden_dim*2]
out = torch.mean(out2, dim=1)
# 输出层
output = self.fc(out)
return output
三、模型训练:
由于是回归问题,这里使用的损失函数是MSELoss,它计算的是模型预测值与实际值之间差的平方的平均值:
平均绝对误差MAE,它计算的是模型预测值与实际值之间差的绝对值的平均值:
保存最佳模型: 即测试集的MSE最少:
# 4. 训练和测试函数
def train_model(model, train_loader, test_loader, model_save_path, epochs=10):
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
best_val_loss = float('inf')
best_epoch=0
for epoch in range(epochs):
model.train()
running_loss = 0.0
pbar = tqdm(train_loader, desc=f'Epoch [{epoch+1}/{epochs}]')
for batch_X, batch_y in pbar:
#batch_X shape: [batch_size, 20]
#batch_y shape: [batch_size]
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
optimizer.zero_grad()
outputs = model(batch_X)
#outputs shape: [batch_size, 1]
loss = criterion(outputs.squeeze(), batch_y)
loss.backward()
optimizer.step()
running_loss += loss.item()
# 更新进度条显示的损失值
pbar.set_postfix({'loss': f'{loss.item():.4f}'})
model.eval()
val_loss = 0.0
mae = 0.0
total = 0
with torch.no_grad():
for batch_X, batch_y in test_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
outputs = model(batch_X)
# 计算MSE和MAE
val_loss += criterion(outputs.squeeze(), batch_y).item()
mae += torch.abs(outputs.squeeze() - batch_y).sum().item()
total += batch_y.size(0)
avg_val_loss = val_loss / len(test_loader)
avg_mae = mae / total
if avg_val_loss < best_val_loss:
best_val_loss = avg_val_loss
best_epoch=epoch
torch.save(model.state_dict(), model_save_path)
print(f'Model saved with validation loss: {best_val_loss:.4f}')
print(f'Epoch [{epoch+1}/{epochs}] Training Loss: {running_loss/len(train_loader):.4f} Validation MSE: {avg_val_loss:.4f} Validation MAE: {avg_mae:.4f}')
return best_epoch, best_val_loss
四、模型效果:
评估函数:
其中R² 是决定系数,它是回归分析中的一个重要指标,用于衡量模型对数据的拟合程度。
R² 的值介于 0 和 1 之间。值越接近 1,表示模型对数据的拟合越好。
def evaluate_model(model, test_loader, device):
model.eval()
predictions = []
actuals = []
with torch.no_grad():
for batch_X, batch_y in test_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
outputs = model(batch_X)
predictions.extend(outputs.squeeze().cpu().numpy())
actuals.extend(batch_y.cpu().numpy())
predictions = np.array(predictions)
actuals = np.array(actuals)
# 计算指标
mse = np.mean((predictions - actuals) ** 2)
mae = np.mean(np.abs(predictions - actuals))
r2 = r2_score(actuals, predictions)
# 绘制散点图
plt.figure(figsize=(10, 6))
plt.scatter(actuals, predictions, alpha=0.5)
plt.plot([min(actuals), max(actuals)], [min(actuals), max(actuals)], 'r--')
plt.xlabel('Actual Values')
plt.ylabel('Predicted Values')
plt.title('Predictions vs Actuals')
#plt.savefig('prediction_scatter.png')
plt.show() # 显示图像
plt.close()
return {
'MSE': mse,
'MAE': mae,
'R2': r2
}
得到结果:
'MSE': 1.5201329e-05
'MAE': 0.0030947053
'R2': 0.9938964847233116
散点图:
- 从图中可以看出,大多数点都紧密地围绕着对角线分布,这表明模型的预测与实际值非常接近,模型的预测性能较好。
- 点的分布越接近对角线,表示模型的预测误差越小。