多くの人が初めてCNN1Dをテーブルデータ処理に使用すると疑問を持つことがあります。なぜなら、テーブルデータは通常全結合ネットワークやツリーモデルで処理されるべきだと思われることが多いからです。しかし、実際のプロジェクトで特徴の次元間に明確な空間関係がある場合、CNN1Dはそのようなローカルパターンを自動的に捉えることができます。
全結合ネットワークはすべての特徴を同じように扱い、特徴間のローカル関連性を認識できないという欠点があります。一方、CNN1Dのカーネルは特徴次元上でスライドしてローカルパターンを検出します。例えば、医療チェックアップデータでは、血圧と心拍数などの隣接する特徴が重要な健康情報を含む可能性があります。CNN1Dはこの組み合わせ特徴を自動的に見つけることができます。
実際のテストでは、同じデータ量での訓練速度においてCNN1Dが全結合ネットワークよりも約30%速かったことが確認されました。これはカーネルパラメータの共有メカニズムがパラメータ数を大幅に減らすためです。ただし、特徴間に空間関係がない場合(例えば、完全に独立した統計指標など)、CNN1Dの利点はほとんど現れません。
データのリシェイピングのキーアプローチ
入力テンソルの三次元構造を理解する
PyTorchのConv1dでは、入力が(batch_size, channels, sequence_length)という三次元テンソルである必要があります。通常のCSVデータは(batch_size, features)という二次元構造なので、次の2つの変換が必要となります。
# 元のデータ形状 [128,11] (128個のサンプル、各サンプルに11個の特徴)
X = torch.randn(128, 11)
# 第1ステップ: チャンネル次元を追加 [128,1,11]
X = X.unsqueeze(1)
# または次の書き方でも同じ結果
X = X.reshape(-1, 1, 11)
ここで注意すべき点として、チャンネル数(channels)は特徴数ではありません。それは画像のRGBチャンネルのようなもので、一般的なテーブルデータでは1に設定します。特徴が明確なマルチチャンネル特性を持っている場合(例えば複数のセンサーシグナルなど)のみ調整します。
異なるサイズの入力に対処するアプローチ
実際のプロジェクトでは、サンプルの特徴長さが一貫していないことがよくあります。例えば、一部のサンプルが100個の時間ポイントを記録しているのに対し、他のサンプルは80個しかありません。このような場合、以下の方法を試すことができます:
- パディング(Padding): 短いサンプルをゼロ値で埋める
- 切り捨て(Truncating): 最初のN個の特徴を保持する
- 動的カーネル: 自動アダプティブプーリングを使用して出力サイズを統一する
from torch.nn.utils.rnn import pad_sequence
padded_X = pad_sequence(X_batch, batch_first=True)
X = X[:, :, :max_length] # 最初のmax_length個の特徴を保持
self.adaptive_pool = nn.AdaptiveAvgPool1d(output_size=1)
モデルアーキテクチャ設計の実践
基本的なアーキテクチャテンプレート
私はこのテンプレートを複数のプロジェクトで試し、多くのテーブルデータのシナリオで効果が良かったと報告しています。
class CNN1D(nn.Module):
def __init__(self, input_features=11):
super().__init__()
self.feature_extractor = nn.Sequential(
nn.Conv1d(1, 16, kernel_size=3, padding=1),
nn.BatchNorm1d(16),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(16, 32, kernel_size=3, padding=1),
nn.BatchNorm1d(32),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Flatten()
)
# 全結合層の入力サイズを動的に計算
with torch.no_grad():
dummy = torch.zeros(1, 1, input_features)
fc_input_size = self.feature_extractor(dummy).shape[1]
self.classifier = nn.Sequential(
nn.Linear(fc_input_size, 64),
nn.ReLU(),
nn.Linear(64, 2)
)
def forward(self, x):
features = self.feature_extractor(x)
return self.classifier(features)
重要な設計ポイント:
- padding='same': 特徴の長さを維持する(padding=1とkernel_size=3を使用して達成)
- バッチノーマライゼーション: 収束を加速するための必須のテクニック
- 動的全結合層: 異なる長さの入力特徴に対応する
カーネル設計の芸術
カーネルサイズは反復的な調整が必要なパラメータです。私の経験によると:
- 10-30個の特徴を持つテーブルの場合、kernel_size=3または5が効果的です
- 50個以上の特徴を持つ場合は、感受野を広げるためにアトランティックカーネル(dilated convolution)を検討します
- グループカーネル(group convolution)を試すことでパラメータ数を削減できます
# アトランティックカーネルの例
nn.Conv1d(16, 32, kernel_size=3, dilation=2, padding=2)
# グループカーネルの例
nn.Conv1d(32, 64, kernel_size=3, groups=16)
訓練とデバッグの実用的なヒント
学習率とバッチサイズの調整
CNN1Dは学習率に対して非常に敏感です。ここでは私のパラメータ調整の方法を紹介します。
- 初期学習率 = 0.001 * batch_size / 32
- OneCycleLR戦略を使用すると効果が抜群です
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
scheduler = torch.optim.lr_scheduler.OneCycleLR(
optimizer,
max_lr=0.01,
steps_per_epoch=len(train_loader),
epochs=50
)
クラスの不均衡に対処する
テーブルデータではしばしばクラスの不均衡が問題となります。私がよく使用する解決策:
- 重み付けクロスエントロピー損失
- 少数クラスのオーバーサンプリング
weights = torch.tensor([1.0, 5.0]) # 少数クラスの重みを大きくする
criterion = nn.CrossEntropyLoss(weight=weights)
from imblearn.over_sampling import SMOTE
smote = SMOTE()
X_res, y_res = smote.fit_resample(X_numpy, y_numpy)
特徴重要性の可視化
モデルがどの特徴に注目しているかを理解することはビジネス解釈にとって重要です。勾配加重クラス活性化マッピング(Grad-CAM)方法を使用できます。
def grad_cam(model, input_tensor):
model.eval()
input_tensor.requires_grad_()
# 最後の畳み込み層の出力を取得
features = model.feature_extractor[:-1](input_tensor)
# 勾配を計算
output = model(input_tensor)
class_idx = output.argmax()
output[0, class_idx].backward()
# 重みを計算
pooled_grad = torch.mean(features.grad, dim=[0, 2])
heatmap = torch.matmul(features.squeeze(0).T, pooled_grad)
return heatmap.detach().numpy()
このヒートマップは、予測結果に最も影響を与える特徴列を表示します。私は医療データ分析プロジェクトでこの方法を使用していくつかの重要なバイオマーカーを見つけました。