くろたんく雑記帳

日常とか、わんちゃんとか、機械学習とか、競プロとか、

MENU

【Fast.ai Vision】Single-Label Classificationの基礎

サマリー

Fast.aiのversion2で関数が結構変わっていたので、Single-Label Classificationを行うために最低限の知識をメモとしてまとめる。 基本的にはこちらの書籍に書かれている。

本格的に使う場合は、csvからのデータ取得やaugumentationも既存ツールでないものを使ったほうがいい、学習の仕方の工夫などがあるが、今回は取り扱わず、別記事とする。

環境準備

condaでInstallすることが推奨されている。まっさらな環境のときはcondaをオススメする。colabの場合はPytorchも使える状況なので、pip installで問題ない。

# Colabの場合(すでにPytorch環境が整っている場合)
!pip install -Uqq fastai
# まっさらな環境の場合(Pytorch環境もなく、1から環境作る場合)
conda install -c fastai -c pytorch fastai

Colabではない環境でどうしてもpipで環境を作りたい場合は、以下で適切にPytorchのinstallの設定を調べてから行う。 pytorch.org

モジュールの読み込み

賛否両論あるが、Fastaiはこの様に一行ですべて読込させる方針。

from fastai.vision.all import *

どのような形で必要なモジュールを読み込んでいるのかを把握したい場合は、いかから追ってみるといい。必要なモジュールだけを、すべて個別で読み込もうと試みたが、ただ使うだけなら度々import errorが起きるため諦めて、Fastaiの方針にのることにした。

github.com

Data Blockについて

DataLoadersの準備

以前はDataBunchというクラスでDataSetsやDataLoadersを作成したがversion2以降では変更となったようだ

github.com

DataBlockは、以下の引数を与えるとのDatasetsまたはDataLoadersを作成できる。

今回は比較的簡単にpathを取得できる例で説明する。Multi-Label ClassificationやデータセットのPATHがcsvで提供されている場合のDataBlockの作り方は別記事にする。

  • blocksはsinglelabelならblocks = (ImageBlock, CategoryBlock)
  • get_itemsは画像ファイルのパスを取得する方法、例えばget_image_filesを使う。
  • splitterは、データを分割する方法、以下のsplitterから状況に応じて使い分ける。
  • get_xは、入力に何かを適用する必要があるなら記述。
  • get_yは、ターゲットに何かを適用する必要があるなら記述。多くは予測ラベルの取得方法の関数。例えば、画像ファイル名から0, 1にする関数や画像ファイル名から名前やカテゴリ名を取得する方法を記述。 例えば、RegexLabellerusing_attrを使うなどする。

  • item_tfmsは、batchが形成される前にアイテムに適用される変換を記述。GPUにコピーされる前に個々の画像に適用される。例えばresize。

  • batch_tfmsは、batchが形成された後に適用される変換を記述。GPU上で一括して適用される。例えばaug_transformsなどを使う。
    item_tfmsとbatch_tfmsの両者に使う関数は以下が参考になる。 Data augmentation in computer vision | fastai

  • インスタンスを作ったら、以下の様に画像のディレクトリを入力すればDataLoadersが作ることができる。

# 例えば、path/"images"以下に画像データそれぞれのPathが入ってるとすると
data = DataBlock(blocks = (ImageBlock, CategoryBlock),
                 get_items=get_image_files, 
                 splitter=RandomSplitter(seed=42),
                 get_y=using_attr(RegexLabeller(r'(.+)_\d+.jpg$'), 'name'),
                 item_tfms=Resize(460),
                 batch_tfms=aug_transforms(size=224, min_scale=0.75))

dls = data.dataloaders(path/"images")

# 実際のカテゴリ、カテゴリ数を表示
print(dls.vocab)
len(dls.vocab)

DataLoadersの確認

DataLoadersの中身をバッチ分確認したい場合は以下。 デフォルトでは、バッチサイズは64。

x, y = dls.one_batch()
# y 
# TensorCategory([18, 36, 35, 22, 17, 26,  0, 21, 32, 34,  1,  7,  1, 20, 31, 15, 31, 34, 5,  1, 22, 33, 21, 26,  0,  9, 15,  7, 13, 16, 28, 23, 18,  5,  1,  5, 15,  6,  5, 12,  1, 24,  2, 16,  6,  5,  8, 11, 30,  5, 22, 34, 11, 18, 0, 19, 25, 28, 32,  9, 21,  6, 17, 13], device='cuda:0')

画像自体を確認するときは[show_batch] (https://docs.fast.ai/data.core.html#TfmdDL.show_batch)で表示できる。以下は、The Oxford-IIIT Pet Datasetを読み込んだときの例。

dls.show_batch(nrows=4, ncols=4, max_n=16)

f:id:black_tank_top:20201213142431p:plain
The Oxford-IIIT Pet Datasetを表示した例

学習(Fine turning)

learnerの設定

基本的に最低3つ設定する。

モデルは自分で構築したものも使えるし、以下のpretrainedされたモデルアーキテクチャも使える。 pytorch.org

Metricsは自分で構築したものも使えるし、以下の用意されたMetricsを使える。

docs.fast.ai

learn = cnn_learner(dls, resnet34, metrics=error_rate)

モデル構造のチェック

cnn_learnerで作っている場合は、選択したモデルの最後の層だけcutして、DataLoaderに合わせた数にセットされる。以下で確認できる。

learn.model

転移学習とFine turning

転移学習は、

事前に学習した重みや他のレイヤーの重みを変更せずに(freezing)、目的のタスクを正しく達成するように最終層の完全結合層(fully conect layer)はランダム重みを持つ新しいものに置き換えてこの重みを置き換えた最終層のみの重みだけを更新するように学習すること。

Fine turningは、

転移学習後の微調整で、転移学習後に他のレイヤーの重みの変更を許して(unfreezing)、目的のタスクを正しく達成する重みに置き換えて学習すること。

多くの場合で、以下のやり方で転移学習・Fine turningすることでよりLossが下がることが知られている。

  • 最終層以外をfreezingして学習する。
  • unfreezeする。
  • 更に学習する。

Fastai version1のときは

learn.fit_one_cycle(1)
unfreeze()
learn.fit_one_cycle(1)

のようにやっていたが、version2から新たにfine_tuneというmethodが追加された。unfreezingで何epoch学習するかが引数。freezingで何epoch学習するかは別途設定できる(デフォルトでは1)

なので、上記と同義なのが以下

learn.fine_tune(1)

ただ、基本的には、fit_one_cyclelr_findを組み合わせて、freezing時でも最適な学習率を設定し、unfreezingでも最適な学習率を設定し(最初は大きな値、徐々に小さく)学習することが望ましい。

以下に、適切な学習率を探索する方法について書いた。

適切な学習率の探索

Leslie Smithによって、learning rate finderというアイディアが考案された。 arxiv.org 内容の詳細は省くがその論文は以下なので、参考にするといい。学習率の良い値は、

learnerを設定したら、以下のようにすれば、上記の2つの値が返ってくる。

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.lr_find()
# SuggestedLRs(lr_min=0.012022644281387329, lr_steep=0.0063095735386013985)

f:id:black_tank_top:20201213001730p:plain
learning rate finderの結果の例

まず、freezingしている状態で実際にその値を見た上で適切な学習率を考える。上記の例だと、以下のようにするのが例となる。

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(2, base_lr=3e-3)

fit_one_cycleを使った改善

fit_one_cyclefine_tuneを使わずにモデルを学習する方法として用意されていて、より自由度高く学習の設計ができる。低い学習率で学習を開始し、最初の部分では徐々に学習率を上げ、最後の部分では再び学習率を徐々に下げていくことが可能。

  1. 学習率を探索する。
  2. freezingして、最終層の重みだけを更新するように学習する。
  3. unfreezingする。再度学習率を探索する。
  4. すべての層の重みを更新するように学習する。
# 1. 学習率を探索する。
learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.lr_find()
SuggestedLRs(lr_min=0.012022644281387329, 
lr_steep=0.0063095735386013985)

f:id:black_tank_top:20201213001730p:plain
learning rate finderの結果の例

# 2. 最終層の重みだけを更新するように学習する。
learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fit_one_cycle(3, 3e-3)
# 3. unfreezingする。再度学習率を探索する。
learn.unfreeze()
learn.lr_find()

f:id:black_tank_top:20201213005202p:plain
最終層の学習・unfreezing後learning rate finderの結果の例

上記の例だと、freezing前の探索時のLossの落ち込みに比べて、unfreezing後の探索時の急激なlossの落ち込みがないのは、学習がある程度進んでいるからと考えられる。この場合だと、以下のように学習をすすめるなど、考えられる。

# 4. すべての層の重みを更新するように学習する。
learn.fit_one_cycle(6, lr_max=1e-5)

Discriminative Learning Rates

Jason Yosinskiらによって、支持されているものとして、ネットワークの初期層には低い学習率を使い、最終層には高い学習率を使うのが良いとされている。論文は以下。 arxiv.org

例えば、これまでの内容をふまえると、以下のような流れで行うことができる。

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fit_one_cycle(3, 3e-3)
learn.unfreeze()
learn.fit_one_cycle(12, lr_max=slice(1e-6,1e-4))
# lossのプロット
learn.recorder.plot_loss()

f:id:black_tank_top:20201213010758p:plain
学習と評価データのlossのプロット

Mixed Precision

より、深いネットワークを使ったときの問題点としては、

  1. メモリ不足
  2. 時間がかかる

解決策として、以下が考えられる。

  1. メモリ不足に関してはバッチサイズを減らして学習させる。
  2. 時間がかかることに関しては、Mixed Precisionとして、半精度浮動小数点(fp16)を使うことで数倍早くなる。

Fastai version2では半精度浮動小数点はfrom fastai.callback.fp16 import *を読み込むことで使うことができる。
(fastaiのバージョンにもよる。callbackのうちfp16についてはfrom fastai.vision.all import *で自動的に読み込まれることもある)

from fastai.callback.fp16 import *
learn = cnn_learner(dls, resnet50, metrics=error_rate).to_fp16()
learn.fine_tune(6, freeze_epochs=3)

精度解釈

学習時にvalidation lossで自分で決めたmetricで精度は理解できているものの、実際にどのクラスとどのクラスが間違えやすいか、その程度はどの程度かを把握するために、confusion matrixで予測があっているかを直感的に理解する。

confusion matrix

interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix(figsize=(12,12), dpi=60)

f:id:black_tank_top:20201213105319p:plain
Confusion Matrixの例

クラスを間違っているかのチェック

間違っているクラス同士をチェックするにはmost_confusedというメソッドが用意されている

interp.most_confused(min_val=5)

参考になる書籍

FastaiとPytorchを使って画像認識や協調フィルタリングNLP関連のタスクを例として、かなり詳細まで書かれている。Fastaiを使っていくなら一読することをおすすめする。