こんにちは
今回はCassavaコンペ期間中に導入したことや、自分用のメモを書きます。
HydraとMLflow Trackingの導入
下記サイトにお世話になりました。
- 公式ドキュメント
- 小さく始めて大きく育てるMLOps2020
- ハイパラ管理のすすめ -ハイパーパラメータをHydra+MLflowで管理しよう-
- Hydraで始めるハイパラ管理
- Python: 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
run nameも最悪あとから Rename 出来ます。
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の部分)
# 最終層の差し替え(キャッサバコンペは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
とカッコを忘れてしまって学習し直しになったりしました・・・。
- statedict()
# セーブ torch.save(model.state_dict(), PATH) # ロード model = TheModelClass(*args, **kwargs) model.load_state_dict(torch.load(PATH)) model.eval()
- モデル全体
# セーブ torch.save(model, PATH) # ロード model = torch.load(PATH) model.eval()
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を始める人にはぜひおすすめしたい取り組み方です。
おわりに
以上になります。自分のコンペ中のコードやメモからピックしましたが、もう少しあとから活用できるようなメモをつけれるようになりたいと感じました。