catlaの備忘録

忘れないように書いておきます。

CUDA/Dockerの環境構築

目標

初期状態のUbuntu18.04 LTSにCuda, Docker, nvidia-dockerの環境を構築する。

想定環境

  • OS: Ubuntu 18.04 LTS (初期状態)
  • CUDAサポートのGPUが刺さっている

Step0 : 前準備

$ sudo apt update
$ sudo apt -y upgrade

Step1 : GPUドライバ(CUDA)のインストール

Cuda Toolkitの公式サイトから以下のように選択し、表示されるものに従ってダウンロード。(ここではCuda10.2)

過去のバージョンはLegacy Releasesからインストール手順が参照可能。

f:id:katsura_jp:20190927100657p:plain

$ wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin

$ sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600

$ sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub

" aptレポジトリに追加
$ sudo add-apt-repository "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/ /"

$ sudo apt-get update

" CUDAのインストール
$ sudo apt-get -y install cuda

" 確認(表示されればOK)
$ nvidia-smi


" Pathを通す
$ export PATH="/usr/local/cuda/bin:$PATH"
$ export LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH"

Step2 : Dockerのインストール

公式のインストールガイドに従ってdocker-ceをインストールする。

" (任意) 必要に応じてaptのアップデート
$ sudo apt-get update

" HTTPSのためのソフトフェアをインストール
$ sudo apt-get -y install \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg-agent \
     software-properties-common

" Docker公式のGOG keyを追加
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88

" aptレポジトリに追加
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

" aptのアップデート
$ sudo apt-get update

" docker-ceのインストール
$ sudo apt-get -y install docker-ce docker-ce-cli containerd.io

" (任意) 正しくインストールされたか確認
$ sudo docker run --rm hello-world

" (任意) 必要に応じてuserグループに追加
" 追加するとsudoを使用する必要がなくなる。
$ sudo usermod -aG docker $USER
$ exit
【余談】docker-ceとは?

DockerはCE(コミュニティエディション)とEE(エンタープライズエディション)があり、無料版と有料版という違いがある.(個人用か商業用かの違いみたいなもの)

Step3 : nvidia-dockerのインストール

githubにある公式のレポジトリにあるREADMEのQuickStartを参照しながらインストールを行う。

github.com

最新のDocker19.03のリリースで、Nvidia GPUがDockerでネイティブにサポートされるようになったためnvidia-docker2の使用は非推奨になりました。

Docker19.03の説明は次の記事がわかりやすい。

medium.com

nvidia-docker2では--runtime=nvidiaのオプションを使いますが、19.03では--gpusのオプションが追加されたのでこれを使う。

" レポジトリのパッケージを追加する
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -sL https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
$ sudo systemctl restart docker

" (任意) 正しくインストールされたか確認
$ docker run --gpus all nvidia/cuda:9.0-base nvidia-smi

nvidia-smiの画面が出れば完了!!

PyTorchのSchedulerまとめ[torch v1.1.0]

目次

以前の記事のアクセス数がこのブログ全体の90%を占めているので最新版に更新します。ちなみに前のは v0.4だったかと思います。

Pytorchのscheduler公式ドキュメントは こちら

v1.1.0のソースはこちら

PyTorch公式のscheduler一覧

  • LambdaLR
  • StepLR
  • MultiStepLR
  • ExponentialLR
  • CosineAnnealingLR
  • ReduceLROnPlateau
  • CyclicLR <- New
  • CosineAnnealingWarmRestarts <- New

本題に移る前に

今回のschedulerのコードはgoogle colabで公開してあります。自分でパラメータをいじりながらschedulerの動きを見たい場合は、以下の手順に従ってもらえればと思います。colabについて分からなければ、「ググればええねん」って、ゆたぼんが言ってます。

まず、Googleアカウントがある前提として、以下を開いてください。

colab.research.google.com

f:id:katsura_jp:20190724140726p:plain

左上のPLAYGROUNDで開くを押してもらえれば使えます。

実行する時に、なんか色々出てきますが適当にぽちぽちしたら使えます、たぶん。

v1.1.0の問題点について

残念ながらv1.1.0のStepLR, MultiStepLRの二つがバグってます。各自で修正してください。

masterブランチやv1.1以外では正常に動作するので、pip show torch等のコマンドで場所調べて、直接ライブラリにコピペなど。

どんなバグかというと、学習率が下がるタイミングのとき、下げたい割合の二乗分下が ります るように見えます(見えるだけ)。 f:id:katsura_jp:20190723200535p:plain

以下では、StepLR, MultiStepLRだけv1.01における説明になります。

[追記(2019/07/24)]

scheduler側からget_lrで学習率を取得すると上記のようにおかしな挙動になりますが、optimizer側から学習率を取得すると期待通りの学習率が得られますので学習自体には問題はないと思われます。学習率を記録する際は気をつけましょう。@hara_pets さん、ご報告ありがとうございます。

f:id:katsura_jp:20190724191658p:plain

LambdaLR


引数一覧

  • optimizer : 最適化のインスタンスを指定
  • lr_lambda : ラムダ式や関数を指定
  • last_epoch : 指定したことないのでいじらなくていいと思う(個人的意見)。深く理解したい人はソースを見てください。

このスケジューラはlr_lambdaにオリジナルの式を入れられるのが最大のポイントです。引数には、stepが呼ばれた回数が与えられ、戻り値としてbase_lr(optimizerに指定したlr)からどれほど変化させたいかの比率を返すようにしてください。つまり、学習率はbase_lr * 戻り値となります。

また、継承を用いる事で、自作のschedulerを簡単に作成することができます。 継承でLambdaLRを使う際は pytorch_transformers/optimization.py を参考にしてみるといいかもしれません。

example

ラムダ式を与えた場合

scheduler = LambdaLR(optimizer, lr_lambda = lambda epoch: 0.95 ** epoch)

f:id:katsura_jp:20190724131255p:plain

関数を渡した場合

def func(epoch):
    if epoch < 40:
        return 0.5
    elif epoch < 70:
        return 0.5**2
    elif epoch < 90:
        return 0.5**3
    else:
        return 0.5**4
scheduler = LambdaLR(optimizer, lr_lambda = func)

f:id:katsura_jp:20190724131354p:plain

継承を用いた場合

class WarmupConstantSchedule(torch.optim.lr_scheduler.LambdaLR):
  # Reference : https://github.com/huggingface/pytorch-transformers/blob/master/pytorch_transformers/optimization.py#L33
    """ Linear warmup and then constant.
        Linearly increases learning rate schedule from 0 to 1 over `warmup_steps` training steps.
        Keeps learning rate schedule equal to 1. after warmup_steps.
    """
    def __init__(self, optimizer, warmup_steps, last_epoch=-1):

        def lr_lambda(step):
            if step < warmup_steps:
                return float(step) / float(max(1.0, warmup_steps))
            return 1.

        super(WarmupConstantSchedule, self).__init__(optimizer, lr_lambda, last_epoch=last_epoch)
optimizer = torch.optim.SGD(model.parameters(), lr=0.05, momentum=0.9, weight_decay=1e-5)
scheduler = WarmupConstantSchedule(optimizer, warmup_steps=10)
for step in range(100):
  scheduler.step()

f:id:katsura_jp:20190724131525p:plain

StepLR


引数一覧

  • optimizer : 省略
  • step_size : 何ステップごとに学習率を減少させるかの値
  • gamma : 学習率の減少率
  • last_epoch : 省略

example

scheduler = StepLR(optimizer, step_size=200, gamma=0.5)

f:id:katsura_jp:20190724132211p:plain

MultiStepLR


引数一覧

  • optimizer : 省略
  • milestones : 減少させたいstepのリスト
  • gamma : 学習率の減少率
  • last_epoch : 省略

StepLRは減衰ステップが一つに対し、これは複数取れます。注意点として、milestonesには、ステップの小さい順のリストを与えてください。 つまり、10,30,50のステップ数で減衰させたい場合は、[10,30,50]と与えてください。

このschedulerはImageNet等のベンチマークでSOTAなモデルの実験でも使われていて、最適化関数はmomentumSGD、全体のepoch数に対して50%, 75%くらいで学習率を0.1倍にしていることが多いような気がします。

example

scheduler = MultiStepLR(optimizer, milestones=[200, 350], gamma=0.5)

f:id:katsura_jp:20190724132340p:plain

ExponentialLR


引数一覧

  • optimizer : 省略
  • gamma : 学習率の減少率
  • last_epoch : 省略

簡潔に説明するとgamma**step回数が学習率に乗算されます。

example

scheduler = ExponentialLR(optimizer, gamma=0.95)

f:id:katsura_jp:20190724141706p:plain

CosineAnnealingLR


引数一覧

  • optimizer : 省略
  • T_max : 半周期のステップサイズ
  • eta_min : 下限学習率
  • last_epoch : 省略

example

scheduler = CosineAnnealingLR(optimizer, T_max=100, eta_min=0.001)

f:id:katsura_jp:20190724132845p:plain

ReduceLROnPlateau


引数一覧

  • optimizer : 省略
  • mode : 監視されている要因が'min'だと下がっているか、'max'だと上がっているかを指定
  • factor : 学習率の減衰率
  • patience : 何ステップ向上しなければ減衰するかの値
  • verbose : 減衰するとき出力してれるかどうか
  • threshold : threshold_modeで説明するのに使われてる値
  • threshold_mode : 'rel'と'abs'の2種類のどちらかを取ります。 'rel'の場合、減衰の起因となる閾値をmaxモードだとbest*(1+threshold)、minモードだとbest*(1-threshold)となります。'abs'の場合は、maxモードだとbest-threshold、minモードだとthresh+thresholdとなります。
  • cooldown : 学習率が下がってから、factorを再監視するまでのステップ数
  • min_lr : 最小学習率
  • eps : nanとかInf回避用の微小数

また、監視する要因はschedulerの関数に存在するstepの引数に与えます。

また、大げさに例をあげると、 - mode : 'min' - patience : 10 - threshold : 0.5 - threshold_mode : 'abs' のように指定すると、監視している値のbestが0.01とすると、 0.01+0.5=0.51よりも高い値を10回連続で取ると減衰します。

example

scheduler = ReduceLROnPlateau(optimizer, 'min') 
for epoch in range(10):
    scheduler.step(val_loss) #val_lossが下がらなければ減衰

CyclicLR


引数一覧

  • optimizer : 省略
  • base_lr (float or list) : 下限学習率(初期値)
  • max_lr : 上限学習率
  • step_size_up : 学習率上昇のサイクル数
  • step_size_down : 学習率減少のサイクル数。Noneの時は、step_size_upと一致。
  • mode : {"triangular", "triangular2", "exp_range"}から選択。scale_fnNoneでなければ、無視される。
  • gamma : modeが'exp_range'の場合の減少率
  • scale_fn : 任意のスケールを満たしたい場合、関数(ラムダ式等)を渡す。渡した場合、modeは無視される。
  • scale_mode : {'cycle', 'iterations'}から選択。scale_fnの引数がcycle数なのかiteration数なのかを決定。
  • cycle_momentum : Trueならば、base_momentummax_momentumを逆に循環させる。また、max_momentumを初期値にする。
  • base_momentum : 最適化関数におけるmomentumのサイクルの下限
  • max_momentum : momentumのサイクルの上限
  • last_epoch : 省略

新たに追加されたschedulerです。Fast Geometric Ensembling等で使われています。

example1

scheduler = CyclicLR(optimizer, base_lr=0.001, max_lr=0.1,
                                     step_size_up=50, step_size_down=100, 
                                     mode='triangular')

f:id:katsura_jp:20190724133120p:plain

example2

scheduler = CyclicLR(optimizer, base_lr=0.001, max_lr=0.1,
                                      step_size_up=50, step_size_down=None, 
                                      mode='triangular2')

f:id:katsura_jp:20190724133252p:plain

example3

scheduler = CyclicLR(optimizer, base_lr=0.001, max_lr=0.1,
                                     step_size_up=50, step_size_down=None, 
                                     mode='exp_range', gamma=0.995)

f:id:katsura_jp:20190724133403p:plain

CosineAnnealingWarmRestarts


引数一覧

  • optimizer : 省略
  • T_0 : 初期の繰りかえし回数
  • T_mult : サイクルのスケール倍率
  • eta_min : 下限学習率
  • last_epoch : 省略

これも新たに追加されたschedulerです。SnapShot Ensemble等で使われています。

example

scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=50, T_mult=2, eta_min=0.001)

f:id:katsura_jp:20190724134004p:plain

Schedulerの自作

Schedulerを自作する場合、_LRScheduler または LambdaLRを継承すると便利です。LambdaLRの継承を使った自作は上で書いたので、ここでは_LRSchedulerを継承した場合での作成を行います。

optimizerの学習率更新は、scheduler.step()が呼ばれた際に更新されますが、その際、self.last_epochがインクリメントされます。厳密には、このget_lrが呼ばれる前に行われます。step内でself.get_lrが呼ばれ、その戻り値を新たな学習率に置き換えるように設計されてます。

つまり、自作関数でschedulerを作成するならば、get_lrを自作するのにほとんど等しいです。

ということで、ここではCosineAnnealingWarmRestartsにWarmupとサイクル毎に上限学習率を減らしていくschedulerを作成します。

githubにもあります。 github.com

class CosineAnnealingWarmUpRestarts(_LRScheduler):
    def __init__(self, optimizer, T_0, T_mult=1, eta_max=0.1, T_up=0, gamma=1., last_epoch=-1):
        if T_0 <= 0 or not isinstance(T_0, int):
            raise ValueError("Expected positive integer T_0, but got {}".format(T_0))
        if T_mult < 1 or not isinstance(T_mult, int):
            raise ValueError("Expected integer T_mult >= 1, but got {}".format(T_mult))
        if T_up < 0 or not isinstance(T_up, int):
            raise ValueError("Expected positive integer T_up, but got {}".format(T_up))
        self.T_0 = T_0
        self.T_mult = T_mult
        self.base_eta_max = eta_max
        self.eta_max = eta_max
        self.T_up = T_up
        self.T_i = T_0
        self.gamma = gamma
        self.cycle = 0
        super(CosineAnnealingWarmUpRestarts, self).__init__(optimizer, last_epoch)
        self.T_cur = last_epoch
    
    def get_lr(self):
        if self.T_cur == -1:
            return self.base_lrs
        elif self.T_cur < self.T_up:
            return [(self.eta_max - base_lr)*self.T_cur / self.T_up + base_lr for base_lr in self.base_lrs]
        else:
            return [base_lr + (self.eta_max - base_lr) * (1 + math.cos(math.pi * (self.T_cur-self.T_up) / (self.T_i - self.T_up))) / 2
                    for base_lr in self.base_lrs]

    def step(self, epoch=None):
        if epoch is None:
            epoch = self.last_epoch + 1
            self.T_cur = self.T_cur + 1
            if self.T_cur >= self.T_i:
                self.cycle += 1
                self.T_cur = self.T_cur - self.T_i
                self.T_i = (self.T_i - self.T_up) * self.T_mult + self.T_up
        else:
            if epoch >= self.T_0:
                if self.T_mult == 1:
                    self.T_cur = epoch % self.T_0
                    self.cycle = epoch // self.T_0
                else:
                    n = int(math.log((epoch / self.T_0 * (self.T_mult - 1) + 1), self.T_mult))
                    self.cycle = n
                    self.T_cur = epoch - self.T_0 * (self.T_mult ** n - 1) / (self.T_mult - 1)
                    self.T_i = self.T_0 * self.T_mult ** (n)
            else:
                self.T_i = self.T_0
                self.T_cur = epoch
                
        self.eta_max = self.base_eta_max * (self.gamma**self.cycle)
        self.last_epoch = math.floor(epoch)
        for param_group, lr in zip(self.optimizer.param_groups, self.get_lr()):
            param_group['lr'] = lr

example1

scheduler = CosineAnnealingWarmUpRestarts(optimizer, T_0=150, T_mult=1, eta_max=0.1,  T_up=10, gamma=0.5)

f:id:katsura_jp:20190724134858p:plain

example2

scheduler = CosineAnnealingWarmUpRestarts(optimizer, T_0=50, T_mult=2, eta_max=0.1,  T_up=10, gamma=0.5)

f:id:katsura_jp:20190724134912p:plain

example3

scheduler = CosineAnnealingWarmUpRestarts(optimizer, T_0=100, T_mult=1, eta_max=0.1,  T_up=10, gamma=0.5)

f:id:katsura_jp:20190724134925p:plain

AlbumentationsのTransformsをまとめたレポジトリを公開しました!!

Kaggleのイメージでもalbumentationsが導入されるということで、albumentationsで画像を変換するTransformsクラスをまとめました。

github.com

albumentationsの公式ドキュメントは こちら

ご指摘などあれば、Twitterなどでいただけると幸いです。

時間があれば、Composition の使用例や google colaboratory でノートブックを公開するかもしれません。

雑談

作成するにあたり、trelloで管理すると楽でした笑 f:id:katsura_jp:20190516190755p:plain

1変数多項式のベクトル空間の直交化[メモ]

問題

1変数多項式のなすベクトル空間 P_2(\bf{R})において、 f, g \in P_2(\bf{R})に対して、 \begin{align} (f, g) = \int_{-1}^{1} f(x)g(x) dx \end{align} で内積を定める。このとき、 1, x, x^2に対してグラム・シュミットの直交化法を適用して、 P_2(\bf{R})の正規直交基底をつくれ。

解法(グラム・シュミットの直交化法の説明は省く)

求める正規直交基底を (w_1, w_2, w_3)とする。 問題の積分内積としているだけなので、

  1.  (1, 1) = \int_{-1}^{1} dx = 2より、
     w_1 = \frac{1}{\sqrt{(1,1)}} = \frac{\sqrt{2}}{2}


2.  (x, w_1) = \int_{-1}^{1} \frac{\sqrt{2}}{2} x dx = 0 ,
 f(x) = x - (x, w_1) w_1 = x - 0 \times \frac{\sqrt{2}}{2}
よって、
 (f, f) = \int_{-1}^{1} x^2 = \frac{2}{3}
 w_2 = \frac{f}{\sqrt{(f,f)}} = \sqrt{\frac{3}{2}} x = \frac{\sqrt{6}}{2} x


3.  (x^2, w_1) = \int_{-1}^{1}  \frac{\sqrt{2}}{2} x^2 dx =  \frac{\sqrt{2}}{2} \times \frac{2}{3} =  \frac{\sqrt{2}}{3},
 (x^2, w_2) = \int_{-1}^{1} x^2(\sqrt{\frac{3}{2}} x) dx = \int_{-1}^{1} \sqrt{\frac{3}{2}}x^3 dx = 0
よって、
 g(x) = x^2 - (x^2, w_1)w_1 - (x^2, w_2)w_2 = x^2 - \frac{\sqrt{2}}{3} \times \frac{\sqrt{2}}{2} - 0 \times \sqrt{\frac{3}{2}} x = x^2 - \frac{1}{3}
 (g,g) = \int_{-1}^{1} (x^2 - \frac{1}{3})^2 = \frac{2}{5} - \frac{2}{9} = \frac{8}{45}
ゆえに、
 w_3 = \frac{g}{\sqrt{(g,g)}} = \frac{3\sqrt{5}}{2\sqrt{2}}(x^2 - \frac{1}{3}) = \frac{\sqrt{10}}{4}(3x^2 - 1)


 (w_1, w_2, w_3) = (\frac{\sqrt{2}}{2},\frac{\sqrt{6}}{2} x, \frac{\sqrt{10}}{4}(3x^2 - 1))

PyTorchのSchedulerまとめ

[追記:2019/07/24] 最新版更新してます。

katsura-jp.hatenablog.com

目次

最近暇な時間にPyTorchのReferenceを細かくみたり実装をみたりしているのですが、 今回スケジューラを見ていて、グラフとかあった方嬉しいね(百聞は一見にしかず)と思ったので、グラフをまとめて作りました。

Pytorchのscheduler公式ドキュメントは こちら

PyTorchライブラリ内にあるscheduler

PyTorchでもともと存在するschedulerは以下のとおり

  • LambdaLR
  • StepLR
  • MultiStepLR
  • ExponentialLR
  • CosineAnnealingLR
  • ReduceLROnPlateau

今回は、ReduceLROnPlateau以外の可視化を説明を交えて紹介します。 (ReduceLROnPlateauはloss等が関係するので可視化がめんど... 難しかったので割愛で)

基本設定

今回の記事で使われるパラメータとして

  • epoch数: 100
  • 初期の学習率: 0.01
model = nn.Sequential(nn.Linear(2, 2), nn.Linear(2, 2))
optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=0.01, momentum=0.9)

LambdaLR


引数一覧

  • optimizer : 最適化のインスタンスを指定
  • lr_lambda : ラムダ式や関数を指定
  • last_epoch : 指定したことないのでいじらなくていいと思う(個人的意見)。深く理解したい人はソースを見てください。

このスケジューラはlr_lambdaにオリジナルの式を入れられるのが最大のポイントです。引数には、stepが呼ばれた回数が与えられ、戻り値としてbase_lr(optimizerに指定したlr)からどれほど変化させたいかの比率を返すようにしてください。つまり、学習率はbase_lr * 戻り値となります。

example

scheduler = LambdaLR(optimizer, lr_lambda = lambda epoch: 0.95 ** epoch)
for epoch in range(0, 100): #ここは以下省略
    scheduler.step() 

f:id:katsura_jp:20190130182249p:plain

  • 関数を渡した場合
def func(epoch):
    if epoch < 40:
        return 0.5
    elif epoch < 70:
        return 0.5**2
    elif epoch < 90:
        return 0.5**3
    else:
        return 0.5**4
scheduler = LambdaLR(optimizer, lr_lambda = func)

f:id:katsura_jp:20190130182322p:plain

StepLR


引数一覧

  • optimizer : 省略
  • step_size : 何ステップごとに減衰させるかの値
  • gamma : 減衰率
  • last_epoch : 省略

これは見てもらった方が早いと思います。

example

scheduler = StepLR(optimizer, step_size=20, gamma=0.5)

f:id:katsura_jp:20190130182318p:plain

MultiStepLR


引数一覧

  • optimizer : 省略
  • milestones : 減衰させたいstepのリスト
  • gamma : 減衰率
  • last_epoch : 省略

StepLRは減衰ステップが一つに対し、これは複数取れます。注意点として、milestonesには、ステップの小さい順のリストを与えてください。

つまり、10,30,50のステップ数で減衰させたい場合は、[10,30,50]と与えてください。

example

scheduler = MultiStepLR(optimizer, milestones=[10, 20, 40], gamma=0.5) # 10 < 20 < 40

f:id:katsura_jp:20190130182258p:plain

ExponentialLR


引数一覧

  • optimizer : 省略
  • gamma : 減衰率
  • last_epoch : 省略

簡潔に説明するとbase_lr**step回数が学習率に与えられる。

example

scheduler = ExponentialLR(optimizer, gamma=0.95)

f:id:katsura_jp:20190130182249p:plain

CosineAnnealingLR


引数一覧

  • optimizer : 省略
  • T_max : 1 半周期のステップサイズ
  • eta_min : 最小学習率(極小値)
  • last_epoch : 省略

これは、kaggleやsignateの画像コンペでしばしば解法で登場するsnapshot ensemblingで使用されてます。もちろんそのほかでも使われます。

秋頃(2018年9月ころ)にpytorchになかった気もしなくもなく(見つけてなかっただけかも)、じぶんで実装した記憶があります。

これも見ていただいた方がわかりやすい

example

scheduler = CosineAnnealingLR(optimizer, T_max=20, eta_min=0.001)

f:id:katsura_jp:20190130182240p:plain

ReduceLROnPlateau


引数一覧

  • optimizer : 省略
  • mode : 監視されている要因が'min'だと下がっているか、'max'だと上がっているかを指定
  • factor : 学習率の減衰率
  • patience : 何ステップ向上しなければ減衰するかの値
  • verbose : 減衰するとき出力してれるかどうか
  • threshold : threshold_modeで説明するのに使われてる値
  • threshold_mode : 'rel'と'abs'の2種類のどちらかを取ります。 'rel'の場合、減衰の起因となる閾値をmaxモードだとbest*(1+threshold)、minモードだとbest*(1-threshold)となります。'abs'の場合は、maxモードだとbest-threshold、minモードだとthresh+thresholdとなります。
  • cooldown : 学習率が下がってから、factorを再監視するまでのステップ数
  • min_lr : 最小学習率
  • eps : nanとかInf回避用の微小数

また、監視する要因はschedulerの関数に存在するstepの引数に与えます。

また、大げさに例をあげると、 - mode : 'min' - patience : 10 - threshold : 0.5 - threshold_mode : 'abs' のように指定すると、監視している値のbestが0.01とすると、 0.01+0.5=0.51よりも高い値を10回連続で取ると減衰します。

example

scheduler = ReduceLROnPlateau(optimizer, 'min') 
for epoch in range(10):
    scheduler.step(val_loss) #val_lossが下がらなければ減衰

自作scheduler


PyTorchでは自分でschedulerを作成することも容易です。torch.optim.lr_scheduler._LRSchedulerを継承したクラスを作成すると、上に紹介したようなschedulerを自作することが容易にできます。

今回わかりやすい例として、Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs で使われているようなlinear cyclical learning rate scheduleを作成して見たいと思います。

f:id:katsura_jp:20190130182221p:plain

実装

from torch.optim.lr_scheduler import _LRScheduler

class LinearCyclicalLR(_LRScheduler):
        def __init__(self, optimizer, T_max, eta_min=0, last_epoch=-1):
            self.T_max = T_max
            self.eta_min = eta_min
            super(LinearCyclicalLR, self).__init__(optimizer, last_epoch)

        def get_lr(self):
            last_step = self.last_epoch % self.T_max
            point = self.T_max // 2
            return [-(base_lr-self.eta_min)* last_step / (point-0.5) + base_lr if last_step < point
                    else (base_lr-self.eta_min)*last_step/(point-0.5) + self.eta_min - base_lr
                    for base_lr in self.base_lrs]

まず、pytorchのschedulerの自作にあたり、_LRSchedulerを継承します。optimizerの学習率更新は、scheduler.step()が呼ばれた際に更新されますが、その際、self.last_epochがインクリメントされます。厳密には、このget_lrが呼ばれる前に行われます。step内でself.get_lrが呼ばれ、その戻り値を新たな学習率に置き換えるように設計されてます。

つまり、自作関数でschedulerを作成するならば、get_lrを自作するのにほとんど等しいです。

今回のget_lrの戻り値のリスト内包表記は中学1年で習うような一次関数なので説明は省略。引数はCosineAnnealingLRを丸パクリ。

これを実際に学習率をプロットすると

scheduler = LinearCyclicalLR(optimizer, T_max=20, eta_min=0.001)

f:id:katsura_jp:20190130182218p:plain

おわりに


いかがだったでしょうか。 最近TLにPyTorchはじめた方が散見されていましたので書いて見ました。個人的にはPyTorch書きやすい/読みやすい/普及しているの3点でおすすめです。 ちなみに、学習率の取得は、scheduler.get_lr()で返ってきます。

しかし、chainer(chainercvなども含める)と比べられると自分で実装しないといけないことが多いかもしれません。どちらも使えるように自分も頑張りたいと思います。

kaggleに初挑戦したお話

はじめに

この記事では、具体的な解法は書いておりませんのでご了承を。 基本的には、初挑戦した感想を書いております。 アドバイス等ございましたらぜひ@katsura_jpまでご教授お願いいたします。

初参戦したコンペ概要

初挑戦コンペはTGS Salt Identification Challengeでした。 どんなものかというと、地震画像(sesmic image)から塩分が含まれている箇所をセマンティックセグメンテーションしようという内容です。 あらかじめ報告しておくと、ゴールド圏内に入ることができました。

参加するきっかけ

まず、なぜ参加しようと決めたのかというお話からしたいと思います。 私自身、Kaggleは1年ほど前に存在を知り、なんとなくアカウントを作成していました。 しかし、自信もなく、具体的に何から始めればいいの?と感じていました。

しかし、今回参加することを決めたのは、一つのことがきっかけとなりました。

私は今、筑波大の情報科学類に所属しています。学群学類というのは実際は他大学でいう学科に該当します。そこで今年「情報特別演習」という授業を履修したのですが、 どんな授業かというと、(情報学で)興味あることやったら単位あげるよ!という内容です。私は、コンピュータビジョン系でミーハーなのでディープラーニングに興味があったので、 同大学で関連している教員をクラスの担任から紹介されて、その先生にお願いしました。

その研究室でディープなら学生の方が詳しいからとの理由で紹介していただいたのがKaggle masterのlyakaap先輩で、先生からkaggleやってみればいいじゃんと言われたのが参加のきっかけです。

参加する前の知識量

参加する前後あたりでの私の知識ですが、実際はとても貧しいものでした。 5月ごろには先輩とkerasでCNNでMNISTのクラス分類を行い、「おぉ〜」と感動したのを今でも覚えていて、5~6月は、kerasの使い方とDLの基本(順伝播、逆伝播、CNN、最適化、活性化関数あたり)を勉強しました。正直、最適化に関しては、数式をみてもわからない部分も多少、いやたくさんあってこころが折れてました。そして、7月ころ、授業の中間発表のためにそれとなくやってみたのが、U-Netで人物をセマンティックセグメンテーション。実際そのくらいの知識量です。

参加した直後

参加したのは、ちょうど夏休みに入る直前でした。実際は夏休み入ってからやろうと思ってたのですが、課題を早めに終わらせていたので暇だからやるかとなって早めました。 当初の目標がブロンズメダル取れたらいいなくらいの気持ちでした。

こちらが初めてのsubmission f:id:katsura_jp:20181021211521p:plain

8月4日

たしか、順位的には50%くらいだったかな。kernelの最高スコアより低くて凹んだ記憶があります。 f:id:katsura_jp:20181021211822p:plain

8月8日

このときは、kernelをほぼコピペでそれにflipのみのaugmentを加えました。が、その直後、それより高いkernelが登場して、凹んだ記憶があります。 f:id:katsura_jp:20181021212153p:plain

8月12日

参戦して、約一週間、semantic segmentationに関連する記事をあさり、Kfoldを試してみるとスコアが伸びて、シルバー圏内へ。 とても嬉しい気もちになり、目標をブロンズからシルバーへ。

コンペ終了まで

しかし、ここから1ヶ月間休戦へと入る。理由としては、飽きたからではない。もうアイディアが無いからだ。1ヶ月間放置した結果、44位から350位くらいまで下がっていて、 気持ちが鬱に。。。私はkernelばかり漁っていたのだが、8月末からdiscussion漁りを開始する。そこでいろんな情報を提供していたのが、カエルアイコンのHeng先生。 しかし、ResNetの存在すら知らなかった私は、はじめ何を言っているのか理解できなかった。

8月末~9月上旬にかけて、ネットワークの知識を増やそうと思いネットサーフィン。ありがたいことに夏休み期間で時間もあったので、一週間めちゃくちゃ知識を詰め込んだ。 ResNetをはじめSENet,ResNeXtなど。ここで、discussionをみて見ると、Pytorch使ってる人おおいなと思い、Pytorchを使い始めようと思った。

約一週間Kaggle外で知識を詰め込み、はじめて英論文も読んだ。が、専門用語が多すぎて初めは1本読むのに丸1日。読んだが理解をしたかと言われると、していない。 一番なんだよこれ、と思ったのが"state-of-the-art".

そんなこんなで、充電期間を終え、回し切ったモデルを提出。 f:id:katsura_jp:20181021214419p:plain

9月27日
シルバー圏内に戻れて、めちゃくちゃ嬉しかった。このときは、どん底から上がったので、嬉しさは大きいものでした。

もともと、画像サイズを256x256でinputしていたので、128x128でやろうかチームマージしたいと考えてるなか、マージしたのが、お世話になってるlyakaap先輩。 マージ直後は、マージ相手を探していたのでやったーという気分だった。が、その気分もすぐに冷める。マージして思い知ったのが、自分のDLML知識の少なさ。 soft votingやoofの意味を知らず、やばいと感じた。一人でやっていたのは、すべてhard votingだったのもここで気づく。

ここでの心境としては、先輩の足を引っ張るわけにはいかないという焦り。また、数日たち、ギリシャ人の人とマージをする。英語でコミュニケーションをしたことがなかったため、不安も多かった。しかし、チームメイトは、こんな私にも優しかった。

kaggle mastar5人とnovice1人。議論を共にする自信が0を下回ると、食らいついていこうという気持ちが2147483647に変わった。 基本的にDL系は他の人に任せて後処理に回った(が、終わってから後処理部分の核となっていたものが機能していない説が出てややアイデンテティを消失気味になっている笑)

[2018/10/22 追記]

チームメイトが検証くれた結果、後処理なしではゴールド圏に入れなかった模様。

結果

結果として、当初ブロンズ目標だったものが、ゴールドを手にしていた。嬉しい気持ちもあるが、悔しいという気持ちもある。今回に関しては、個人戦だとシルバー入れていたのかも不安だし、謙遜しすぎかもしれないがおこぼれをいただいた感もある。自分がもっと知識をつけていたらもっと上にいけたはずだと感じもした。

ただ、topの人たちが、どのようにスコアを上げるかを近くでみれたのはもっとも大きな収穫だ。

これから

私はまだ大学2年。時間もたくさんあるし、1、2年で単位も順調に取っているので、来年以降はもっと時間がある。lyakaap先輩を慕っているので、院卒業時にはkaggle masterなりたいと野望を心の中に秘めていたが学部卒までになってやると変えた。

また、これからは、画像認識に関する論文を読んで、知識をもっと入れて、いつか今回のメンバーとチームを組むにしろ、戦うにしろ、肩を並べられる存在になりたいと強く思った。

さいごに

やや、ポエム気味な文章になってしまったのは申し訳ない。。。

今回のチームメイトには最大の感謝とこれからの健闘を祈りたい。

Thank you "Leaning the Future"!!

f:id:katsura_jp:20181021221534p:plain

f:id:katsura_jp:20181021221722p:plain

追記[2019-02-10]

Kaggle Meetup Tokyo#5で発表したスライドです。解法載せてあります。

speakerdeck.com