TensorFlow は定評のある Deep Learning フレームワークで、Keras はその公式高レベル API で、モデルの作成を簡略化するものです。画像認識/分類は一般的なタスクですが、ありがたいことに、Kerasを使えばかなりわかりやすく、簡単にできます。
このガイドでは、PythonでKerasを使って画像を分類/認識する方法について見ていきます。
です。
コードを弄ってみたい方、もう少し深く勉強したい方は、GitHubにプロジェクトをアップロードしています。
このガイドでは、カスタムCNNを構築し、ゼロからトレーニングしていきます。より高度なガイドについては、既存の高性能なアーキテクチャで知識表現を転送するために転送学習を活用することができます – Kerasの転送学習による画像分類 – 最先端のCNNモデルを作ろう!をお読みください。
をご覧ください。
定義
画像分類の背後にある基本的な概念が明確でなければ、このガイドの残りの部分を完全に理解することは困難でしょう。そこで、先に進む前に、いくつかの用語を定義しておきましょう。
TensorFlow/Keras
TensorFlowは、Google BrainチームによってPython用に作成されたオープンソースライブラリです。TensorFlowは、多くの異なるアルゴリズムとモデルをまとめ、画像認識/分類や自然言語処理などのタスクで使用するディープニューラルネットワークを実装することが可能です。TensorFlowは、一連の処理ノードを実装することで機能する強力なフレームワークで、各ノードは数学演算を表し、一連のノード全体は「グラフ」と呼ばれています。
Kerasで言えば、その下にあるTensorFlowの機能を利用できる高水準API(アプリケーションプログラミングインターフェース)です(Theanoのような他のMLライブラリも同様)。Kerasは、使いやすさとモジュール性を指針として設計されました。実用面では、KerasはTensorFlowの多くの強力だが複雑になりがちな機能をできるだけシンプルに実装し、大きな修正や設定なしにPythonで動作するように構成されています。
画像分類(認識)
画像認識とは、ニューラルネットワークに画像を入力し、その画像に対して何らかのラベルを出力させる作業のことです。ニューラルネットワークが出力するラベルは、あらかじめ定義されたクラスに対応する。画像のラベルは、複数のクラスがあってもよいし、1つだけであってもよい。クラスが1つの場合は「認識」と呼ばれることが多いが、複数クラスの認識タスクは「分類」と呼ばれることが多い。
画像分類のサブセットには物体検出があり、物体の特定のインスタンスが動物、車、人などの特定のクラスに属していると識別される。
特徴抽出
画像認識・分類を行うためには、ニューラルネットワークは特徴抽出を行う必要があります。特徴とは、ネットワークに流すデータの中で気になる要素のことである。画像認識の場合、特徴とは、ネットワークがパターンを分析する対象物のエッジや点などのピクセル群のことである。
特徴認識(または特徴抽出)とは、入力画像から関連する特徴を取り出し、これらの特徴を分析できるようにするプロセスである。多くの画像には、ネットワークが関連する特徴を見つけるのに役立つ、画像に関する注釈やメタデータが含まれています。
ニューラルネットワークはどのように画像認識を行うのか – 畳み込みニューラルネットワークの入門編
ニューラルネットワークがどのように画像を認識するのかについて直感的に理解することは、ニューラルネットワークモデルを実装する際に役立ちますので、次のセクションで画像認識プロセスを簡単に探ってみましょう。
このセクションは、畳み込みニューラルネットワークのクラッシュコース/予備知識として、また、畳み込みニューラルネットワークに慣れている人のための復習として提供されるものです。
フィルタによる特徴抽出
出典:commons.wikimedia.org
ニューラルネットワークの第1層は、画像内のすべてのピクセルを取り込みます。すべてのデータがネットワークに入力された後、画像にさまざまなフィルターが適用され、画像のさまざまな部分の表現が形成されます。これが特徴抽出であり、「特徴マップ」を作成する。
この画像からの特徴抽出のプロセスは「畳み込み層」で実現されており、畳み込みとは、簡単に言えば画像の一部の表現を形成することである。この畳み込みの概念から、画像の分類/認識で最もよく使われるニューラルネットワークの一種であるCNN(Convolutional Neural Network)という用語が生まれました。最近、Transformerは画像の分類においても素晴らしい成果を上げていますが、これはRecurrent Neural Network(RNN)アーキテクチャをベースにしています。
畳み込みニューラルネットワークのための特徴マップを作成する方法を視覚化したいなら、暗い部屋の中で写真に懐中電灯を当てることを考えてみてください。写真の上でビームを滑らせると、画像の特徴を学習することになる。ネットワークが画像の表現を形成するために使用するのがフィルターであり、この比喩では懐中電灯の光がフィルターにあたります。
懐中電灯の光の幅は、一度に検査する画像の量を制御します。ニューラルネットワークにも同様のパラメータ、フィルタサイズがあります。フィルターサイズは、一度に検査される画像の量、つまりピクセル数に影響します。CNNでよく使われるフィルターサイズは3で、これは縦と横の両方をカバーするので、フィルターは3×3のピクセルの領域を調べることになります。
出典:commons.wikimedia.org
フィルタサイズはフィルタの高さと幅をカバーしますが、フィルタの深さも指定する必要があります。
2D画像に奥行きを持たせるには?
デジタル画像は、高さ、幅、およびピクセルの色を定義するいくつかのRGB値としてレンダリングされます。したがって、追跡される「深さ」は、画像が持つ色チャンネルの数です。グレースケール(非カラー)画像には1つのカラーチャンネルしかなく、カラー画像には3つの深度チャンネルがあります。
つまり、フルカラー画像にサイズ3のフィルターをかけると、フィルターの大きさは3×3×3となり、そのフィルターにかかったすべてのピクセルに対して、ネットワークはフィルターの値とピクセル自身の値を掛け合わせて、そのピクセルの数値表現を得ます。この処理を画像全体に対して行うことで、完全な表現が可能になる。フィルターは「ストライド」と呼ばれるパラメータに従って画像の残りの部分を移動する。ストライドは、フィルターが現在の位置の値を計算した後、何ピクセル移動するかを定義する。CNNの標準的なストライドサイズは2です。
このような計算の最終結果が特徴マップである。この処理は通常、複数のフィルターで行われ、画像の複雑さを保持するのに役立ちます。
起動機能
画像の特徴マップを作成した後、画像を表す値は活性化関数または活性化層に渡される。活性化関数は、畳み込み層によって線形になった画像を表す値を、画像自体が非線形であることから、その非線形性を増大させるものである。
このために使われる典型的な活性化関数はRectified Linear Unit (ReLU)ですが、時折他の活性化関数も使われます(それらについてはこちらを参照してください)。
プーリングレイヤー
データがアクティブになった後、プーリングレイヤーを経由して送信されます。プーリングは画像を「ダウンサンプリング」する。つまり、画像を表す情報を取り込み、それを圧縮して小さくする。このプーリング処理により、ネットワークはより柔軟になり、関連する特徴に基づいて物体や画像を認識することができるようになる。
私たちが画像を見るとき、通常、画像の背景にあるすべての情報には関心がなく、人物や動物など、関心のある特徴だけに関心がある。
同様に、CNNのプーリング層は画像の不要な部分を取り除き、プーリング層の指定サイズによって制御されるように、画像の関連性があると考えられる部分のみを残す。
CNNは画像中の最も関連性の高い部分を判断しなければならないので、ネットワークは画像中の本当に対象物を表す部分のみを学習することが期待される。これは、ネットワークが学習ケースの側面を学習しすぎて、新しいデータへの汎化に失敗するオーバーフィッティングを防ぐのに役立つ。
出典:commons.wikimedia.org
値をプールする方法は様々あるが、最大プーリングが最もよく使われる。マックスプーリングは、1つのフィルター内(画像内の1つのスポット内)のピクセルの最大値を取得します。これは、2×2のフィルターを使用していると仮定して、3/4の情報を落とすことになります。
ピクセルの最大値は、画像の歪みを考慮するために使用され、画像のパラメータやサイズはオーバーフィッティングを抑制するために縮小されます。プーリングには他にも平均プーリングや合計プーリングなどがありますが、最大プーリングの方が精度が高い傾向があるため、あまり使用されません。
平坦化
CNNの最後の層である密結合層は、処理するデータがベクトルの形であることを要求する。このため、データは「平坦化」されなければならない。値は長いベクトルあるいは連続的に並べられた数値の列に圧縮される。
完全連結層
CNNの最後の層は密結合層、つまり人工ニューラルネットワーク(ANN)である。ANNの主な機能は、入力された特徴を分析し、分類を助ける異なる属性に結合することである。これらの層は基本的に、問題のオブジェクトのさまざまな部分を表すニューロンの集合体を形成しており、ニューロンの集合体は、犬のはねた耳やリンゴの赤さを表すことがある。入力画像に反応してこれらのニューロンが十分に活性化されると、その画像は物体として分類される。
出典:commons.wikimedia.org
誤差、つまり計算された値と学習セットで期待される値との差は、ANNによって計算される。そして、ネットワークはバックプロパゲーションを行い、あるニューロンが次の層のニューロンに与える影響を計算し、その影響力を調整する。これはモデルの性能を最適化するために行われる。このプロセスは何度も繰り返される。このようにしてネットワークはデータを学習し、入力の特徴と出力のクラスとの間の関連性を学習する。
中間の完全連結層のニューロンは、考えられるクラスに関連するバイナリ値を出力する。4つの異なるクラス(例えば、犬、車、家、人)がある場合、ニューロンは、画像が表すと考えるクラスに対して「1」の値を持ち、他のクラスに対しては「0」の値を持つことになる。
最後の完全連結層は、その前の層の出力を受け取り、それぞれのクラスの確率の和が1になるように出力する。もし「犬」カテゴリに0.75の値があれば、その画像が犬である確率は75%であることを意味する。
これで画像分類器の学習が完了し、CNNに画像を渡すことができるようになり、画像の内容に関する推測が出力されるようになった。
機械学習のワークフロー
画像分類器のトレーニングの例に入る前に、機械学習のワークフローやパイプラインを理解するために少し時間が必要です。ニューラルネットワークモデルの学習プロセスはかなり標準的で、4つの異なるフェーズに分けることができます。
データ準備
まず、データを収集し、ネットワークが学習できるような形にする必要があります。これには画像の収集とラベル付けが必要です。誰かが用意したデータセットをダウンロードした場合でも、学習に使用する前に前処理や準備が必要な場合が多い。データの準備とは、欠損値、破損したデータ、間違った形式のデータ、間違ったラベルなどを扱うことであり、それ自体が芸術である。
この記事では、前処理されたデータセットを使用します。
モデルの作成
ニューラルネットワークのモデルを作成するには、さまざまなパラメータとハイパーパラメータを選択する必要があります。モデルで使用する層の数、層の入力と出力のサイズ、使用する活性化関数の種類、ドロップアウトを使用するかどうか、などについて決定する必要があります。
どのパラメータとハイパーパラメータを使うかは、時間をかけて(そしてたくさん勉強して)学ぶことになりますが、最初から実行するために使えるヒューリスティックがいくつかあります。
モデルのトレーニング
モデルを作成したら、モデルのインスタンスを作成し、学習データでフィットさせるだけです。モデルを学習する際に最も考慮すべきことは、モデルの学習に要する時間です。ネットワークの学習時間は、学習するエポック数を指定することで指定できます。モデルの学習時間が長ければ長いほど、その性能は向上しますが、学習エポック数が多すぎるとオーバーフィットの危険性があります。
また、学習セッションの間にネットワークの重みを保存しておくと、一度学習を進めた後にやり直す必要がありません。
モデル評価
モデルの評価には複数のステップがある。モデルを評価する最初のステップは、モデルの性能を検証用データセット(モデルが学習していないデータセット)と比較することです。この検証用データセットに対してモデルの性能を比較し、さまざまな測定基準によってその性能を分析することになります。
ニューラルネットワークモデルのパフォーマンスを決定するための様々なメトリックがありますが、最も一般的なメトリックは「精度」であり、正しく分類された画像の量をデータセットの画像の総数で割ったものです。
検証用データセットでモデルの性能の正確さを確認した後、通常、最初の訓練でネットワークの性能に満足することはまずないため、少し調整したパラメータを使って再度ネットワークを訓練します。ネットワークの精度に満足できるまで、ネットワークのパラメータを微調整し、再トレーニングを行い、そのパフォーマンスを測定し続けることになる。
最後に、テストセットでネットワークのパフォーマンスをテストします。このテストセットは、モデルが今まで見たことのない別のデータセットである。
おそらく、あなたは疑問に思うでしょう。
と疑問に思うかもしれません。
なぜわざわざテストセットを使うのか?もし、モデルの精度を知るのであれば、検証セットの目的はそれではないのでしょうか?
ということです。
なぜなら、検証セットで再テストを行いながらパラメータを調整すると、ネットワークが検証セットの特異性を学習してしまい、サンプル外のデータに対して一般化できない可能性があるからです。
したがって、テストセットの目的は、オーバーフィッティングのような問題をチェックし、あなたのモデルが実世界でのパフォーマンスに本当に適合しているかどうか、より確信を持つことです。
KerasのCNNによる画像認識・分類
これまで多くの情報を見てきましたが、もしこれらの情報に少し圧倒されたのであれば、データセットで訓練された分類器のサンプルでこれらの概念を見ることで、これらの概念がより具体的になるはずです。それでは、データのロードから評価まで、Kerasを使った画像認識の完全な例を見てみましょう。
出典: www.cs.toronto.edu
まず始めに、学習するためのデータセットが必要です。この例では、有名なCIFAR-10データセットを使用します。CIFAR-10は、猫、飛行機、車など10種類の異なるクラスのオブジェクトを表す6万枚以上の画像を含む大規模な画像データセットです。
画像はRGBのフルカラーですが、32×32とかなり小さいです。CIFAR-10データセットの素晴らしい点は、Kerasにあらかじめパッケージされているため、データセットを読み込むのが非常に簡単で、画像はほとんど前処理を必要としないことです。
まず最初にやるべきことは、必要なライブラリのインポートです。これらのインポートがどのように使われるかは追って紹介しますが、とりあえずNumpyと、Kerasに関連する様々なモジュールを利用することを知っておいてください。
import numpy
from tensorflow import keras
from keras.constraints import maxnorm
from keras.utils import np_utils
この記事で得られた結果をあなたにも再現できるように、ここではランダムシードを使用することにします。
# Set random seed for purposes of reproducibility
seed = 21
データの準備
もうひとつ、データセットのインポートが必要です。
from keras.datasets import cifar10
では、データセットをロードしてみましょう。データをロードする変数を指定し、load_data()
関数を使えば簡単にロードできます。
# Loading in the data
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
ほとんどの場合、データを使えるようにするために何らかの前処理をする必要がありますが、今回はパッケージ化されたデータセットを使っているので、前処理はほとんど必要ありません。ひとつは、入力データの正規化である。
入力データの値の範囲が広すぎると、ネットワークのパフォーマンスに悪い影響を与える可能性がある。この場合、入力値は画像のピクセルで、0から255の間の値を持っている。
したがって、データを正規化するには、画像の値を単純に255で割ればよいことになります。これを行うには、まずデータを float 型にする必要があります。現在データは整数値だからです。これは astype()
Numpy コマンドを使用して、どのようなデータ型にするかを宣言することで行うことができます。
# Normalize the inputs from 0-255 to between 0 and 1 by dividing by 255
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train = X_train / 255.0
X_test = X_test / 255.0
もう一つ、データをネットワークに接続するために必要なことは、値を一発符号化することです。ここではワンホットエンコーディングの詳細については触れませんが、とりあえず画像はそのままではネットワークで使えないので、まずエンコードする必要があり、ワンホットエンコーディングは2値分類をするときに使うとよいでしょう。
画像はあるクラスに属するか属さないか、その中間のクラスには属さないので、ここでは事実上2値分類を行っています。Numpyのコマンド to_categorical()
はワンホットエンコードに使用されます。このため、Kerasから np_utils
関数をインポートしました。この関数には to_categorical()
が含まれているからです。
また、データセットに含まれるクラスの数を指定する必要があるので、最終層を何ニューロンまで圧縮すればよいかが分かります。
# One-hot encode outputs
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
class_num = y_test.shape[1]
モデルの設計
CNNモデルを設計する段階まで来ました。Kerasにはモデルを構築するためのいくつかの異なるフォーマットやブループリントがありますが、Sequential
が最も一般的に使用されており、そのためKerasからインポートしています。
モデルの作成
シーケンシャルモデルは、空白のインスタンスを作成し、それにレイヤーを追加することによって構築することができます。
model = Sequential()
model.add(keras.layers.layer1)
model.add(keras.layers.layer2)
model.add(keras.layers.layer3)
または、各レイヤーをリストの要素として Sequential()
コンストラクタの呼び出しに渡すこともできます。
model = keras.Sequential([
keras.layers.layer1,
keras.layers.layer2,
keras.layers.layer3
])
このモデルの最初の層は畳み込み層である。これは入力を受け取り、それに対して畳み込みフィルタを実行します。
Kerasでこれらを実装する場合、欲しいチャンネル/フィルタの数(以下32個)、欲しいフィルタのサイズ(今回は3×3)、入力形状(第1層作成時)、必要なアクティベーションとパディングを指定しなければならない。これらはすべてCNNのハイパーパラメータであり、チューニングの対象となりやすい。前述のように、relu
は最も一般的な活性化であり、padding='same'
は画像のサイズを全く変えないということです。他の活性化レイヤーを試すこともできます。しかし、relu
はチューニングの前にまず試すべき、非常に賢明なデフォルトです。
model = keras.Sequential()
model.add(keras.layers.Conv2D(32, (3, 3), input_shape=X_train.shape[1:], padding='same'))
model.add(keras.layers.Activation('relu'))
注:活性化レイヤーは実質的にすべてのレイヤーの後に存在するので、代わりに前のレイヤーに文字列引数としてそれを追加することができます。Keras は自動的にアクティベーション・レイヤーを追加し、この方法は一般的にはるかに読みやすくなります。
model.add(keras.layers.Conv2D(32, 3, input_shape=(32, 32, 3), activation='relu', padding='same'))
ドロップアウト層は、レイヤー間の接続をランダムに削除することで機能します(0.2
は、既存の接続の20%を削除することを意味します)。
model.add(keras.layers.Dropout(0.2))
また、ここでバッチ正規化を追加したい場合がある。バッチ正規化は次の層に向かう入力を正規化し、ネットワークが常に我々が望むのと同じ分布で活性化を作成することを保証する。
model.add(keras.layers.BatchNormalization())
これはCNNを構築するのに使われる基本的なブロックである。畳み込み層、活性化、ドロップアウト、プーリング。これらのブロックは次に積み重ねることができ、典型的には複雑さの点からピラミッド型になる。次のブロックは通常、より大きなフィルターを持つ畳み込み層を含み、より詳細にパターンを見つけ、さらに抽象化することができ、次にプーリング層、ドロップアウト、バッチ正規化が続く。
model.add(keras.layers.Conv2D(64, 3, activation='relu', padding='same'))
model.add(keras.layers.MaxPooling2D(2))
model.add(keras.layers.Dropout(0.2))
model.add(keras.layers.BatchNormalization())
畳み込み層の正確な数はお好みで変えられますが、1つ増えるごとに計算コストが増えます。畳み込み層を追加するとき、モデルがより複雑な表現を学習できるように、一般的にそのフィルター数を増加させることに注意してください。これらのレイヤーに選ばれた数が多少任意に思えるかもしれませんが、一般的には、進むにつれてフィルタを増やし、GPUで学習するときにわずかな利益を与えることができる2の累乗にすることが推奨されます。
プーリング層はあまり多くしないことが重要です。各プーリングは、与えられたファクターで入力の次元を削減することにより、いくつかのデータを破棄するからです。私たちの場合は、画像を半分にカットしています。プーリングが多すぎると、密結合層が学習するデータがほとんどなくなってしまう。
プーリングレイヤーの正確な数は、タスクによって異なるので、時間をかけて感覚をつかんでください。ここではすでに画像が小さいので、2回以上プールすることはないでしょう。
このレイヤーを繰り返すことで、より多くの表現力をネットワークに与えることができます。
model.add(keras.layers.Conv2D(64, 3, activation='relu', padding='same'))
model.add(keras.layers.MaxPooling2D(2))
model.add(keras.layers.Dropout(0.2))
model.add(keras.layers.BatchNormalization())
model.add(keras.layers.Conv2D(128, 3, activation='relu', padding='same'))
model.add(keras.layers.Dropout(0.2))
model.add(keras.layers.BatchNormalization())
畳み込み層が終わったら、データを「平坦化」する必要があります。これが上記の関数をインポートした理由です。また、ドロップアウトのレイヤーを再び追加する。
model.add(keras.layers.Flatten())
model.add(keras.layers.Dropout(0.2))
ここで Dense
のインポートを利用し、最初の密結合層を作成します。密な層のニューロンの数を指定する必要があります。後続の層のニューロン数は減少し、最終的にはデータセットのクラスの数と同じニューロン数(この場合は10)になることに注意してください。
ここで複数の密な層を持つことができ、これらの層は特徴マップから情報を抽出し、特徴マップに基づき画像を分類することを学習する。今回はかなり小さな画像にかなり小さな特徴マップを凝縮しているので、複数の密なレイヤーを持つ必要はありません。32個の神経細胞で構成されるシンプルな層が1つあれば十分です。
model.add(keras.layers.Dense(32, activation='relu'))
model.add(keras.layers.Dropout(0.3))
model.add(keras.layers.BatchNormalization())
注意:高密度レイヤーに注意。この層は全結合であるため、1つの層ではなく2つの層を持つだけで、学習可能なパラメーターの数が大幅に増加する。例えば、3つの密な層(128、64、32)があった場合、学習可能なパラメータ数は、このモデルの400kに対して、2.3Mと急激に増加することになります。このモデルでは、学習時間が長くなることに加え、より大きなモデルは、実際には精度が低くなってしまいます。
最後の層では、ニューロン数に対してクラス数を渡します。各ニューロンはクラスを表し、この層の出力は10ニューロンベクトルとなり、各ニューロンには問題の画像がそれが表すクラスに属するという確率が格納されます。
最後に、「ソフトマックス」活性化関数は、最も高い確率を持つニューロンを出力として選択し、画像がそのクラスに属することを投票します。
model.add(keras.layers.Dense(class_num, activation='softmax'))
さて、m
さらに上を目指す – 手作りのEnd to Endプロジェクト
好奇心旺盛なあなたは、もっと先を目指したいと思っていませんか?私たちのガイド付きプロジェクトをチェックすることをお勧めします。「Kerasで最初のCNNを構築する “をご覧ください。
を使うことができます。
なぜ、あるクラスが予測されたのか?モデルはどこで間違っていたのか?何が原因で間違った予測になったのか?モデルは何に注目していたのか?学習された特徴はどのようなものか?別個のクラスに分けることができるのか?クラスNを構成する特徴はクラスMを構成する特徴とどの程度近いか?
これがあなたのモデルの潜在特徴空間を可視化したもので、それ以外は隠されています。
このガイド付きプロジェクトでは、あなたのモデルを構築するプロセスを紹介します。このガイド付きプロジェクトでは、Kerasを使用して独自のCNNを構築するプロセスを、あなたが基礎に精通していることを前提に進めていきます。
このプロジェクトでは、実践的で手探りなアプローチを通して、以下のことを学びます。
- 共起とデータセットにおける共起バイアスの原因
- データセットの検索、ダウンロード、データの抽出
- 画像のサブセットの可視化
- データの読み込みと前処理
- データ補強とKerasのImageDataGeneratorクラスの約束と危険性
- カスタムCNNアーキテクチャの定義
- LRFinderをKerasで実装し、学習率を自動で求める
- モデルの分類能力を評価する
- モデルの予測値の解釈と誤差の評価
- 何がネットワークに間違った予測をさせるのか
- モデルのアテンションマップを解釈し、tf-keras-visとGradCam++でモデルが実際に何を学習しているかを特定する。
- 主成分分析とt-SNEによる、モデルの畳み込み層が何を学習したかの解釈
- 類似性検索エンジンがどのように類似画像を見つけるか
結論
Kerasで最初の画像認識ネットワークを実装したので、モデルで遊んでみて、パラメータを変更することで性能にどのような影響があるかを確認するとよいでしょう。
これにより、様々なモデルパラメータの最適な選択について直感的に理解できるようになります。また、パラメータとハイパーパラメータの選択について読んでおくとよいでしょう。これらに慣れたら、別のデータセットに自分の画像分類器を実装してみましょう。