fast.ai で deep learning を勉強しよう(7)Lesson 3: Planet Amazon dataset

Lesson3 では、Kaggle のデータセットを使ってマルチラベルについて学びます。

以下は Planet Amazon dataset の部分を抜き出した内容に簡単な解説を付けたものです。

Windows10 Python3.7.1 fastai 1.0.57 Pytorch1.2.0 (py3.7_cuda100_cudnn7_1) cudatoolkit10.0.130 cudnn7.6.0

lesson3-planet.ipynb

https://course.fast.ai/videos/?lesson=3

[関連サイト]
Hiromi's Note : https://github.com/hiromis/notes/blob/master/Lesson3.md
Video : https://course.fast.ai/videos/?lesson=3
Notebook : https://github.com/fastai/course-v3/blob/master/nbs/dl1/lesson3-planet.ipynb
 

まずはいつも通りのおまじないです。

%reload_ext autoreload
%autoreload 2
%matplotlib inline
from fastai.vision import *

Getting the data

今回のデータは Kaggle からのダウンロードになります。 Kaggle のデータは規約上 fast.ai に置くことができないとの事で、各自 Kaggle からダウンロードが必要になります。 レッスンでは jupyter のスクリプトで行っています。

Kaggle API を使ってダウンロードする方法

まずは Kaggle にID登録が必要です。

https://www.kaggle.com/

Sign in したら My Account を開きます。

f:id:feynman911:20190716224611j:plain

中ほどにあるAPIから ”Create API Token" を押して kaggle.json をダウンロードして notebook のフォルダーに置きます。

f:id:feynman911:20190716224627j:plain

下記スクリプトpython の Kaggle API をインストールします。

! pip install kaggle --upgrade

%userprofile% の下に .Kaggle フォルダーを作成 (ex. C:\Users\yourid\.kaggle) し、kaggle.json をコピーします。

! mkdir %userprofile%\.kaggle
! move kaggle.json %userprofile%\.kaggle

データをダウンロードして展開するためのフォルダー planet を作成します。 場所は %userprofile%の.fastai\data\planet (ex. C:\Users\yourid\.fastai\data\planet) になります。

path = Config.data_path()/'planet'
path.mkdir(parents=True, exist_ok=True)

データは7z形式の書庫ファイルとなっているので、解凍用に下記のライブラリーをインストールしておきます。

! conda install -y -c haasad eidl7zip

データをダウンロードして、展開します。

! kaggle competitions download -c planet-understanding-the-amazon-from-space -f train-jpg.tar.7z -p {path}  
! kaggle competitions download -c planet-understanding-the-amazon-from-space -f train_v2.csv -p {path}  
! unzip -q -n {path}/train_v2.csv.zip -d {path}
! 7za -bd -y -so x {path}/train-jpg.tar.7z
! 7za -bd -y -so x {path}/train-jpg.tar.7z | tar xf - -C {path}


以上がレッスンで行っている事ですが、上手く機能しない時には、PCのブラウザでダウンロードして7z形式を扱える解凍ソフトで解凍してデータを所定の場所に置いた方が簡単だと思います。  

直接ダウンロードして展開する方法

まず、ブラウザで直接 Kaggle からデータをダウンロードします。

次のリンク先の Data Sources から、
train-jpg.tar.7z (600.14 MB)
train_v2.csv (159.11 KB)
をダウンロードします。

https://www.kaggle.com/c/planet-understanding-the-amazon-from-space/data

tar & 7z & zip が扱えるソフトで解凍して、データを .fastai\data\planet に置きます。

.fastai\data\planet\train_v2.csv
.fastai\data\planet\train-jpg\train-***.jpg

以上でデータの準備ができました。
データのダウンロードは1回行えばいいだけですが、途中で出てくる Path の設定は毎回必要です。 データのダウンロードが完了したら、Path 設定以外はコメントにしてしまいましょう。

Multiclassification

ラベルの CSVファイル を読み込んで、初めの部分を表示してみます。 csv の読込には pandas を使用すると簡単です。

df = pd.read_csv(path/'train_v2.csv')
df.head()

f:id:feynman911:20190717220102j:plain

1カラム目に画像ファイルの名前、2カラム目にスペース区切りでラベルが並んでいます。 画像はアマゾンの衛星写真で、ラベルにはその写真に写っている物、天候が複数スペース区切りで書かれています。

f:id:feynman911:20190722220801j:plain

例えば、次のようなラベルがあります。

  • haze :かすみ
  • primary:primary rainforest 一次熱帯林(原生林)
  • agriculture :商業的農地
  • water:川、湖
  • habitation :居住地
  • road:道
  • cultivation:畑
  • slash_burn :Slash-and-burn 焼き畑農業

それぞれを見分ける意義等詳しい事は Kaggle の対応ページを見てください。

Planet: Understanding the Amazon from Space | Kaggle

Databunch

Databunch は学習モデルに与えるデータをまとめたものです。

データには、

  • レーニング用のデータ
  • 検証用のデータ
  • テスト用データ

があり、それらのデータをまとめて処理できるようにします。 データの個数を保持して抜き出してくるメソッドを持つクラスであるDataSetと、DataSetを使ってミニバッチ用にランダムに複数の item を抜き出してGPUに送るDataLoader、そしてトレーニング用、検証用、テスト用のDataLoaderをまとめたものがDataBounchと言われるものになります。

np.random.seed(42)
src = (ImageList.from_csv(path, 'train_v2.csv', folder='train-jpg', suffix='.jpg')
        #Where to find the data? -> in planet 'train-jpg' folder
       .random_split_by_pct(0.2)
       #How to split in train/valid? -> randomly with 20% in valid
       .label_from_df(label_delim=' '))
       #How to label? -> use the second column of the csv file and split the tags by ' '

ランダムにデータを抜き出す作業をするので、そのままでは毎回結果が異なってしまい扱いにくいため、乱数のシード(種、初期値)を固定する事で毎回同じ乱数列を使うようにしています。

上記でDataSetが作成されます。

Fast.aiのライブラリーではメソッドチェーンという手法で処理を連続で繋げています。

各パラメータの意味は次のようなものです。

  • ImageList.from_csv
    データがある場所: path = .fastai\data\planet\
    データファイル: train_v2.csv
    画像データがある場所:train-jpg
    画像サフィックス: .jpg

  • random_split_by_pct(0.2)
    20%をランダムにトレーニングデータから分離して検証用データとして使用

  • label_from_df(label_delim=' ')
    データ(train_v2.csv)の2コラム目にあるラベル区切りはスペース
      例:train_1,agriculture clear primary water
         -> train_1.jpg のラベルは"agriculture" "clear" "primary" "water"

(参照)https://docs.fast.ai/data_block.html#ItemList.from_csv

レーニング用の画像データに対してサイズを変えたり、歪めたりしてデータに変化を与えることでデータ数を増やしたのと同じような効果を与えて過学習を防ぐのが transform です。

tfms = get_transforms(flip_vert=True, max_lighting=0.1, max_zoom=1.05, max_warp=0.)
data = (src.transform(tfms, size=128)
        .databunch().normalize(imagenet_stats))

transform の各パラメータの意味は次のようなものです。

  • flip_vert=true
    flipはデフォルトで左右のみとなっていますが、衛星画像なので上下の概念がないので上下flipもtrueとします。

  • max_lighting=0.1
    明るさとコントラストを変化させる幅を指定します。デフォルト0.2ですが、今回の場合0.1の方が良い結果が出たとの事で0.1が設定されています。式の部分を抜き出してみると次の様になっています
    res.append(brightness(change=(0.5(1-max_lighting), 0.5(1+max_lighting)), p=p_lighting))
    res.append(contrast(scale=(1-max_lighting, 1/(1-max_lighting)), p=p_lighting))
    0.1なので10%の幅で明るさとコントラストをランダムに変化させます。変化させるかどうかの確率はp_lightingで指定しますが、デフォルトは0.75なので75%のデータに適用します。

  • max_zoom=1.05
    画像のサイズを変更します。1~1.05までの倍率を75%効かせます。

  • max_warp=0
    遠くが小さいという遠近感の歪みを与えるパラメータです。普通の写真の場合には有効ですが、衛星写真の場合歪みはないので0としています。

  • size=128
    元画像が256x256なので1/2倍にして処理しています。メモリーの節約とトレーニング時間に効いてきます。

.databounch() でDataLoaderとそれを束ねたDataBounchが作られます。

imagenet と同じ normalize を行います。

(参照)https://docs.fast.ai/vision.data.html#ImageDataBunch.normalize

バッチデータを表示してみます。

data.show_batch(rows=3, figsize=(12,9))

f:id:feynman911:20190909205855j:plain

Model & Learner

モデルに resnet50 を設定します。 はじめは resnet34 を使用していましたが resnet50 の方が若干良い結果が出たとの事で、ここでは resnet50 を使っているとの事です。

小さなモデルと小さな画像サイズから始めて、試行錯誤し、モデルや画像サイズを変えながら徐々に精度を上げるトライアルを行うとの事です。

arch = models.resnet50

シングルラベルの時にはニューラルネットの出力である probability の値が最大のものを予測ラベルとします。マルチラベルの場合には、ニューラルネットの出力である probability の値が thresh を超えた物すべてを予測されたラベルとします。

data と model と metrics から learner を作ります。

(参照)https://docs.fast.ai/vision.learner.html

acc_02 = partial(accuracy_thresh, thresh=0.2)
f_score = partial(fbeta, thresh=0.2)
learn = create_cnn(data, arch, metrics=[acc_02, f_score])

ここでは metrics として accuracy_thresh と fbeta が使われ、 partial を使用して引数 thresh を 0.2 に固定した関数を作って create_cnn に渡しています。どちらもデフォルトとしてシグモイド関数が適用されています。

model として何を使うかとか、transform のパラメータとか、accuracy の thresh とか Jeremy が試行錯誤した結果が盛り込まれています。自分で他の課題に取り組む時にはパラメータを変えながらの試行錯誤が必要です。

Training

Choosing learning rates

learn.lr_find()
learn.recorder.plot()

lr_finder( )を使って、トレーニングで使用する learning rate の目安を探索します。

f:id:feynman911:20190916201931j:plain

learning rate として傾きが急な場所を設定します。

ワンサイクルポリシーを使用して lr=0.01 で 5エポックだけ学習させてみます。

(参考) https://arxiv.org/pdf/1803.09820.pdf

(参考) Another data science student's blog – The 1cycle policy

lr = 0.01
learn.fit_one_cycle(5, slice(lr))

f:id:feynman911:20190916212437j:plain

学習内容を保存しておきます。

learn.save('stage-1-rn50')

ここまででは、CNNの学習データをフリーズして、最終層のみを学習してきましたが、CNNの部分を unfreeze(学習できるように) します。

learn.unfreeze()

前と同様にして、learning rate の探索を行います。

learn.lr_find()
learn.recorder.plot()

f:id:feynman911:20190916232305j:plain

sliceの1番目の引数はグラフから選んで 1e-5, 2番目の引数は unfreeze する前に決めた lr の1/5~1/10程度の値を設定します。これは Jeremy の経験則との事です。

learn.fit_one_cycle(5, slice(1e-5, lr/5))

f:id:feynman911:20190916232432j:plain

ここまでを保存しておきます。

途中経過を保存しておくことで、学習がうまく進まない場合にパラメータを変えてリトライが可能となります。

learn.save('stage-2-rn50')

次に画像サイズを256x256にして同じことを行います。

再度CNN部分を freeze します。

data = (src.transform(tfms, size=256)
        .databunch().normalize(imagenet_stats))

learn.data = data
learn.freeze()
learn.lr_find()
learn.recorder.plot()

f:id:feynman911:20190916233101j:plain

128x128の時ほどはっきりした形になっていませんが、適当に前回の1/2で学習させます。

(こういうところは試行錯誤になってしまいます)

lr=1e-2/2
learn.fit_one_cycle(5, slice(lr))

f:id:feynman911:20190917001008j:plain

学習結果を保存します。

learn.save('stage-1-256-rn50')

unfreezeして学習を進めます。

learn.unfreeze()
learn.fit_one_cycle(5, slice(1e-5, lr/5))

f:id:feynman911:20190917010234j:plain

lossの経過をグラフ化してみます。

learn.recorder.plot_losses()

f:id:feynman911:20190917010248j:plain

学習結果を保存します。

learn.save('stage-2-256-rn50')