【Jane Street Market Prediction】CNNモデルを作ってみた②

前回の記事は、PytorchでCNN(1次元畳み込み)を用いたモデルとして、単純な構造のCNNとTCN(Temporal Convolutional Network)を構築してみました。
CNNのスコアが1745.844でTCNが2091.370で、若干ですが、TCNの方が精度が良いという結果が得られました(スコアはかなり悪い)。

今回は、TCNを改良してスコアアップを図ってみたので、簡単に記事にしておきます。
現時点では、TCNが5785.452まで向上しました。

※本記事は、公開しているNotebookをまとめたものとなっております。
・CNNの学習に関するNotebook ⇒ こちら, 推論に関するNotebook ⇒ こちら
・TCNの学習に関するNotebook ⇒ こちら, 推論に関するNotebook ⇒ こちら

変更①全結合層の追加

最初のモデルでは、TCN-全結合層×1-出力層(全結合層)の構成をしておりました。
出力層の前の全結合層を追加することで精度向上が見られました。

  • TCN – 全結合層×1 – 出力層(全結合層):2091.370
  • TCN – 全結合層×2 – 出力層(全結合層):5785.452
  • TCN – 全結合層×3 – 出力層(全結合層):5132.795

上記の結果から、全結合層は出力層を含めて、3層が良いみたいです。
単純に層を増やすだけでは、精度向上に限界があるようです。

変更②ネットワーク構成の修正

TCNのネットワークを丁寧に書くようにしました。

以下の図は、ネットワークをprint出力したものになりますが、fc2~fc3でドロップアウト層やバッチノーマライゼーション層が省略されているのがわかります。

これは、横着してfc1で利用しているドロップアウト層やバッチノーマライゼーション層をそのままfc2やfc3に適用してしまっているためです。(もしかしたら、Pytorch内でも共通の認識をされている?)

これをきちんと分けて、以下のようになるように修正しました。

変更③ 期間幅(window_size)を変えてみる

最初のモデルでは、一旦、期間幅を20としてました。
この期間幅のサイズに応じで、モデルはどれだけ過去のデータを参考にするのかを決めることができます。

今回は、この期間幅を短期(window_size=5)と長期(window_size=100)の場合で試してみました。
結果は、以下の通り。

  • 短期(window_size=5):6951.875
  • 中期(window_size=20):5785.452
  • 長期(window_size=100):5227.883

上記の結果から、期間は短いほうが良いことがわかりました。
長期の時系列データとして扱わない方が精度が良くなるのかもしれません。

(追記)
さらに期間を短くしてみた場合(window_size=3)、スコアは0.000となりました。
出力結果を見るとaction=1に偏っていたことから過学習の可能性がありました。

変更④ 学習データを選別する

今回のコンペでは、weightが0の予測値はスコア計算に利用されません。

上で作成してきたモデルの学習データにはweight=0のものを含めていました。
そこで、weight=0の場合は、バッチに含めないように処理を修正してみました。

期間幅が5のモデルで比較を行った結果が以下の通り。

  • weight=0を含む場合:6951.875
  • weight=0を含まない場合:5559.145

スコアが良くなると思っていただけに残念な結果になりました。
時系列でweight=0を含む場合があることが多少作用したのでしょうか。原因は、わかりません。

主なコードの変更点としては、

  • Dataset Classでweightを返すようにしたこと
  • BatchSamplerクラスをオーバライド
  • DataLoaderでは新しく定義したBatchSamplerを用いてバッチデータを作成する

詳細については、Notebookを見てください。
以下に変更したコードを参考程度に載せておきます。

from torch.utils.data import Dataset
from torch import nn
 
class JSMP_Dataset(Dataset):
     
    def __init__(self, file_path, window_size):
        # valiables
        self.file_path = file_path
        self.window_size = window_size
        
        # read csv
        train = pd.read_csv(file_path)
        
        # pre processing
        train = train.query('date > 85').reset_index(drop = True) 
        #train = train[train['weight'] != 0]
        train.fillna(train.mean(),inplace=True)
        train['action'] = ((train['resp'].values) > 0).astype(int)
        
        resp_cols = ['resp_1', 'resp_2', 'resp_3', 'resp', 'resp_4']
        self.features = [c for c in train.columns if "feature" in c]
        self.f_mean = np.mean(train[self.features[1:]].values,axis=0)
        
        self.X_train = train.loc[:, train.columns.str.contains('feature')].values
        self.y_train = np.stack([(train[c] > 0).astype('int') for c in resp_cols]).T
        
        self.X_train = torch.from_numpy(self.X_train).float()
        self.y_train = torch.from_numpy(self.y_train).float()
        
        self.X_weight = torch.from_numpy(train['weight'].values)
        
        # reduce memory
        del train
        gc.collect()
 
    def __len__(self):
        return len(self.X_train) - self.window_size
     
    def __getitem__(self, i):
        weight = self.X_weight[i + self.window_size - 1]
        data = self.X_train[i:(i+ self.window_size), :] 
        label = self.y_train[i + self.window_size - 1]
 
        return weight, data, label
from torch.utils.data import RandomSampler
from torch.utils.data import BatchSampler
class NonZeroWeightBatchSampler(BatchSampler):
    def __init__(self, batch_size, dataset):
        self.sampler = RandomSampler(dataset)
        self.batch_size = batch_size
        self.dataset = dataset
    def __iter__(self):
        batch = []
        for idx in self.sampler:
            # avoid using weight = 0 data
            if self.dataset[idx][0].item() == 0:
                continue
            else:
                batch.append(idx)
            if len(batch) == self.batch_size:
                yield batch
                batch = []
        if len(batch) > 0:
            yield batch
# make DataLoder
train_dataloader = torch.utils.data.DataLoader(train_ds, batch_sampler=batch_sampler_train)
valid_dataloader = torch.utils.data.DataLoader(valid_ds, batch_sampler=batch_sampler_valid)
# dict
dataloaders_dict = {'train': train_dataloader,
                    'val'  : valid_dataloader}

まとめ

今回は簡単なTCNモデルの改良をしてみました。
ここまでの結果から、window_size=5で全結合層が3層(出力層を含む)がスコア6951.875と一番良かったです。

ここからさらに改善をしていくとなると、アンサンブル学習にするとかになるのでしょうか。
今回は、検証データの損失値が最小となるモデルを最良のモデルとしていたので、Accuracyが最高の場合を最良のモデルにした場合やそれら二つの平均を予測値とするなど改良はできそうな気がします。

コメント

タイトルとURLをコピーしました