まだタイトルない

アウトプット用です

Cassavaコンペ中に学んだことや自分用メモ

こんにちは

今回はCassavaコンペ期間中に導入したことや、自分用のメモを書きます。

HydraとMLflow Trackingの導入

下記サイトにお世話になりました。

小さくMLOpsを始めてみようと言うことで今回は2つのツールを入れてみました。鳥コンペの頃に参考にしたnotebookの影響でもともとyamlでコンフィグを書いていたのでHydraに関してはそこまで難しく有りませんでした。

MLFlowに関しては、ハイパラ管理のすすめ -ハイパーパラメータをHydra+MLflowで管理しよう-で紹介されていたwriterを使わさせていただきました。本当はコンペフォルダ直下のmlrunsフォルダに記録をして行きたかったのですがどうしても出来なかったです。ただ、デフォルトでD直下に一応安定して記録されるためそこで妥協しました。コマンドライン立ち上げてDに移動してmlflow uiすれば済むので案外楽です。

実際のコードではHydraのデコレータをmainに付与しておけばconfigを読み込めます。

@hydra.main(config_name="config/config")
def main(cfg: DictConfig):
    # 個人的にはこっちの書き方より
    exp_name = cfg.exp_name
    # こっちの書き方のほうが、kaggleに持っていったときも動くので好きです。
    exp_name = cfg['exp_name']

Hydraのマルチラン機能を実行した場合別々のグラフになってくれるので嬉しいです。 python train_exp15.py -m optimizer.name=SGD optimizer.params.lr=1.0e-5,1.0e-4,1.0e-3,1.0e-2

f:id:teyoblog:20210221200120p:plain

run nameも最悪あとから Rename 出来ます。 f:id:teyoblog:20210221200130p:plain

timmの使用

pytorchで学習済みモデルを使うにはtorchvisionで使うことも出来ますが、今回はtimmを使用。様々なモデルを使えるので、初心者はここで使えるものから選ぶだけで十分だと思います。また、コンペ期間中にvit_deit_base_patch16_384が使えるようになりと後進も頻繁にされているようです。

小噺になりますが、kaggleのTPU環境は最新環境しか選べないようでpytorch側の仕様変更1でtimmのimportでエラーがでるようになりました。しかし数日の間にtimmのほうが仕様変更に対応してくれたため、コンペ期間中にTPUでの学習を継続して行うことが出来ました。

timmのインポート

kaggle内のdataset2として上げてくれている人がいるのでそれをinputに追加することで使えます。この方法だとインターネットが使用できない推論用ノートブックでも使用可能。

import sys
sys.path.append('../input/pytorch-image-models/pytorch-image-models-master')

import timm

上記のように最新版をほしいときは直接参照する。今回は推論はGPUでするため、学習時は仕様変更対応バージョンを使って、推論時はDatasetのものを使いました。

!pip install git+https://github.com/rwightman/pytorch-image-models.git

import timm

モデル準備周りの自分用メモ

モデルの取得

model = timm.create_model(model_name, pretrained=pretrained)
imagenet用で最後が1000クラスになっているため、全結合層をタスクにあったものに差し替える。(図の最後のfcの部分) f:id:teyoblog:20210221200215p:plain

# 最終層の差し替え(キャッサバコンペは5クラス分類)
n_features = model.fc.in_features
model.fc = nn.Linear(n_features, 5)

resnet系だとfc,efficientnetはclassifier,ViTはheadになっているため注意が必要

Finetuning

モデルの最終層以外のrequires_gradをFalseにすることでパラメータが更新されることを防ぐ。公開ノートできれいな実装があったため、今後もこれにお世話になると思います。

class CustomResNext(nn.Module):
    def __init__(self, model_name='resnext50_32x4d', pretrained=False):
        super().__init__()
        self.model = timm.create_model(model_name, pretrained=pretrained)
        n_features = self.model.fc.in_features
        self.model.fc = nn.Linear(n_features, CFG.target_size)

    def forward(self, x):
        x = self.model(x)
        return x
    
    def freeze(self):
        # To freeze the residual layers
        for param in self.model.parameters():
            param.requires_grad = False

        for param in self.model.fc.parameters():
            param.requires_grad = True
    
    def unfreeze(self):
        # Unfreeze all layers
        for param in self.model.parameters():
            param.requires_grad = True

model_name = 'resnext50_32x4d'
if model_name=='resnext50_32x4d':
    model = CustomResNext(model_name, pretrained=True)
model.freeze()
学習したモデルの取り回し

trainデータで学習したものを保存して、testデータを推論するときに読み込んで使います。困ったときはココにいつも見に行っています。今回のコンペ期間中にはTPU上でmodelを保存してしまいサルベージが必要になったり、model.state_dictとカッコを忘れてしまって学習し直しになったりしました・・・。

  1. statedict()
# セーブ
torch.save(model.state_dict(), PATH)

# ロード
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()
  1. モデル全体
# セーブ
torch.save(model, PATH)
# ロード
model = torch.load(PATH)
model.eval()
  • 保存時と違うデバイスでロードするときはmodel.load_state_dict(torch.load(PATH, map_location=device))とデバイスを指定する

TTA

TTAchを使ってTTAをしました。 学習時のData augmentationは確率的に事前設定した処理を掛け合わせる(例:1/2の確率で左右反転させる)のですが、TTAでは左右反転させたものとさせてないものの2枚の推論結果を使います。それが通常のTransformsでうまく出来ているのかわからなかったため、これでやりました。7種の変換をカスタムして、modelをラッパーすればいいです。推論結果の混ぜ方も選択できます。 コンペ中の推論は下記のようにやってましたが上手な回し方なのかはわかりません。

def inference(model, states, test_loader, device):
    model.to(device)
    tk0 = tqdm(enumerate(test_loader), total=len(test_loader))
    probs = []
    for i, (images) in tk0:
        images = images.to(device)
        avg_preds = []
        for state in states:
            model.load_state_dict(state['model'])
            model.eval()
            transforms = tta.Compose(
                 [
                     tta.HorizontalFlip(),
                     tta.VerticalFlip(),
                     #tta.Rotate90(angles=[0, 90, 180]),
                     #tta.Multiply(factors=[0.9, 1, 1.1]),     
                 ])
            tta_models = []
            tta_models.append(tta.ClassificationTTAWrapper(model, transforms,merge_mode='mean'))
            for net in tta_models:
                with torch.no_grad():
                    y_preds = net(images)
                avg_preds.append(y_preds.softmax(1).to('cpu').numpy())
        avg_preds = np.mean(avg_preds, axis=0)
        probs.append(avg_preds)
    probs = np.concatenate(probs)
    return probs

今回はTTAは軽くても良さそうな雰囲気でFlipのみで済みましたが、もう少し色々やったほうがいい場合は対応できないので結局TransformsでのTTAは勉強する必要が有る気がします。

TPUでの学習

公開ノートがあったためkaggleのTPUリソースを活用できました。efficientnet-b4において、GPUだとimagesize384、batchsize16のところ、TPUだとimagesize512、batchsize64で学習できます。

nprocsが1のときと1より多いときでは並列に学習していくため少し処理が変わってきます。自分は8にしたらうまく動かなかったためコンペ期間中は1で過ごしていました。

LINE通知

コピペで使える。Kaggleでの実験を効率化する小技まとめで紹介されているLINEに通知を導入しました。限られたリソースが無駄にならないようにepoch毎にスコアを通知していました。まだまだ自分のコードに自身が持てないので、学習が思った感じに進んでない場合など事故が起きた場合すぐに気づいて止めることができます。何回か助けられました。これからkaggleを始める人にはぜひおすすめしたい取り組み方です。

f:id:teyoblog:20210221200651p:plain
学習が思ってない感じで進んでる図

おわりに

以上になります。自分のコンペ中のコードやメモからピックしましたが、もう少しあとから活用できるようなメモをつけれるようになりたいと感じました。