大阪大学医学部 Python会 (情報医科学研究会)

Now is better than never.

【勉強会資料 2020 第7回】機械学習実践編 : Kerasで作る深層学習画像分類器

2020-07-01(Wed) - Posted by 小川 in 技術ブログ    tag:勉強会 tag:Machine Learning

Contents

    今回は機械学習の実践編として、深層学習を使った画像分類器を実際に作って動かしてみます。

    深層学習は「ゼロから作る」のもとても勉強になってよいのですが、実用的に使う場合には通常は何らかのライブラリを使います。GPUを動かすための処理なども全て組み込まれていて、慣れればとても楽ちんです。

    今回はその中から「Keras」というライブラリ (フレームワークともいう) を選びました。記述量が少なく、初めて使ってみるのには特に適していると思います。

    (TensorFlowを呼び出すところまでは全てPythonで書かれているので、中身を読んで理解できるのも良いところです。 本格的に使っていると読む機会は実際あります。)

    この他には「PyTorch」も一大勢力です。 (今回、どっちにするか迷いました。)
    PyTorchをやってみたい人にも配慮?して、今回やることは概ね、こちらのPyTorchの公式チュートリアル (の一部) をKeras訳したようなものにしています。
    (そこ、めんどかっただけやろとか言わない。)

    後からそちらをやって、似ているところや違いを感じてみるのも面白いと思います。

    では、はじめましょう。

    準備 (インストール)

    自分で使う場合は、前準備としてKeras本体などのインストールが必要です。

    例えば

    $ pip install keras

    などのようにしてインストールします (pip, conda などは環境に応じて)。

    Google Colabでは何もしなくてOKです。

    TensoflowとKerasの初期化

    おまじないです。とりあえずコピペで問題ありません。

    (Kerasはそのさらに裏でTensorFlowというライブラリが動いているので、順番に読み込んで初期化します。)

    実行すると、GPUがいくつ使えるか、が表示されます。
    Google Colabで実行した場合、1 と出ると思います。 (多くのラップトップPCなどでは 0 です。)

    In [ ]:
    # TensorFlow (=backend)
    import tensorflow as tf
    gpus = tf.config.experimental.list_physical_devices('GPU') # ver2.1の場合。experimentalなので書き方は将来verで変更されそう
    print("Num GPUs Available: ", len(gpus))
    for device in gpus: tf.config.experimental.set_memory_growth(device, True) # 無駄なVRAM確保をしないよう設定
    
    # Keras
    import keras
    import keras.backend as K  # なぜ K... 慣例です。今回は最後まで使いませんが。
    

    ついでに他のパッケージのインポートもしておきます。

    In [ ]:
    import numpy as np  # NumPy はいつでもどこでもほぼ必須
    import matplotlib.pyplot as plt  # 画像表示に MatplotLib を使います
    

    学習データの準備

    まずはデータが必要です。

    今回やってみるのは、深層学習を用いた自動画像分類器の作成です。

    そのためには、 (下でもう少し説明しますが) まず「どういう分類器が欲しいか」を決め、それに応じて、「画像とその答え」を大量に用意する必要があります。

    例えば組織染色画像を用意して、何の組織か分類するとか、病気の進行具合 (グレード、スコア) を判定するなどが考えられます。 実際に高度な自動判別器が作れる場合もあり、多くの研究やコンペティションなどがあります。

    CIFAR-10 データセット

    今回は練習なので、もっと簡単なものを使います。

    CIFAR-10と呼ばれる実験用の定番データセットのひとつで、32x32のカラー画像が全部で6万枚程度あります。 (学習用5万、テスト用1万)

    各々の画像は、「飛行機」「自動車」「鳥」「猫」「鹿」「犬」「蛙」「馬」「船」「トラック」。

    自動車とトラックって何だよというツッコミはさておき、この順に0-9のラベルがついています。

    In [ ]:
    classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
    

    Kerasでの自動読み込み

    Kerasには、CIFARなどの実験用データを自動的にダウンロードして読み込んでくれる機能があります。

    (他にもPyTorchなど、この種のフレームワークは実験を容易にするために大抵そういう機能があります。)

    実際に何か研究をするときのデータなどはそうはいかず、データを揃えて用意するところが学習よりよっぽど大変なのがむしろ普通なくらいですが、ここではその部分はさぼります。

    まずはKerasの機能でデータの読み込み。訓練用 (5万枚) とテスト用 (1万枚) にあらかじめ分かれています。

    In [ ]:
    from keras.datasets import cifar10
    (x_train_all, y_train_all), (x_test, y_test) = cifar10.load_data()
    

    画像の表示ができるように準備しておきます。

    In [ ]:
    # function to show image/images
    def imshow(img):
        plt.imshow(img)
        plt.show()
    
    def joinimg(imgs):
        return np.concatenate(imgs, axis=1)
    

    データ分割・整形

    学習と評価のため、データは適切な分割が必要です。

    今回使うCIFAR-10では、テスト用データをあらかじめ別にしてくれています。

    しかしそれだけだと実は不十分で、ここでは訓練用データをさらに9:1にランダム分割し、小さい方を検証 (validation) 用データとします。
    (意味は後ほど。)

    In [ ]:
    N = len(y_train_all)
    np.random.seed(0)
    order = np.random.permutation(N)
    Nv = N // 10
    Nt = N - Nv
    x_train, y_train = x_train_all[order[:Nt]], y_train_all[order[:Nt]].reshape(-1)
    x_valid, y_valid = x_train_all[order[Nt:]], y_train_all[order[Nt:]].reshape(-1)
    y_test = y_test.reshape(-1) # 列ベクトル → 行ベクトルに整形
    

    x_train などは大きな配列です。

    In [ ]:
    print(x_train.shape, x_valid.shape, x_test.shape)
    print(y_train.shape, y_valid.shape, y_test.shape)
    

    テストデータの最初の5枚と、それぞれのラベルを表示して確認します。

    In [ ]:
    imshow(joinimg([x_test[i] for i in range(5)]))
    print(y_test[:5])
    print([classes[y_test[i]] for i in range(5)])
    

    これで実験用データの用意は完了です。

    深層ネットワークの作成

    画像分類ネットワーク : Kerasによるモデル定義

    データが用意できたので、それを学習・分類する深層ネットワークの構造を作ります。

    CIFAR10の各画像は 32x32 サイズのカラー画像 (RGB) です。

    これらを入力にして、畳み込み (Conv2D())、プーリング (MaxPooling2D)、全結合 (Dense())、を順に繰り返しながら出力(分類)を作っていきます。
    (上記 PyTorch Tutorial と完全に同一にしてあります。)

    Kerasにモデルの定義方法は複数あるのですが、一番簡単な方法(Sequential())を使います。

    In [ ]:
    from keras.models import Sequential
    from keras.layers import Input, Dense, Conv2D, MaxPooling2D, Activation, Flatten
    
    net = Sequential([
        # 畳み込み1回目 : 「畳み込み + 活性化(Relu) + プーリング」 の3層で1セット
        Conv2D(filters=6, kernel_size=(5, 5), input_shape=(32,32,3)), # 32x32のRGB画像を入力。大きさ5x5、厚み3の畳み込みフィルタが6枚
        Activation('relu'),
        MaxPooling2D(pool_size=(2, 2)),
    
        # 畳み込み2回目 : 構造は1回目と同じ
        Conv2D(filters=16, kernel_size=(5, 5)), # 大きさ5x5、厚み6の畳み込みフィルタが16枚
        Activation('relu'),
        MaxPooling2D(pool_size=(2, 2)),
        
        # 5x5の16チャネル画像だが、構造を壊して 5x5x16=400 成分、1列に平たく並べる。
        Flatten(),
    
        # 全結合を3回繰り返す。「全結合 + 活性化」で1セット
        Dense(units=120),
        Activation('relu'),
    
        Dense(units=84),
        Activation('relu'),
    
        Dense(units=10),
        Activation('softmax') # 最後だけSoftMax
    ])
    

    これだけ!
    (実際にはもっと短くも書けますが、意味がわかるように、あえてやや冗長に書きました。)

    やや長いですが、中身は深層学習の「層 (layer)」をただ上から順番に並べているだけです。

    これだけで、

    • CIFAR-10に集められている32x32サイズのカラー画像 (RGB) を入力にして
    • その画像が10通りの分類それぞれに属する確率 (0-1の実数10個、足すと1) を出力する

    「深層ネットワーク」net が完成です。

    (ネットワークとモデルという言葉は区別が難しいですが、モデルの方がネットワークの上に定義されたより詳細な構造をも含む概念、とでも思っておきましょう。 ひとまずそんなに気にしなくていいです。)

    ネットワーク構造とパラメータ

    できあがった net の構造は net.summary() で表示できます。

    In [ ]:
    net.summary()
    

    元の画像から、性質の異なる13層の処理を順番に通して、最後に10成分の出力に到達します。
    こうやって層を何重にも重ねるから「深層学習 (Deep Learning)」と呼ぶわけです。
    極端なものでは数百層以上という場合すらあります。

    今回は13層のうち、パラメータを含む層が5層 (畳み込みと全結合層) あります。
    合計で約6万のパラメータが含まれています。

    ネットワークモデルの学習と利用

    ここから後半です。 前半で作ったモデルのパラメータを学習していきます。

    パラメータと学習

    ですがその前に、そもそも学習とは??

    ネットワーク netは、32x32のカラー画像から10分類の確率 (より正しくは、足すと1になる非負実数の組) を出力する、ということがわかっています。 しかし、どういう分類を与えるか、はこれを見ても全くわかりません。

    もっと卑近な例だと例えば、実数から実数への写像 (関数) と言われても、直線かもしれないし二次関数かもしれないし指数関数かもしれないし、もっとでたらめな関数かもしれない。 それと同じです。

    その具体的な関数の形を決めるのが、上の6万個のパラメータです。 作りたい関数に応じて、この値を変えてやればいい。

    まあそんなこと言われてもどう変えたらいいか、わからないわけですが、そこで登場するのが「学習」です。
    望ましい出力(答え)がわかっている入力画像を大量に用意し、それぞれの答えになるべく近づくように、6万個の値を少しずつ動かす、という気の遠くなる地道な操作を繰り返す。 この操作のことを、比喩的に「学習 (learn)」あるいは「訓練 (train)」と呼んでいます。
    (心配しなくても、これはKerasとGPUがやってくれます。)

    するとあら不思議、欲しかった性質を持つ関数(分類器)がひとりでにできあがる、という仕組みです。

    方法、尺度の設定

    Kerasで学習を行わせる時は、「どういう方法で」「どういう尺度を使って」行うか、をまず設定します。

    In [ ]:
    net.compile(optimizer='adam', metrics=['accuracy'], loss='categorical_crossentropy')
    

    細かいことは気にしなくてよいです。今やりたいのが分類問題であることをモデルに教えました。

    学習の実行

    いよいよ本当の学習!!!!

    以下を実行すると学習が始まり、4万5000枚の訓練用画像データを1周するたびに中間成績が表示されていきます。

    In [ ]:
    history = net.fit(
        x_train, np.eye(10)[y_train], # 学習するデータを指定 (「過去問」)
        batch_size=64,
        validation_data=(x_valid, np.eye(10)[y_valid]), # 中間評価を行うための検証用データを指定 (「模試」)
        initial_epoch=0,
        epochs=30, # 同じ訓練データを何周 (= 「エポック」) 回して学習するか設定
        shuffle=True,
        verbose=2, # エポック毎に成績を出力
    ).history
    

    左が訓練データ自体に対する成績、右が検証用データでの成績。

    loss は小さい方が、 accuracy は大きい方が好成績です。 やってみて何か気付くことはありませんか??

    学習済パラメータの保存

    大事な学習済パラメータはまとめてちゃんと保存しておきます。

    今回はとりあえず必要ありませんが、実際に使う場合は大規模ネットワークを何時間もかけて学習したりするのと、学習過程にはランダム性が入っていて完全な再現は難しいからです。

    In [ ]:
    # net.save_weights('net_param.hdf5')
    # 上をコメントアウトして実行すると保存される
    

    学習後性能の評価

    学習が終わったら、実際に期待したような関数が高精度でできているかどうか、テストデータを使って評価します。

    予測分布の取得

    学習後のモデルで、テストデータに対する予測結果を出力します。

    (模試 (valid) でベンチマークしながら過去問 (train) を何周も解きまくり、最後に本番試験 (test) に臨むイメージです)

    ここでは上で30エポック学習したモデルを使います。 (問題 : これは本当に適切でしょうか?)

    In [ ]:
    predict_test = net.predict(x_test)
    

    結果は10個組の確率分布が1万個並んだ配列です。

    In [ ]:
    print(predict_test.shape)
    print(predict_test[:5])
    

    結果を目視してみる

    とりあえず最初のいくつかを手動で確認して、予測性能の雰囲気をつかんでみます。

    確率を見ると悩んでいる問題 (画像) もあることがわかりますが、マーク式試験だと思って10択の答案にします。

    In [ ]:
    answersheet = predict_test.argmax(axis=-1)
    answersheet[:5]
    

    画像と一緒に見てみましょう。

    In [ ]:
    problems = range(5)
    imshow(joinimg([x_test[i] for i in problems]))
    print('Ground Truth :', [classes[y_test[i]] for i in problems])
    print('Answer Sheet :', [classes[answersheet[i]] for i in problems])
    
    In [ ]:
    problems = range(5,10)
    imshow(joinimg([x_test[i] for i in problems]))
    print('Ground Truth :', [classes[y_test[i]] for i in problems])
    print('Answer Sheet :', [classes[answersheet[i]] for i in problems])
    

    最終採点

    1万問題全てを採点します。

    In [ ]:
    score = len(np.where(answersheet == y_test)[0])/len(y_test)
    score
    

    過去問や模試の成績と比べてみましょう。

    まとめ

    Kerasでの機械学習の基本的手順のまとめ

    • Kerasの初期化
    • 学習データの用意
      • CIFAR-10などの実験用データはKerasで簡単に使える
      • (train, valid, test) に3分割
    • モデルの定義・初期化
    • valid データでベンチマークしながら反復学習
    • test での最終成績評価

    その他

    • より精密には交差検定 (cross-validation) と組み合わせる方法などもありますが、余程でなければ普通はなかなかやらない気がします。

    • エポック数は今回固定にしましたが、実際にはvalidの結果を見ながら打ち切ったり、パラメタを順に保存しておいてvalidのスコアから良さそうなものを選ぶ、等するのが普通です。

    • 今回のモデル自体は率直に言って、大して優れたものではありません。 層やノードの数、その他パラメタなどを変えて、スコアがもっと良くならないか、色々やってみるのも面白いです。

    おしまい。