我正在使用以下 1D CNN 模型根据时间序列数据进行特征预测任务:
import torch
import torch.nn as nn
import torch.nn.functional as F
class My1DCNN(nn.Module):
def __init__(self):
super(My1DCNN, self).__init__()
self.conv1 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=1)
self.conv2 = nn.Conv1d(in_channels=64, out_channels=64, kernel_size=5, stride=1, padding=1)
self.conv3 = nn.Conv1d(in_channels=64, out_channels=128, kernel_size=5, stride=1, padding=1)
self.conv4 = nn.Conv1d(in_channels=128, out_channels=128, kernel_size=5, stride=1, padding=1)
self.conv5 = nn.Conv1d(in_channels=128, out_channels=256, kernel_size=5, stride=1, padding=1)
self.maxpool = nn.MaxPool1d(kernel_size=2, stride=2)
self.fc1 = nn.Linear(1280, 239 * 4) # Adjust the input features to the correct flattened size
def forward(self, x, eeg_mask=None):
if eeg_mask is not None:
eeg_mask = eeg_mask.unsqueeze(1)
x = x * eeg_mask
x = F.relu(self.conv1(x))
x = self.maxpool(x)
x = F.relu(self.conv2(x))
x = self.maxpool(x)
x = F.relu(self.conv3(x))
x = self.maxpool(x)
x = F.relu(self.conv4(x))
x = self.maxpool(x)
x = F.relu(self.conv5(x))
x = self.maxpool(x)
x = x.view(x.size(0), -1) # Flatten keeping the batch size intact
x = self.fc1(x)
x = x.view(-1, 239, 4)
return x
该模型采用一批具有形状(批次、通道、时间)的序列并预测具有形状(批次、时间、特征)的特征。为了处理可变长度序列,我对它们进行了零填充,并使用
eeg_mask
来屏蔽填充的部分。
该模型训练良好,并且在测试数据上表现良好。然而,当我使用
torch.rand_like(x)
输入随机数据时,模型仍然表现得相当好。这表明该模型正在利用零填充部分的长度作为特征,这是不希望的。该模型应仅依赖于实际数据,而不是零值的长度。
如何修改我的一维 CNN 模型以确保它不依赖于序列的零填充部分的长度?我知道在 RNN 中,
pack_padded_sequence
可用于忽略填充。 CNN 有类似的方法吗?
参考:我检查了这个 Stack Overflow 问题 关于具有全局平均池化的 CNN 模型的时间序列零填充,但这对解决我的问题没有帮助。
虽然像 RNN 中的
pack_padded_sequence
那样的直接模拟在 CNN 中不存在,但你可以通过几种方法来减轻你的 1D CNN 模型对零填充序列长度的依赖:
- 掩蔽特征图:
-
不是仅仅在输入处掩蔽,而是在每次卷积和最大池化操作后,将掩码应用于特征图。这可以通过将
eeg_mask
上采样到特征图的空间分辨率,然后将其与特征图逐元素相乘来完成。
```python # 在 init 中添加一个上采样层 self.upsample = nn.Upsample(scale_factor=2, mode='nearest') # 调整 scale_factor 以匹配最大池化
# 在 forward 方法中 x = F.relu(self.conv1(x)) x = x * self.upsample(eeg_mask) # 在此处和后续层中应用掩码 x = self.maxpool(x) ... ```
- 因果卷积:
-
通过使用因果卷积(其中内核仅查看过去的时间步长,而不是未来的时间步长),你可以防止信息从填充部分泄漏。这可以通过在你的卷积层中设置
padding=0
并在每一层之后添加适当的填充量来实现,以保持所需的空间维度。
python
self.conv1 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=0)
...
x = F.relu(self.conv1(x))
x = F.pad(x, (0, 4)) # 右侧填充以保持维度
x = self.maxpool(x)
...
- 位置编码:
- 与依靠模型隐式地学习位置信息不同,你可以将明确的位置编码添加到输入中。这可以帮助模型区分实际数据点和填充标记,无论其在序列中的位置如何。你可以使用正弦位置编码或学习到的嵌入。
```python class PositionalEncoding(nn.Module): ... # 实现正弦或学习到的位置编码
# 在 init 中 self.positional_encoding = PositionalEncoding(...)
# 在 forward 方法中 x = self.positional_encoding(x) # 在应用掩码后添加位置编码 ... ```
- 注意力机制:
- 在你的 CNN 模型的顶部添加一个注意力机制可以帮助它关注与预测相关的输入的非填充部分。注意力层可以学习为实际数据点分配更高的权重,而忽略填充标记。
```python class Attention(nn.Module): ... # 实现注意力机制
# 在 init 中 self.attention = Attention(...)
# 在 forward 方法中 ... x = self.attention(x, eeg_mask) # 使用掩码来指导注意力 x = self.fc1(x) ... ```
评估和选择:
建议你尝试这些方法的不同组合,看看哪种方法最适合你的特定数据集和任务。仔细评估修改后的模型在具有不同填充长度的序列上的性能,以确保它不再依赖于填充信息。
标签:python,deep-learning,pytorch,time-series,conv-neural-network From: 78804721