nn.TransformerEncoder的输出为NaN值的原因及解决方法

问题描述:

当使用nn.TransformerEncoder时,即如下情况:

实例化一个TransformerEncoder 
 self.encoder_layer = nn.TransformerEncoderLayer(d_model=encoder_in_dim, nhead=encoder_head,
                                                           dim_feedforward=encoder_ffnn_dim,
                                                           batch_first=batch_first)
 self.model = nn.TransformerEncoder(self.pre_encoder_layer, num_layers=pre_encoder_layer_num)
调用:
transformer_features =  self.model(embeddings, src_key_padding_mask=src_padding_mask)

transformer_features的值为NaN
原因在于src_padding_mask的传入出现均为0/False的情况!即attention—mask出现了全1/True行
由于我们在使用MultiheadAttention做self-attention时因为batch内序列长度不一致,难免需要使用mask
以pytorch自带的torch.nn.TransformerEncoder方法为例,其forward函数如下

forward(src, mask=None, src_key_padding_mask=None)

这里的mask会送到torch.nn.TransformerEncoderLayer的forward函数:

def forward(self, src: Tensor, src_mask: Optional[Tensor] = None, src_key_padding_mask: Optional[Tensor] = None) -> Tensor:
        src2 = self.self_attn(src, src, src, attn_mask=src_mask,key_padding_mask=src_key_padding_mask)[0]

之后送到MultiheadAttention的forward函数的attn_mask参数,而这里做的是一个self attention。
此时若是attn_mask出现一整行都是True的情况,通过如下源码中的实现mask的方法可以看到:

    if attn_mask is not None:
        if attn_mask.dtype == torch.bool:
            attn_output_weights.masked_fill_(attn_mask, float('-inf'))
        else:
            attn_output_weights += attn_mask

把权重矩阵中需要mask的位置置为负无穷,然后再按行做softmax,问题就在这里,把一个元素全是是负无穷的tensor送给softmax,就会得到一个全是NaN的tensor。然后NaN和任何数运算都是NaN,NaN会传染,再经过一轮self attention,输出的tensor就全成NaN了。

解决方法:避免attention mask中存在全1/True的行

<think>好的,我需要帮助用户编写一个使用BiGRU、注意力机制和Transformer模型来填补时间序列缺失的具体代码示例。用户提供了一个名为“鼓风湿度.csv”的数据文件,其中包含两列:时间和鼓风湿度。用户希望随机删除10%的数据点,并用之前提到的模型进行填补。 首先,我需要回顾之前提到的模型结构:结合BiGRU、Transformer和注意力机制。用户需要具体的代码实现,所以应该提供一个端到端的示例,包括数据预处理、模型构建、训练和填补步骤。 接下来,我需要考虑数据的格式。数据有两列,时间列和鼓风湿度。假设时间列是等间隔的时间戳,但用户可能已经处理过,所以可能只需要数数据。可能需要将时间列转换为特征,或者仅使用湿度作为单变量时间序列。这里可能只需要处理湿度,时间列可能作为索引,但模型可能需要时间特征,比如时间戳的嵌入,或者周期性编码(如小时、天等)。不过,如果数据是单变量的,可能只处理湿度,时间作为顺序索引。需要确认,但用户可能只需要单变量填补。 数据预处理步骤包括加载CSV文件,随机删除10%的数据点,生成掩码矩阵,然后进行标准化。然后需要将数据转换为适合模型输入的格式,比如滑动窗口创建序列样本。 模型结构方面,需要结合BiGRU、Transformer和注意力机制。可能需要先通过BiGRU处理序列,获取双向特征,然后通过Transformer编码全局依赖,再通过注意力机制融合特征,最后回归预测缺失。 代码实现需要使用PyTorch框架,因为Transformer和BiGRU在PyTorch中有现成的实现。需要定义数据集类、模型结构、训练循环和填补函数。 可能的挑战包括正确实现注意力机制部分,如何将BiGRU和Transformer的输出结合起来,以及如何处理掩码信息。此外,训练时需要注意只计算缺失位置的损失。 需要确保代码的每一步都有注释,方便用户理解。另外,可能需要调整超参数,如隐藏层大小、头数、学习率等,但用户可能需要自行调整。 验证部分也需要考虑,比如划分训练集和验证集,或者使用全部数据训练后直接填补。但根据问题描述,用户可能希望直接在缺失位置上进行填补,所以可能不需要严格的分割,而是使用自监督的方式,随机掩盖部分数据作为训练目标。 最后,需要测试代码的正确性,确保数据加载、模型训练和预测步骤没有错误,并且填补结果合理。</think>以下是一个基于PyTorch的完整代码示例,包含数据预处理、模型搭建、训练和填补全流程: ```python import numpy as np import pandas as pd import torch import torch.nn as nn from sklearn.preprocessing import StandardScaler from torch.utils.data import Dataset, DataLoader # ==================== # 1. 数据预处理 # ==================== def load_data(file_path, missing_rate=0.1): # 读取数据 df = pd.read_csv(file_path, parse_dates=['时间']) values = df['鼓风湿度'].values.reshape(-1, 1) # 标准化 scaler = StandardScaler() scaled_values = scaler.fit_transform(values) # 生成随机缺失 mask = np.random.rand(*scaled_values.shape) > missing_rate missing_values = np.where(mask, scaled_values, np.nan) return missing_values, mask, scaler # ==================== # 2. 数据集定义 # ==================== class TimeSeriesDataset(Dataset): def __init__(self, data, seq_len=24): self.data = data self.seq_len = seq_len def __len__(self): return len(self.data) - self.seq_len def __getitem__(self, idx): window = self.data[idx:idx+self.seq_len] # 构造输入特征 x = np.zeros((self.seq_len, 2)) x[:, 0] = window[:, 0] # 数特征 x[:, 1] = np.isnan(window[:, 0]).astype(float) # 缺失标记 # 构造标签(仅含缺失位置) y = window[:, 0].copy() y_mask = np.isnan(y) y = np.nan_to_num(y, nan=0.0) return torch.FloatTensor(x), torch.FloatTensor(y), torch.FloatTensor(y_mask) # ==================== # 3. 模型定义 # ==================== class FusionModel(nn.Module): def __init__(self, input_dim=2, hidden_dim=64, n_heads=4): super().__init__() # BiGRU层 self.bigru = nn.GRU(input_dim, hidden_dim, bidirectional=True, batch_first=True) # Transformer编码器 self.transformer = nn.TransformerEncoder( nn.TransformerEncoderLayer(d_model=2*hidden_dim, nhead=n_heads), num_layers=2 ) # 交叉注意力 self.attn = nn.MultiheadAttention(embed_dim=2*hidden_dim, num_heads=n_heads) # 输出层 self.fc = nn.Sequential( nn.Linear(2*hidden_dim, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): # BiGRU处理 gru_out, _ = self.bigru(x) # [batch, seq_len, 2*hidden_dim] # Transformer处理 trans_out = self.transformer(gru_out) # 交叉注意力 attn_out, _ = self.attn(gru_out, trans_out, trans_out) # 残差连接 fused = gru_out + attn_out # 回归预测 pred = self.fc(fused) return pred.squeeze(-1) # ==================== # 4. 训练流程 # ==================== def train_model(data, epochs=50): # 准备数据 dataset = TimeSeriesDataset(data, seq_len=24) loader = DataLoader(dataset, batch_size=32, shuffle=True) # 初始化模型 model = FusionModel() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.MSELoss() # 训练循环 for epoch in range(epochs): total_loss = 0 for x, y, mask in loader: optimizer.zero_grad() pred = model(x) loss = criterion(pred[mask], y[mask]) loss.backward() optimizer.step() total_loss += loss.item() print(f"Epoch {epoch+1}, Loss: {total_loss/len(loader):.4f}") return model # ==================== # 5. 数据填补 # ==================== def impute_missing(model, data, scaler): # 生成输入序列 dataset = TimeSeriesDataset(data, seq_len=24) loader = DataLoader(dataset, batch_size=32, shuffle=False) # 预测 model.eval() imputed = data.copy() with torch.no_grad(): for i, (x, _, _) in enumerate(loader): pred = model(x) start_idx = i * 32 * 24 end_idx = start_idx + x.shape[0] * 24 imputed[start_idx:end_idx] = pred.numpy() # 逆标准化 imputed = scaler.inverse_transform(imputed) return imputed # ==================== # 主流程执行 # ==================== if __name__ == "__main__": # 加载数据 missing_data, mask, scaler = load_data("鼓风湿度.csv") # 训练模型 model = train_model(missing_data) # 填补缺失 filled_data = impute_missing(model, missing_data, scaler) # 保存结果 pd.DataFrame(filled_data, columns=["鼓风湿度"]).to_csv("filled_result.csv") ``` #### 代码说明要点: 1. **数据预处理**: - 使用`StandardScaler`进行Z-Score标准化 - 生成随机缺失标记(10%缺失率) - 构造包含数特征和缺失标记的输入矩阵 2. **滑动窗口处理**: - 将时间序列划分为24步长的滑动窗口 - 每个样本包含数特征和缺失标记 3. **模型核心结构**: - **BiGRU层**:双向捕捉局部时序特征 - **Transformer层**:通过自注意力捕捉全局依赖 - **交叉注意力**:动态融合BiGRU和Transformer的特征 - **残差连接**:保留原始序列信息 4. **训练策略**: - 仅计算缺失位置的MSE损失 - 使用Adam优化器 - 滑动窗口增强数据多样性 5. **填补过程**: - 按滑动窗口进行批量预测 - 逆标准化恢复原始量纲 - 结果保存为CSV文件 #### 实际使用建议: 1. 调整超参数: ```python # 可调整参数示例 seq_len = 48 # 增大窗口长度 hidden_dim = 128 # 增加模型容量 n_heads = 8 # 更多注意力头 num_layers = 3 # 加深Transformer层数 ``` 2. 添加正则化: ```python optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) # L2正则化 ``` 3. 高级技巧: ```python # 添加位置编码 class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super().__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) self.register_buffer('pe', pe) def forward(self, x): return x + self.pe[:x.size(1)] ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值