深層学習 (deep learning) は民主化が進んでおり,様々なオープンソースのパッケージが開発されている. ここではfastai v2を使う。

https://mikiokubo.github.io/analytics/14fastai2020/

たとえば tensorflow, Keras, PyTorch, Chainerなどが有名であるが,ここではfastaiを用いる.

fastaiは、最先端の深層学習を実務家が気軽に適用できるようにするためのパッケージである.

開発者が「AIをもう一度uncoolに」を標語にしているように,専門家でなくても(Pythonを知っていれば)ある程度(というか数年前の世界新記録程度)の深層学習を使うことができる.

特徴は以下の通り。

  • コードが短くかける(Kerasよりも短い)。
  • 速い。
  • 最新の工夫が取り入れられている。
  • PyTorchの足りない部分を補完してくれる。
  • 無料の(広告なしの) 講義ビデオがある.
  • 本のソースも無料公開.

fastaiの無料講義については,https://www.fast.ai/ 内のPractical Deep Learning for Coders 参照. 

本のgithubリポ https://github.com/fastai/fastbook/

ドキュメント(マニュアル) https://docs.fast.ai/

日本語の解説ビデオ

from IPython.display import Image, YouTubeVideo
YouTubeVideo("iJVkwofNY9E")

簡単な用語の定義

  • 人工知能: 機械に知能を持たせるための技術.
  • 機械学習:(教師ありに限定だが)入力データと出力データから,モデルのパラメータを調整する方法.
  • ニューラルネット:単なる関数近似器.ニューラルネットを体験 https://playground.tensorflow.org/
  • 深層学習:単なる多次元分散型関数近似器.
  • fast.ai:PyTorchのラッパー

深層学習とは

多くの隠れ層(後で説明する)をもつニューラルネットである.ニューラルネットとは,

  • 訓練データを複数の階層から構成されるモデルに入力,
  • 上層からの重み付き和(線形変換)に活性化関数を適用して,下層に流す,
  • 最下層では損出関数によって誤差を評価,
  • 誤差の情報から勾配を計算,
  • 勾配の情報を用いて,重み(パラメータ)の更新,

以上を繰り返す仕組みである.これは単に,入力を出力に変換する関数とも考えられるが, できるだけ与えられたデータに適合するように近似することを目標としている点が特徴である.

1ニューロンのニューラルネットは単なる古典的な線形回帰(もしくは分類問題の場合にはロジスティック回帰)である.

深層学習の歴史

いま流行の深層学習(deep learning)はニューラルネットから生まれ,そのニューラルネットはパーセプトロンから生まれた.起源であるパーセプトロンまで遡ろう.

1958年に,コーネル大学の心理学者であったFrank Rosenblattがパーセプトロンの概念を提案した.これは1層からなるニューラルネットであり,極めて単純な構成をもつが,当時は部屋いっぱいのパンチカード式の計算機が必要であった.

隠れ層のない2層のニューラルネットワークでの出力誤差からの確率的勾配降下法は1960年にB. Widrow と M.E. Hoff, Jr. らが Widrow-Hoff 法(デルタルール)という名称で発表した。隠れ層のある3層以上の物は、1967年に甘利俊一が発表した

1969年に,MITのMarvin Minsky(人工知能の巨人として知られる)が,ニューラルネットの限界についての論文を発表した.彼の名声による影響のためか,その後ニューラルネットの研究は徐々に下火になっていく.

2006年に,トロント大学のGeoffrey Hinton(ニューラルネットの父として知られる)は,多階層のニューラルネットでも効率よく学習できるような方法に関する論文を発表する.この手法はオートエンコーダーと呼ばれ,その後の深層学習の爆発的な研究のもとになったものである.

2011年にマイクロソフト社は,言語認識のためにニューラルネットを使うようになる.その後も言語認識や機械翻訳は,画像認識ととともに,深層学習の応用分野として定着している.

2012年の7月にGoogle社は猫を認識するためのニューラルネットであるGoogle Brainを開始し,8月には言語認識に用いるようになる.同年の10月には,Hintonの2人の学生が,ImageNetコンテストで断トツの成績で1位になる.これをきっかけに,深層学習が様々な応用に使われるようになる.

2015年の12月には,マイクロソフト社のチームが,ImageNetコンテストで人間を超える結果を出し,2016年3月には,AlphaGoが碁の世界チャンピオンでLee Sedolを打ち負かす(ただしこれは深層学習というより強化学習の成果とも言える).

なぜ深層学習が巧くいくようになったのか?

データ量の増大に伴い,それをうまく利用できる手法である深層学習が有効になってきている.層の数を増やしても大丈夫なようなアーキテクチャ(モデル)が開発されたことも,重要な要因である.つまり,データと新しいモデルが両輪となって,様々な分野への応用を後押ししているのである.小さなデータしかないときには,ニューラルネットは線形回帰やサポートベクトル機械(SVM)と同じ程度の性能である.しかし,データが大規模になると,ニューラルネットはSVMより高性能になり,小規模なニューラルネットより大規模なニューラルネットの方が良い性能を出すようになる.

さらには,GPUの低価格化によって単純な計算の反復が必要な深層学習が高速に実行できるようになったことも普及を後押ししている.深層学習が巧く動くことが知られるにつれて,研究も加速している.古典的なシグモイド関数からReLU(ならびにその亜種)への移行,ドロップアウト,バッチ正規化など,実際に巧く動くアルゴリズムの開発も重要な要因である.さらに,応用に応じた様々なモデル(アーキテクチャ)が提案され,問題に応じて適切なモデルを使い分けることができるようになってきたのも,理由の1つである.

多くの人材が深層学習の分野に参入したことも重要な要因であるように感じている.ハイパーパラメータの適正化は,最適化における実験的解析と同様に,膨大な系統的な実験と,それを解析するマンパワーが必要となる.データを公開し,開発したソフトウェアをオープンソースにして配布するといったこの分野の風土も研究を加速している.

データやソフトウェアを非公開にする風土をもつ他の研究分野は,深層学習をお手本にする必要があるだろう.特に,日本の企業との共同研究では,データや開発したソフトウェアは非公開にしがちである.深層学習を牽引するコミュニティーのパワーは,そういった秘密主義がないことに起因している.

fastaiのインストール

fastaiのインストールは本家サイトを参照されたい。ここでは、Google Colab.上でインストール方法を解説する。

  1. 上部メニューのランタイム/ランタイプの種類を変更でGPUをオンにする.
  2. fastaiをpipで入れる.

とまあこれだけである。Google Colab.ではPyTorchがあらかじめインストールされているので、これだけで良いのだ。

google driveにアクセスする権限を与える必要があるので,リンクを開いてauthentification codeをコピペする.

# git push時の timeout errorを避けるためのフラグ(push時にはTrueにする)
GIT = True
!pip install -Uqq fastbook
import fastbook
fastbook.setup_book()
     |████████████████████████████████| 51kB 5.6MB/s  eta 0:00:01
     |████████████████████████████████| 358kB 11.2MB/s 
     |████████████████████████████████| 1.0MB 18.9MB/s 
     |████████████████████████████████| 61kB 9.7MB/s 
     |████████████████████████████████| 92kB 11.8MB/s 
     |████████████████████████████████| 40kB 6.8MB/s 
     |████████████████████████████████| 51kB 9.0MB/s 
     |████████████████████████████████| 61kB 9.8MB/s 
     |████████████████████████████████| 2.6MB 46.1MB/s 
Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3aietf%3awg%3aoauth%3a2.0%3aoob&scope=email%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdocs.test%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive.photos.readonly%20https%3a%2f%2fwww.googleapis.com%2fauth%2fpeopleapi.readonly&response_type=code

Enter your authorization code:
··········
Mounted at /content/gdrive

まずは必要なモジュールをインポートする.

from fastbook import *
from fastai.vision.all import *

最初の例題(猫と犬の判別)

ペットの名前を当てるデータセットを読み込む.

ラベルの最初の文字が大文字なのが猫なので,猫なら真を返す関数is_catを作成し, データローダーを作成し,畳み込みニューラルネットの学習器learnを作成し,データセットで訓練する.

いまは理解しなくても良い.

from fastai.vision.all import *
path = untar_data(URLs.PETS)/'images'

def is_cat(x): return x[0].isupper()
dls = ImageDataLoaders.from_name_func(
    path, get_image_files(path), valid_pct=0.2, seed=42,
    label_func=is_cat, item_tfms=Resize(224))

GIT = True
if GIT==False:
    learn = cnn_learner(dls, resnet34, metrics=error_rate)
    learn.fine_tune(1)
Downloading: "https://download.pytorch.org/models/resnet34-333f7ec4.pth" to /root/.cache/torch/hub/checkpoints/resnet34-333f7ec4.pth

epoch train_loss valid_loss error_rate time
0 0.169087 0.012510 0.003383 00:49
epoch train_loss valid_loss error_rate time
0 0.040912 0.022309 0.004060 00:55

自分で適当な猫の写真をダウンロードしてから,ファイルをアップロードして,猫の判別をしてみよう!

uploader = widgets.FileUpload()
uploader
if GIT==False:
    img = PILImage.create(uploader.data[0])
    is_cat,_,probs = learn.predict(img)
    print(f"Is this a cat?: {is_cat}.")
    print(f"Probability it's a cat: {probs[1].item():.6f}")
Is this a cat?: True.
Probability it's a cat: 1.000000

MNIST_SAMPLE の訓練

深層学習における ”Hello World” は、MNISTの手書き文字認識である。ここでは、さらに簡単なMNISTの一部(3と7だけの画像)を認識するためのニューラルネットを作成する。これは、2値分類問題と呼ばれ、似た例をあげると,与えられた写真に猫が写っているか否か,受け取ったメイルがスパムか否か,などを判定することがあげられる。 2値分類問題は、独立変数(ニューラルネットの入力,特徴ベクトル)に対する従属変数(ターゲット)が 0か1の値をとる問題であると言える.

この簡単な例を用いて、fastaiを用いた訓練 (training;学習と呼ぶ人もいる) のコツを伝授する.

まず、fastaiで準備されているMNIST_SAMPLEのデータを読み込む.

pathはデータを展開するフォルダ(ディレクトリ)名であり、dlsはデータローダーと名付けられた画像用データローダー (ImageDataLoader)の のインスタンスである。

データローダーには,様々なファクトリメソッド(インスタンスを生成するためのメソッド)がある.ここでは,フォルダから生成するfrom_folderメソッドを用いる.

path = untar_data(URLs.MNIST_SAMPLE)
dls = ImageDataLoaders.from_folder(path)

doc()でドキュメントを見ることができる.

doc(ImageDataLoaders)

class ImageDataLoaders[source]

ImageDataLoaders(*loaders, path='.', device=None) :: DataLoaders

Basic wrapper around several DataLoaders with factory methods for computer vision problems

Show in docs

読み込んだデータの1バッチ分は,データローダーのshow_batchメソッドでみることができる.

dls.show_batch()

畳み込みニューラルネット(cnn)モデルのインスタンスを生成し,データとあわせて学習器 learn を生成する.メトリクス(評価尺度)はerror_rateを指定しておく.

学習器はresnet34を用い,学習済みのデータを用いた転移学習を行う.

RESNET

ResNetは残差ブロックとよばれる層の固まりを,何層にも重ねたものである.残差ブロックでは,ブロックへの入力,線形層,ReLU,線形層の流れに,入力そのものを加えたものに,ReLUを行うことによって出力を得る. 入力をそのまま最終の活性化関数の前に繋げることによって,必要のないブロックを跳ばして計算することができるようになり,これによって多層のニューラルネットで発生する勾配消失や勾配爆発を避けることが可能になる. 残差ブロックは,畳み込み層の間に「近道(ショートカット)」を入れたものに他ならない.この「近道」を入れることによって,最適化が楽になることが知られている.局所解が減少し,滑らかな空間(ランドスケープ)での最適化になるのだ.

残差ネットワークの学習器learnを作成してからlearn.summaryをみると、その構造がわかる。以下で定義するresnet34は34層の大規模な畳み込みニューラルネットである。実行すると、学習済みの重みが読み込まれ,この重みをもとに転移学習を行うことができる.

転移学習

通常の訓練においては,初期のパラメータ(重み)はランダムに設定される.しかし,ランダムな初期パラメータからの学習は,アーキテクチャが大規模になると膨大な時間がかかることがある.そこで,特定のアーキテクチャに対して,事前に訓練されたパラメータ(重み)を用いることが行われるようになってきた.これが転移学習 (transfer learning) である.

多層の畳み込みニューラルネットで発生を用いて画像の分類をするケースを考えよう.学習が進むにつれて,最初の方の層では線や角などの簡単な形状を抽出するようになり,層が深まるにつれて徐々に複雑な形状を学習するようになる.たとえば,猫のふわふわした毛に反応するニューロンや,猫の目に反応するニューロンが出てくる.最終層の直前では,分類したい物の特徴を抽出するニューロンがある.転移学習では,他の目的のために訓練されたパラメータを用い,判別を行う最終層だけに対して訓練(パラメータの調整)を行う.線や角の判別は共通であるが,最終的な分類は,対象とするものに依存して再訓練をしなければならないからだ.

最終層のパラメータが十分に訓練されたら,上層のパラメータに対しても訓練を行う方が良い.fine_tuneメソッドは,これを自動的にしてくれる.

学習率の調整

深層学習で最も重要なパラメータは,学習率(learning rate: lrと略される)である.深層学習では,重み(パラメータ)調整のために非線形最適化を行う.

つまり,勾配に適当なステップサイズを乗じて現在の値から減じる操作を繰り返す.この非線形最適化におけるステップサイズのことを,学習率と呼んでいる.

これをチューニングするために,fastaiでは学習器オブジェクト にlr_find() というメソッドを準備している.

学習器learnを作成してlearn.lr_find()とする.

返値は,損失関数が最小になる学習率の1/10 (lr_min) と,傾きが最大になる学習率 (lr_steep) のタプルである. この2つの値の間あたりが,良い学習率となる. また,lr_findは,学習率を小さな値から1反復ごとに2倍にしたときの損出関数(目的関数のこと)をプロットしてくれる. 目安だが,最小値をもつ谷に入る辺りの学習率が良いと言われている.

if GIT==False:
    learn = cnn_learner(dls,resnet34, metrics=error_rate, cbs=ShowGraphCallback())
    lr_min, lr_steep = learn.lr_find() 
    print(lr_min, lr_steep)
0.02089296132326126 9.12010818865383e-07

損出関数が最小になるのは,学習率が0.2あたりだが,最も大きな谷の下り坂に入るあたりが良いとされている.ここでは,学習率を1e-2(0.01)に設定して訓練してみる.

これには学習器インスタンスのfit_tuneメソッドを用いる.引数はエポック数(最適化の反復回数;データ全体を何回使うかを表す)と学習率である.

なお,実際の反復ごとの学習率は,学習器のcbs引数をShowGraphCallback()とすると,見ることができる.

if GIT==False:
    doc(learn.fine_tune)

Learner.fine_tune[source]

Learner.fine_tune(epochs, base_lr=0.002, freeze_epochs=1, lr_mult=100, pct_start=0.3, div=5.0, lr_max=None, div_final=100000.0, wd=None, moms=None, cbs=None, reset_opt=False)

Fine tune with freeze for freeze_epochs then with unfreeze from epochs using discriminative LR

Show in docs

if GIT==False:
    learn.fine_tune(2, base_lr=0.01)
epoch train_loss valid_loss error_rate time
0 0.217003 0.122364 0.028950 01:32
epoch train_loss valid_loss error_rate time
0 0.038310 0.016312 0.003435 02:51
1 0.005268 0.003391 0.000981 02:59

評価尺度の誤差率は非常に小さく、図から訓練はうまく行われているようだ。 結果を表示してみる。大体当たっているようだ。

if GIT==False:
    learn.show_results()

fine_tuneでは、最終層以外を固定して(既定値では1回)訓練を行い、その後、fit_one_cycleを用いて、指定したエポック数だけ訓練する。 fit_one_cycleは,学習率を小さな値から最大学習率まで増やし,その後徐々に減少させていく.同時に,慣性項を徐々に下げて,その後増加させていく最適化法で,これを使うと収束が速くなると言われている.

fine_tuneメソッドの引数はエポック数と基本学習率 base_lr である.

分類モデルの結果を解釈は、ClassificationInterpretation()クラスのfrom_learnerメソッドを用いてできる。 plot_top_lossesを用いると,損出関数が悪かったデータを描画してくれる.

if GIT==False:
    interp = ClassificationInterpretation.from_learner(learn)
if GIT==False:
    interp.plot_top_losses(9, figsize=(7,7))
if GIT==False:
    print(interp.most_confused())
[('7', '3', 2)]

正解と外れを表す表(混合行列とよばれる)を出力するには,plot_confusion_matrixを使う.

if GIT==False:
    interp.plot_confusion_matrix()

MNIST の訓練

0-9の数字の画像ファイルから,数字を当てる.

path = untar_data(URLs.MNIST)
path.ls()
(#2) [Path('/root/.fastai/data/mnist_png/training'),Path('/root/.fastai/data/mnist_png/testing')]
dls = ImageDataLoaders.from_folder(path,train="training",valid="testing")
if GIT==False:
    learn = cnn_learner(dls, resnet34, metrics=[error_rate,accuracy],cbs=ShowGraphCallback())
    lr_min, lr_steep = learn.lr_find() 
    print(lr_min, lr_steep)
0.014454397559165954 0.005248074419796467
if GIT==False:
    learn.fine_tune(2, base_lr=0.02)
epoch train_loss valid_loss error_rate accuracy time
0 0.255310 0.137086 0.038300 0.961700 01:16
epoch train_loss valid_loss error_rate accuracy time
0 0.069197 0.053692 0.013500 0.986500 01:24
1 0.027597 0.019030 0.005200 0.994800 01:25
if GIT==False:
    learn.show_results()
if GIT==False:
    interp = ClassificationInterpretation.from_learner(learn)
    interp.plot_top_losses(9, figsize=(15,10))
if GIT==False:
    interp.plot_confusion_matrix()

Cifar10の訓練

Cifar10は粗い画像から,10種類の物体を当てる問題である.

データを読み込んだ後に,get_ransform()でデータ増大(data augmentation)を行うためのオブジェクトtfms を生成する.

画像データ束クラス ImageDatabunch のfrom_forder()メソッドでデータを生成するが, その際に引数df_tfmsにtfmsを渡して訓練時に画像を多少変えて行うように設定する.

最後に,データ束をnormalize()メソッドで正規化して,表示する.

path = untar_data(URLs.CIFAR)
dls = ImageDataLoaders.from_folder(path,valid_pct=0.1)
dls.show_batch()
if GIT==False:
    learn = cnn_learner(dls, resnet34, metrics=[error_rate,accuracy])
    lr_min, lr_steep = learn.lr_find() 
    print(lr_min, lr_steep)

データとアーキテクチャ(モデル:RESNET)をあわせて学習器を生成する.

メトリクスは正解率(accuracy)とする.

if GIT==False:
    learn.fine_tune(10, base_lr=1e-3)
epoch train_loss valid_loss error_rate accuracy time
0 1.738080 1.514888 0.510667 0.489333 01:11
epoch train_loss valid_loss error_rate accuracy time
0 1.192455 1.043108 0.361667 0.638333 01:18
1 0.879819 0.802226 0.273333 0.726667 01:18
2 0.699327 0.675941 0.233167 0.766833 01:18
3 0.566587 0.626140 0.217667 0.782333 01:18
4 0.418197 0.619954 0.208000 0.792000 01:18
5 0.277065 0.667390 0.207667 0.792333 01:17
6 0.191293 0.722183 0.204833 0.795167 01:20
7 0.119485 0.764068 0.204667 0.795333 01:19
8 0.092574 0.807524 0.201333 0.798667 01:18
9 0.075562 0.825618 0.202000 0.798000 01:19

混合行列を出力する.

#preds,y,losses = learn.get_preds(with_loss=True)
#interp = ClassificationInterpretation(learn, preds, y, losses)
#interp.plot_confusion_matrix()

PETSのデータセット

画像ファイルから犬か猫からを判別する.

モデル(アーキテキクチャ)は画像ファイルなのでRESNETを用いる.

from fastai.vision.all import *
path = untar_data(URLs.PETS)
path
Path('/root/.fastai/data/oxford-iiit-pet')
path.ls()
(#2) [Path('/root/.fastai/data/oxford-iiit-pet/annotations'),Path('/root/.fastai/data/oxford-iiit-pet/images')]
path_anno = path/"annotations"
path_img = path/"images"
fnames = get_image_files(path_img)
fnames[:5]
(#5) [Path('/root/.fastai/data/oxford-iiit-pet/images/english_cocker_spaniel_194.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/saint_bernard_36.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Persian_139.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Persian_136.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Birman_103.jpg')]
files = get_image_files(path/"images")
len(files)
7390
def label_func(f): return f[0].isupper() #犬猫の判定
dls = ImageDataLoaders.from_name_func(path, files, label_func, item_tfms=Resize(224))
dls.show_batch()
if GIT==False:
    learn = cnn_learner(dls, resnet34, metrics=error_rate)
    learn.fine_tune(1)
epoch train_loss valid_loss error_rate time
0 0.144485 0.030431 0.008796 01:26
epoch train_loss valid_loss error_rate time
0 0.050339 0.011808 0.003383 02:01
if GIT==False:
    learn.show_results()