使用CNN+LSTM+Attn的洪水发生几率的预测回归模型

一、问题总述:

对于预测事件发生概率的回归问题,可以使用多种神经网络模型:多层感知机、卷积神经网络、循环什么网络、Transformer架构等。

  1. 多层感知机(Multilayer Perceptrons, MLP)

    • MLP 是一种基本的前馈神经网络,由输入层、隐藏层和输出层组成。
    • 它们可以学习输入特征和输出概率之间的复杂非线性关系。
    • 对于结构化数据,如表格数据,MLP 可以有效地捕捉特征间的交互。
  2. 卷积神经网络(Convolutional Neural Networks, CNN)

    • CNN 特别适合处理具有空间或时间结构的数据,如图像、音频或时间序列数据。
    • 通过卷积层,CNN 能够捕捉局部特征和模式,这对于识别数据中的复杂结构很有用。
    • 在回归问题中,CNN 可以用于预测基于这些结构特征的概率。
  3. 循环神经网络(Recurrent Neural Networks, RNN)

    • RNN 能够处理序列数据,如文本、时间序列或任何形式的顺序数据。
    • 它们通过循环连接来捕捉序列中的时间依赖性。
    • 在预测事件发生概率时,RNN 可以考虑到序列中先前事件的影响。
  4. 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,它计算的是模型预测值与实际值之间差的平方的平均值:

MSE=\frac{1}{N} \sum_{i=1}^{N}(y_{i}-\tilde{y}_{i})^{2}

平均绝对误差MAE,它计算的是模型预测值与实际值之间差的绝对值的平均值:

MAE=\frac{1}{N} \sum_{i=1}^{N}|y_{i}-\tilde{y}_{i}|

保存最佳模型: 即测试集的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

散点图:

  • 从图中可以看出,大多数点都紧密地围绕着对角线分布,这表明模型的预测与实际值非常接近,模型的预测性能较好。
  • 点的分布越接近对角线,表示模型的预测误差越小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值