よっしーの私的空間

機械学習を中心に興味のあることについて更新します

TensorflowによるEfficientNetの実装

今回はEfficientNetの実装方法についてまとめます。

1. EfficientNetの概要

EfficientNetとは2019年にGoogleが発表した画像分析モデルで、最強のモデルと言われています。画像分析ではモデルの大きさ(層の深さ、広さ)や画像の大きさが分析精度に大きく影響しますが、実際にはモデルを大きくしたり大きな画像を使おうとすると、あっという間にGPUメモリオーバーフローが発生します。そのため、限られたリソースの中でモデルや画像の大きさを調節する必要があります。
EfficientNetはモデルや画像の大きさのバランスをうまく調節したモデルで、リソースの大きさに合わせてスケールされた複数個のモデルが用意されています。EfficientNet_b0が一番小さくて、EfficientNet_b7が公開されている中では一番大きいです。以下の図はEfficientNetの論文から引用しているのですが、従来のモデルよりも少ないパラメータ数で高い精度を出していることが分かります。
f:id:t-yoshi-book:20210325013412p:plain

ちなみに、b0~b7のモデルの大きさ(パラメータ数)や推奨される画像の大きさは以下の通りとのことです。(論文サイトから引用)
f:id:t-yoshi-book:20210325015118p:plain
※画像の大きさは上記の通りでなくても動きます。

2. EfficientNetの実装

早速実装に移りたいと思います。以下でEfficientNetを使用してファインチューニングできます。

!pip install -U git+https://github.com/qubvel/efficientnet

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras import optimizers
from efficientnet.tfkeras import EfficientNetB0

image_size = 224
input_shape=(image_size,image_size,3)

def buildModel():
    model = Sequential()
    model.add(EfficientNetB0(
        include_top=False,
        weights='imagenet',
        input_shape=input_shape))
    model.add(GlobalAveragePooling2D())
    model.add(Dense(num_classes, activation="softmax"))
    model.compile(optimizer=optimizers.Adam(learning_rate=1e-4), loss="categorical_crossentropy", metrics=["accuracy"])
    
    return model

model = buildModel()

TensorflowによるViT(Vision Transformer)の実装

CNNに取って代わると言われている画像分析手法、ViT(Vision Transformer)の実装方法についてまとめます。ViTの内容については以下を参照してください。ざっくりとした理解ですが、BERTの画像分析版だと思っています。
画像認識の大革命。AI界で話題爆発中の「Vision Transformer」を解説! - Qiita

1. 実装方法について

以下はViTをファインチューニングする場合のソースコードです。Kaggleで公開されていたコードを参考にしています。本記事末尾にCIFAR10でファインチューニングしたときに使用したソースコードを載せますので、良ければそちらも見てください。

!pip install vit-keras

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, BatchNormalization, Flatten
from tensorflow.keras import optimizers
from vit_keras import vit, utils
import tensorflow_addons as tfa

image_size = 352

def buildModel():
    vit_model = vit.vit_b16(
        image_size = image_size,
        activation = 'sigmoid',
        pretrained = True,
        include_top = False,
        pretrained_top = False)
    model = tf.keras.Sequential([
        vit_model,
        tf.keras.layers.Flatten(),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Dense(11, activation = tfa.activations.gelu),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Dense(num_classes, 'softmax')
    ],
    name = 'vision_transformer')
    model.compile(optimizer=optimizers.Adam(learning_rate=1e-4), loss="categorical_crossentropy", metrics=["accuracy"])

    return model

model = buildModel()

2. ViTとして公開されているモデルの種類

公開されている主なモデルは以下の通りです。

  • ViT-B_16
  • ViT-B_32
  • ViT-L_16
  • ViT-L_32

上記のBとかLとかはモデルのサイズを表します。BはBase、LはLargeなので、大小関係はB<Lです。
16とか32はパッチサイズです。パッチとはViTに投入するために分割した画像のことです。16の場合は16×16のパッチをViTに投入することになります。 パッチについては以下のイメージでなんとなく伝わると思います。
f:id:t-yoshi-book:20210321013655p:plain
(参考:https://openreview.net/pdf?id=YicbFdNTTy

Largeよりも大きいHugeというモデルも存在するようですが、公開はされていないようです。ちなみに各モデルのパラメータ数は以下の通りとのことです。
f:id:t-yoshi-book:20210321014003p:plain
(参考:https://openreview.net/pdf?id=YicbFdNTTy

3. 画像サイズについて

image_sizeはパッチサイズに依存し、パッチサイズの倍数である必要があるようです。ViT-B_16の場合、image_sizeとして224や240等を設定することが可能になります。

参考:ソースコード(CIFAR10でfine-tune)

# 再現性確保
import os
os.environ['PYTHONHASHSEED'] = '0'
import tensorflow as tf
os.environ['TF_DETERMINISTIC_OPS'] = 'true'
os.environ['TF_CUDNN_DETERMINISTIC'] = 'true'

import numpy as np
import random as rn

SEED = 123
def reset_random_seeds():
    tf.random.set_seed(SEED)
    np.random.seed(SEED)
    rn.seed(SEED)

reset_random_seeds()    

session_conf = tf.compat.v1.ConfigProto(intra_op_parallelism_threads=32, inter_op_parallelism_threads=32)
tf.compat.v1.set_random_seed(SEED)
sess = tf.compat.v1.Session(graph=tf.compat.v1.get_default_graph(), config=session_conf)


# ライブラリインポート
import pandas as pd
import cv2

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, BatchNormalization,Flatten

from tensorflow.keras.utils import to_categorical
from tensorflow.keras.preprocessing.image import ImageDataGenerator

from tensorflow.keras import optimizers
from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping
import tensorflow_addons as tfa

from vit_keras import vit, utils

from sklearn.model_selection import train_test_split
from tensorflow.keras.datasets import cifar10


# CIFAR10のデータをインポート
(x_train, y_train), (x_test, y_test) = cifar10.load_data()


# 変数定義
image_size = 128 #CIFAR10の元々のサイズは32。これを128にリサイズする。
input_shape=(image_size,image_size,3)

# 予測するクラス数(CIFAR10の場合は10)
num_classes = 10


# 画像をリサイズ(拡大)する関数を定義
def upscale(image):
    size = len(image)
    data_upscaled = np.zeros((size, image_size, image_size, 3,))
    for i in range(len(image)):
        data_upscaled[i] = cv2.resize(image[i], dsize=(image_size, image_size), interpolation=cv2.INTER_CUBIC)
    image = np.array(data_upscaled, dtype=np.int)
    
    return image

# 画像リサイズ
x_train = upscale(x_train)
x_test  = upscale(x_test)


#モデル定義
def buildModel_ViT():
    vit_model = vit.vit_b16(
        image_size = image_size,
        activation = 'sigmoid',
        pretrained = True,
        include_top = False,
        pretrained_top = False)
    model = tf.keras.Sequential([
        vit_model,
        tf.keras.layers.Flatten(),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Dense(21, activation = tfa.activations.gelu),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Dense(num_classes, 'softmax')
    ],
    name = 'vision_transformer')
    model.compile(optimizer=optimizers.Adam(learning_rate=1e-4), loss="categorical_crossentropy", metrics=["accuracy"])
    
    return model

# 訓練用関数定義
def train_vit_holdout(X, y, steps_per_epoch, epochs, batch_size, callbacks):
    # 訓練データと評価データの分割
    X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, stratify=y, shuffle=True)
    y_train = to_categorical(y_train)
    y_valid = to_categorical(y_valid)
    
    # Data Augumentation
    datagen = ImageDataGenerator(rotation_range=20, horizontal_flip=True, zoom_range=0.2)
    train_generator = datagen.flow(X_train, y_train,batch_size=batch_size)
        
    # モデル構築
    model = buildModel_ViT()

    # 学習
    history = model.fit(train_generator,
                        steps_per_epoch=steps_per_epoch,
                        epochs=epochs,
                        validation_data=(X_valid, y_valid),
                        callbacks=callbacks,
                        shuffle=True
                       )
    
    return model, history

# 訓練実行
batch_size = 32
steps_per_epoch = 1250 # trainデータ=40,000、バッチサイズ=32なので40,000/32=1250とした。
epochs = 1000 # とにかく大きい数字を指定。EarlyStoppingで止まることを期待。

reduce_lr = ReduceLROnPlateau(monitor='val_accuracy',
                              factor=0.2,
                              patience=2,
                              verbose=1,
                              min_delta=1e-4,
                              min_lr=1e-6,
                              mode='max'
                             )
earlystopping = EarlyStopping(monitor='val_accuracy',
                              min_delta=1e-4,
                              patience=5,
                              mode='max',
                              verbose=1
                             )
callbacks = [earlystopping, reduce_lr]

model, history = train_vit_holdout(x_train, y_train, steps_per_epoch, epochs, batch_size, callbacks)


# 予測
X = x_test
pred = model.predict(X)

# 予測結果の確認
df_pred = pd.DataFrame(pred)
pred = np.array(df_pred.idxmax(axis=1))
df_pred = pd.DataFrame(pred)
df_y = pd.DataFrame(y_test)
df_result = pd.concat([df_y, df_pred], axis=1, join_axes=[df_y.index])
df_result.columns = ['y','pred']
display(df_result)

# 混合行列(Confusion Matrix)の確認
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
print('Confusion Matrix:')
print(confusion_matrix(df_result['y'],df_result['pred']))
print()
print('Accuracy :{:.4f}'.format(accuracy_score(df_result['y'],df_result['pred'])))
print('Precision:{:.4f}'.format(precision_score(df_result['y'],df_result['pred'],average='macro')))
print('Recall   :{:.4f}'.format(recall_score(df_result['y'],df_result['pred'],average='macro')))
print('F_score  :{:.4f}'.format(f1_score(df_result['y'],df_result['pred'],average='macro')))

Tensorflowによる再現性の確保について(GPUを使用する場合)

Tensorflowによる再現性の確保について以前の記事でもまとめたが、前回のはどうやら不十分らしく、GPUを使用して大量の計算(画像分析等)を実施する場合に、計算順の違い等により微小な誤差が積み重なって計算結果がずれてしまうことがあるようです。今回はその解決策も含めてまとめます。

<主に参考にしたサイト>
本記事は以下を参考にしています。Tensorflowにかかわる再現性確保手法について、最新の内容がまとめられています。
github.com

1. 結論

最終的に私は以下のコードに落ち着きました。

import os
os.environ['PYTHONHASHSEED'] = '0'
import tensorflow as tf
os.environ['TF_DETERMINISTIC_OPS'] = 'true'
os.environ['TF_CUDNN_DETERMINISTIC'] = 'true'

import numpy as np
import random as rn

SEED = 123
def reset_random_seeds():
    tf.random.set_seed(SEED)
    np.random.seed(SEED)
    rn.seed(SEED)

reset_random_seeds()    

session_conf = tf.compat.v1.ConfigProto(intra_op_parallelism_threads=32, inter_op_parallelism_threads=32)
tf.compat.v1.set_random_seed(SEED)
sess = tf.compat.v1.Session(graph=tf.compat.v1.get_default_graph(), config=session_conf)

2. Tensorflowバージョン2.1.0以前の場合

どうやらTensorFlowのバージョンによって、必要な対応は異なるようです。私の場合はTensorflowの2.3.0なので、上記の通りとしましたが、2.1.0以前の場合は以下を追加する必要があるようです。

import tensorflow as tf
from tfdeterminism import patch
patch()

3. 終わりに

TensorFlowはバージョンによっても実施する内容によっても、再現性の確保の方法が色々と変わるようです。困ったときは上記のサイトに何かしら掲載されていると思いますので、良ければ参考にしてください。

マルチGPUによるTensorFlowの学習

複数のGPUを使用してTensorflowを学習する方法についてまとめます。

1. Mirrored Strategy

Mirrored Strategyとは、Tensor Flowで作成したモデルを複数のGPU、TPUを使用して学習するためのAPIです。Mirrored Strategyの公式ページに細かく解説が載っていますが、本ページでは実装までに必要な最小限のコードをまとめます。

2. 実装

インポートはtensorflowのみでOK。

import tensorflow as tf

Mirrored Strategyの宣言とモデルの作成・コンパイル。モデルは適当です。

mirrored_strategy = tf.distribute.MirroredStrategy()
with mirrored_strategy.scope():
  model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])
  model.compile(loss='mse', optimizer='sgd')

あとは、いつも通りmodel.fitで学習するだけです。model.fitはMirrored Strategyに入れないように気を付けてください。なお、上記の例はすべてのGPUを使用する場合のコードですが、使用するGPUを指定したい場合は以下のようにする必要があります。

mirrored_strategy = tf.distribute.MirroredStrategy(devices=["/gpu:0", "/gpu:1", "/gpu:2"])
with mirrored_strategy.scope():
  model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])
  model.compile(loss='mse', optimizer='sgd')

AWSでJupyter Notebookサーバを起動してクライアントPC(Windows)からアクセスする方法

AWSUbuntu)でJupyter Notebookを起動して、クライアントPC(Windows)からアクセスする方法について紹介する。

1.環境

AWS

AWSマシンイメージ(AMI):Deep Learning AMI (Ubuntu 18.04) Version 41.0
AMIとは:OSや各種ソフトウェア等を含むスナップショット
(参考:AWS AMI とは - Qiita

インスタンスタイプ:p2.xlarge
有償なので気を付けてください。(2021/3/20現在、0.9USD/時間)
(参考:Amazon EC2 P2 インスタンス| AWS
また、AWSのアカウントを作成したばかりの場合はpインスタンスのvCPU上限値が0のはずなので、おそらく使用できないものと思われます。pインスタンスのvCPU上限値の緩和申請をする必要があります。
(参考:EC2 インスタンス数の制限で起動できない | Oji-Cloud

AWSリージョン:米国東部(バージニア北部)us-east-1 どこでも良いと思いますが、2021/3/20現在においてはPインスタンスを使用する場合は最安値のはずです。 それとpインスタンスが使えないリージョンも多々あるので注意です。

<クライアントPC>

① OS:Windows10 Home

② サーバアクセスツール:Tera Term (インストール先:「Tera Term」定番のターミナルエミュレーター - 窓の杜

2.AWSでEC2インスタンスを作成する

1. インスタンス作成

AWSマネジメントコンソールからEC2サービスにアクセスし、「インスタンスを起動」ボタンを押下。
f:id:t-yoshi-book:20210320161945p:plain

2. AMI選択

AMI選択ページに遷移する。目的は機械学習環境の構築なので、Deep Learning AMIを選択する。

3. インスタンスタイプ選択

p2.xlargeを選択し、「6.セキュリティグループの設定」タブを押下。

4. セキュリティグループの設定

デフォルトだとSSH(ポート22)しかアクセス許可されていないので、カスタムTCPルールとしてポート8888を開けて、クライアントPCのブラウザからJupyter Notebookにアクセスできるようにする。以下は例です。「ソース」を指定していないので、どのIPからでもアクセスできるようになっており、セキュリティ的にはガバガバなので、セキュリティを重視する場合は、必要な設定をお願いします。

f:id:t-yoshi-book:20210320163214p:plain
security group : add port 8888

設定できたら、「確認と作成」ボタンを押下。

5. EC2インスタンス起動

内容を確認して問題がなければ「起動」ボタンを押下する。
SSHアクセスするために、キーペアを作成し、キーペアをダウンロードする(キーペア名を適当に入力して「キーペアのダウンロード」ボタンを押下)。ダウンロードできたら、「インスタンスの作成」ボタンを押下。

f:id:t-yoshi-book:20210320164138p:plain
create key pair and download

3.Tera TermでAWSサーバにアクセスしJupyter Notebookサーバを起動

1. AWSのEC2のインスタンスページからパブリックIPを確認
2. Tera Term起動

ホスト名に上記1で確認したパブリックIPを入力し、「OK」を押下。
f:id:t-yoshi-book:20210320164809p:plain
以下のような画面が表示されるので、ユーザ名は「ubuntu」、「パスワードをメモリ上に記憶する(M)」はチェックをオフに、認証方式は「RSA/DSA/ECDSA/ED25519鍵を使う」を選択し、事前に作成したキーペアを指定し、「OK」を押下。
f:id:t-yoshi-book:20210320165453p:plain

3. 機械学習フレームワーク選択

今回はTensorFlowの最新環境(source activate tensorflow2_latest_p37)を実行する。
使用できる環境は以下の通り、コンソールに一覧として表示されるので、必要な環境をコピーして実行する。
f:id:t-yoshi-book:20210320172219p:plain
Tera Termで以下の通り実行する。

$ source activate tensorflow2_latest_p37
4. Jupyter Notebookのセキュリティ設定

AWS公式の通りですが、以下にまとめます。
まずはJupyter Notebookアクセス時に使用するパスワードを指定する。以下コマンドを実行。

$ jupyter notebook password

以下のように出力されるので、出力内容に従ってパスワードを指定する。

Enter password:
Verify password:
[NotebookPasswordApp] Wrote hashed password to /home/ubuntu/.jupyter/jupyter_notebook_config.json

続いて自己署名SSL証明書を発行します。以下の通り実行

$ cd ~
$ mkdir ssl
$ cd ssl
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mykey.key -out mycert.pem

証明書発行にあたってサーバ情報を要求されるので、適当に入力。良く分からないですが、空白にするとJupyternotebookを起動したときにうまくいかない気がします。

Country Name (2 letter code) [XX]: JP
State or Province Name (full name) []: Tokyo
Locality Name (eg, city) [Default City]: Minato
Organization Name (eg, company) [Default Company Ltd]: NNN
Organizational Unit Name (eg, section) []: NNN
Common Name (eg, your name or your server's hostname) []: NNN
Email Address []: test@gmail.com
5. Jupyter Notebookのコンフィグファイル設定

以下を実行して、コンフィグファイルを開きます。エディタはなんでもOKですが、今回はnanoを使用しています。

$ nano ~/.jupyter/jupyter_notebook_config.py

コンフィグファイルが開くので以下の通り編集する。

c.NotebookApp.ip = '*' #アクセス元のIPとして全IPを許可
c.NotebookApp.open_browser = False #ubuntuサーバでJupyter Notebookを起動した際にブラウザを起動しないようにする
c.NotebookApp.port = 8888 #ポート指定

nanoの操作方法も簡単に説明。
 F6キー:文字検索
 Ctrl + X:nanoの終了

6. Jupyter Notebook起動

以下を実行して、Jupyter Notebookを起動する。

$ jupyter notebook --certfile=~/ssl/mycert.pem --keyfile ~/ssl/mykey.key

無事起動できたら以下のように出力される。
f:id:t-yoshi-book:20210320181120p:plain

4.クライアントPCからJupyer Notebookにアクセス

ブラウザで以下の通りアクセス

https://<AWSのパブリックIP>:8888

パスワードを求められるので、事前に設定したパスワードを入力してログイン。

KerasのCallback:ModelCheckpoint使用時のエラー「Error! hdf5file is not UTF-8 encoded」について

KerasのCallBack:ModelCheckpointを使ったところ「Error! hdf5file is not UTF-8 encoded」というエラーにぶつかりました。結論ですが、無視でOKです。私みたいに余計な調べものをする人が減ることを祈ります。

KerasにはModelCheckpointというクラスが用意されていて、Kerasで学習する際に最良のモデルをhdf5というファイル形式で保存することができます。ところが、試しにModelCheckpointを使用し、出力されたhdf5ファイルをJupyterNotebook上で確認したところ、以下のようなエラーメッセージが出力されていました。

Error! C:hoge/hoge.hdf5 is not UTF8-encoded<br>
Saving disabled.<br>
See Console for more details<br>

冒頭でも言いましたが、上記のエラーは問題ないようです。 上記エラーを見たときに、ModelCheckpointのパラメータか何かで出力ファイルの文字コードをUTF8形式指定しないといけないのかなと思ったのですが、どうやら単純にJupyterNotebookがUTF8以外のファイルを開けないってだけらしいです。JupyterNotebook以外でファイルを開けば、問題なくファイルが生成されていることが確認できます。

参考にしたサイト:

stackoverflow.com

Keras分析結果の再現性確保について

Kerasの再現性確保方法についてまとめます。主にKerasの公式ドキュメントを参考にしていますが、一部注意点があるので、その点を中心に解説します。

1.環境

GPU:GeForce GTX 1070
Python:3.7.3
Keras:2.4.3
tensorflow-gpu:2.4.0

再現性の確保の仕方に影響を与えるか分からないですが、分析モデルのOptimizer(最適化アルゴリズム)はadamを使いました。

2.公式ドキュメント(Keras Documentation)の記述

抜粋すると以下のような内容でした。

import numpy as np
import tensorflow as tf
import random as rn

import os
os.environ['PYTHONHASHSEED'] = '0'

np.random.seed(42)
rn.seed(12345)

session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)

from keras import backend as K
tf.set_random_seed(1234)
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)

(参考:FAQ - Keras Documentation
たしかに上記で再現性を確保できるのですが、少し注意する点があります。

3.注意点

注意点1:Tensorflowのバージョンの問題

Tensorflowバージョン2の場合は上記はサポートされておらず、バージョン1として実行してやる必要があります。そのまま実行すると「AttributeError: module 'tensorflow' has no attribute 'Session'」というエラーが出ます。
修正版のソースは以下の通りです。

import numpy as np
import tensorflow as tf
import random as rn

import os
os.environ['PYTHONHASHSEED'] = '0'

np.random.seed(42)
rn.seed(12345)

session_conf = tf.compat.v1.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)

from keras import backend as K
tf.compat.v1.set_random_seed(1234)
sess = tf.compat.v1.Session(graph=tf.compat.v1.get_default_graph(), config=session_conf)
tf.compat.v1.keras.set_session(sess)

注意点2:Tensorflowセッションを都度開始・終了する必要がある

上記ではセッション開始は定義されているのですが、終了が定義されていません。終了定義せずにJupyterNotebook等で分析部分のセルを繰り返し実行すると、結果が再現しなくなってしまいますので注意が必要です。原因はおそらく、セッションを終了せずに分析処理を2回以上実行すると、1回目の分析の値が残存し、2回目以降の分析に影響してしまうためと思われます。
終了定義を追記した修正版ソースは以下の通りです。※モデル部分はコメントアウトして割愛しています。

import numpy as np
import tensorflow as tf
import random as rn

import os
os.environ['PYTHONHASHSEED'] = '0'

np.random.seed(42)
rn.seed(12345)

session_conf = tf.compat.v1.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)

#セッション開始
from keras import backend as K
tf.compat.v1.set_random_seed(1234)
sess = tf.compat.v1.Session(graph=tf.compat.v1.get_default_graph(), config=session_conf)
tf.compat.v1.keras.set_session(sess)

'''
分析モデル
'''

#セッション終了
K.clear_session() 
【2021/3/21追記】

GPUを使用する場合、上記では不十分らしいです。以下にGPUを使用した場合の方法についてまとめました。
book-read-yoshi.hatenablog.com