<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)]
```