こんにちは。業務ではエンジニアリングから離れていて、やったことで書けるものあんまりないんですが、
ちょっと時間ができたので自分でちょっと勉強した内容をまとめておこうと思います。
アドベントカレンダー怖くないよ!チュートリアルやっただけの記事でも書いていいんだよ!ってことでひとつ...
想定読者
機械学習のことを多々耳にするものの、あまり実装に触れたことがなかったので触れてみます。
なので想定読者は機械学習に触れたことがないけど興味があって、機械学習の実装を覗いてみようという方々です。
熟練の方々は温かい目で、ミスってる部分の指摘をいただけると助かります
テーマ
とっつきやすいテーマが無いと目指すべきゴールがわからず道に迷いますね。
ということで今回は画像を与えたときに、写っているものが何かを分類する、
画像分類器を作りましょう。
なんでもいいですが、何の分類器にするかは最初に決めておきましょう。
私は最近カレーにハマり続けているので、スパイスの画像分類器を作ることを目的にしてみます。
弊ブログもよろしくお願いします
前提知識
画像分類器はキャッチーなテーマなので、チュートリアル記事なんかもたくさんあるわけですが、何の前提知識も無いまま読み始めるより、以下のような記事でサラッと全体を把握しておくことをおすすめします。
数学知識もいらないゼロからのニューラルネットワーク入門
ニューラルネットワークの「基礎の基礎」を理解する ~ディープラーニング入門|第1回
あと、専門用語っぽい言葉には参考リンクを付けておきますのでそちらも参照ください。
チュートリアルをやる
tensorflow(てんそるふろー *1)転移学習(*2)のチュートリアルをやっていきます。
Transfer learning with TensorFlow Hubってやつですね。
2019/12のバージョンの項目に合わせて書いていますが、変更があった場合はいい感じに読み替えてもらえると良いと思います。
それぞれのセクションのコードは転載していますが、一部を除いて結果は載せておりませんのでご自身で実行しながら読んでみてください。
(*1)tensorflowとは機械学習のライブラリ群の一つです、googleが作ってます。wiki
(*2)転移学習とは別のタスクで学習したモデルを他のタスクに流用する手法です。
今回のケースだと本当はかなり大量のデータを用意しないと精度が上がらないのを、
かなり少ない枚数で解決出来るみたいな感じです。
プログラミング言語を1つ習熟すると、2つ目の習熟が早い〜みたいなノリですたぶん。参考
Setup
前提としてこのチュートリアルはGoogleColaboratory(以下Colab *3)を使用しているので、上部の「Run in Google Colab」をクリックして実行できるようにしましょう。
Colabの画面が開いたら、上から順番に実行していってみましょう。コードの左上の[]のあたりにカーソルを合わせると実行ボタンが出てくるはず。
from __future__ import absolute_import, division, print_function, unicode_literals
import matplotlib.pylab as plt
import tensorflow as tf
このあたりは諸々ライブラリのインポートをやっていますね。
__future__ ..
の記述があるのはpython2-3の互換性を保つための記述。
import matplotlib.pylab as plt
はあとで使用するプロットのためののライブラリ。
!pip install -q -U tf-hub-nightly
import tensorflow_hub as hub
from tensorflow.keras import layers
!pip install -q -U tf-hub-nightly
の記述はpythonのライブラリのインストール。
tensorflow_hub
とは事前学習済みのモデルを簡単に利用できるプラットフォーム(tensorflow hub)を利用するためのライブラリ。
tensorflow.keras
とは機械学習のライブラリで、googleのエンジニアが作ったようだがtensorflow以外からも使用できるもの。
テキスト的にパチパチ書くだけで仮想マシン上でこんなに簡単に実行出来るってすごい。
(*3)Colaboratory は、完全にクラウドで実行される Jupyter ノートブック環境です。設定不要で、無料でご利用になれます。
Colaboratory を使用すると、コードの記述と実行、解析の保存や共有、強力なコンピューティング リソースへのアクセスなどをブラウザからすべて無料で行えます。
Colaboratoryへようこそより
ちなみに、こんなエラーが出たときは支持に従って[RESTART RUNTIME]のボタンを押して、もっかい再生ボタンを押すと良いです。
An ImageNet classifier
学習済みモデルを利用して、画像分類をやってみましょう、という項目です。
Download the classifier
classifier_url ="https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/2" #@param {type:"string"}
学習済み分類器のURLです。チュートリアルに書いてる通り、ここに置いてあるやつだったらなんでもいいよーとのことです。
IMAGE_SHAPE = (224, 224)
classifier = tf.keras.Sequential([
hub.KerasLayer(classifier_url, input_shape=IMAGE_SHAPE+(3,))
])
ニューラルネットワークの層として学習済みモデルを突っ込みます。
与えられる画像のフォーマットを教えておく必要があるようなので、(width, height, channel)で与えます。
channel(色数)の3は後で追加してますね。深い意味はあるんだろうか。
Run it on a single image
準備ができたところで、適当な画像で判別させてみましょう。
import numpy as np
import PIL.Image as Image
grace_hopper = tf.keras.utils.get_file('image.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg')
grace_hopper = Image.open(grace_hopper).resize(IMAGE_SHAPE)
grace_hopper
grace_hopper氏の画像を取得しています。
grace_hopper = np.array(grace_hopper)/255.0
grace_hopper.shape
型変換をしているようです。
result = classifier.predict(grace_hopper[np.newaxis, ...])
result.shape
画像のデータに1次元追加して予測を走らせてみると、1001個の要素のベクトルの配列(正確にはndarrayかな)が帰ってきます。
predicted_class = np.argmax(result[0], axis=-1)
predicted_class
この1001の要素はそれぞれのクラス(今回の場合は類推される要素)の確率を表しているので、最大の確率のものを取り出してみます。
653という値が帰ってきます。これが今回の分類器が類推したモノを表しています。
Decodethe predictions
labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
imagenet_labels = np.array(open(labels_path).read().splitlines())
先程の結果が何を示しているのかを探るために、ラベルをダウンロードして配列に詰めておきます。
URLを直接アクセスするとすぐわかりますが、先程の1001の項目に対応しています。
plt.imshow(grace_hopper)
plt.axis('off')
predicted_class_name = imagenet_labels[predicted_class]
_ = plt.title("Prediction: " + predicted_class_name.title())
画像表示ライブラリを使って、さっきの653番目の画像が何だったのかを出力します。
無事、Military Uniformという結果が出ましたね。めでたし。
Simple transfer learning
ここからが本番です。今のは「こんな感じで学習済み分類器って使えるぞ〜」でしたが、
次は「自分が集めてきた画像で分類器作るぞ〜」です。
Dataset
今回のチュートリアルでは花の画像を使います。最後にこのデータセットを適当に入れ替えて、別の分類器にしましょう。
data_root = tf.keras.utils.get_file(
'flower_photos','https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
untar=True)
データセットのダウンロード
image_generator = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1/255)
image_data = image_generator.flow_from_directory(str(data_root), target_size=IMAGE_SHAPE)
画像群を上手く処理するためにImageDataGenaratorに突っ込みます。
ちなみに画像の分類も先述のURLから画像群をダウンロードしてみるとわかりますが、
ディレクトリごとに特定の画像を集めている感じになってます。
for image_batch, label_batch in image_data:
print("Image batch shape: ", image_batch.shape)
print("Label batch shape: ", label_batch.shape)
break
image_dataをイテレートしてimage_batchとlabel_batchの配列をつくります。
Run the classifier on a batch of images
学習後の状態と比較するために、学習していない状態で分類してみます。
result_batch = classifier.predict(image_batch)
result_batch.shape
さっきと同じく予測してみましょう。
predicted_class_names = imagenet_labels[np.argmax(result_batch, axis=-1)]
predicted_class_names
32件の分類結果が出ました。画像と照らし合わせて確認します。
plt.figure(figsize=(10,9))
plt.subplots_adjust(hspace=0.5)
for n in range(30):
plt.subplot(6,5,n+1)
plt.imshow(image_batch[n])
plt.title(predicted_class_names[n])
plt.axis('off')
_ = plt.suptitle("ImageNet predictions")
結果を見るとかなりかけ離れてますね。
1001の対象物から選んでいるからと言うのもありそうですが、全然正解していません。
Download the headless model
ということで分類器を自分で作ってみましょう。
feature_extractor_url = "https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/2" #@param {type:"string"}
例によってtensorflow hubから学習済みモデルをダウンロードしてきます。extractorは抽出機っぽいですね。
feature_extractor_layer = hub.KerasLayer(feature_extractor_url,
input_shape=(224,224,3))
モデルに突っ込むためのlayerを作成。
feature_batch = feature_extractor_layer(image_batch)
print(feature_batch.shape)
こいつは1280のベクトルをそれぞれの画像に対して返してくれるとのこと。
様々な要素から画像の分類を試みるんですね。
feature_extractor_layer.trainable = False
今回は抽出機の部分はチューニングしないので、学習されないように指定しておきます。
Attach a classification head
model = tf.keras.Sequential([
feature_extractor_layer,
layers.Dense(image_data.num_classes, activation='softmax')
])
model.summary()
さっき作った抽出機のインスタンスを分類器に突っ込んでいます。
作ったモデルのサマリーの表示もしていますね。
predictions = model(image_batch)
predictions.shape
tensorのshape(形状)を確認しています。
Train the model
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss='categorical_crossentropy',
metrics=['acc'])
モデルをコンパイルします。
optimizer(最適化アルゴリズム):色々種類があるみたいです。 参考
loss(損失関数):損失が出来るだけ小さいように調整するために学習に使用される関数。これも色々あるみたいです。参考
metrics(評価関数):モデルの性能を評価するために使う値だが、損失関数のように学習には使われないもの。これも同じくいろいろある模様。 参考
class CollectBatchStats(tf.keras.callbacks.Callback):
def __init__(self):
self.batch_losses = []
self.batch_acc = []
def on_train_batch_end(self, batch, logs=None):
self.batch_losses.append(logs['loss'])
self.batch_acc.append(logs['acc'])
self.model.reset_metrics()
損失関数、評価関数の値がどの様になっているかを観測するための関数定義です。
steps_per_epoch = np.ceil(image_data.samples/image_data.batch_size)
batch_stats_callback = CollectBatchStats()
history = model.fit_generator(image_data, epochs=2,
steps_per_epoch=steps_per_epoch,
callbacks = [batch_stats_callback])
fit_generatorで学習を開始します。
学習回数などなどを指定してますね。参考
学習には多少時間がかかります。
plt.figure()
plt.ylabel("Loss")
plt.xlabel("Training Steps")
plt.ylim([0,2])
plt.plot(batch_stats_callback.batch_losses)
損失関数の推移です。徐々に下がっていっていてよさげですね。
plt.figure()
plt.ylabel("Accuracy")
plt.xlabel("Training Steps")
plt.ylim([0,1])
plt.plot(batch_stats_callback.batch_acc)
評価関数の推移です。これは上がっていっているのでいい感じ。
Check the predictions
いよいよ分類してみましょう。
class_names = sorted(image_data.class_indices.items(), key=lambda pair:pair[1])
class_names = np.array([key.title() for key, value in class_names])
class_names
実際に存在するクラスを取り上げます。存在するのは5種類だけですね。
predicted_batch = model.predict(image_batch)
predicted_id = np.argmax(predicted_batch, axis=-1)
predicted_label_batch = class_names[predicted_id]
先程やったようにpredictして、最大値のものを取ってきてラベルにします。
label_id = np.argmax(label_batch, axis=-1)
正解のラベルを確保しておきます
plt.figure(figsize=(10,9))
plt.subplots_adjust(hspace=0.5)
for n in range(30):
plt.subplot(6,5,n+1)
plt.imshow(image_batch[n])
color = "green" if predicted_id[n] == label_id[n] else "red"
plt.title(predicted_label_batch[n].title(), color=color)
plt.axis('off')
_ = plt.suptitle("Model predictions (green: correct, red: incorrect)")
正解のときは緑、失敗のときは赤で出力されるようになっています。
だいたい正解してるんじゃないでしょうか。よさげ。
この後もチュートリアルはちょっとだけ続いて、学習したモデルのexportなどが書かれているんですが、今回使うのはここまでなので、これ以降は割愛しておきます。
自分だけの分類器造り
さて、チュートリアルで出てきたコードを利用して自分だけの分類器を作っていきましょう。今回はColab上で動くところまででゴールにしておきます。
画像収集
画像分類には画像の収集が欠かせません。
画像のダウンロード
google-images-downloadが便利です。(ダウンロードした画像のライセンスには注意しましょう)
pip経由でインストールできるので、macであれば以下のように使えると思います。
pip install google_images_download
googleimagesdownload --keywords "探したい画像"
画像の選別
ダウンロードしてきた画像をそのまま使おうとすると、ゴミが多くて使えません。
ので、画像を1枚ずつ見ながら変な画像を取り除いていきましょう。
この作業が一番大変でした..
最終的にはこんな感じでディレクトリごとにきれいになった特定の画像が集まっている状態を作りましょう。
パッケージしてアップロード
とりあえずColabからdownload出来るようにtarで圧縮したファイルをどこかにuploadしておきましょう。私はGoogleDriveに上げときました。
GoogleDriveの場合はtar.gzをuploadした上で、共有可能なリンクを取得し、そのID部分を以下リンクと差し替えてみましょう。
https://drive.google.com/uc?export=download&id=XXXXXXXXXXXXXXXXXXXXX
Colabのコードに反映
こちらに分類機作る部分だけを抜き出したColabファイルを用意したので、xxxxのところを差し替えてみてください。
上手く動きましたでしょうか。
おわりに
なんとなーく機械学習の上辺を体験できましたが、分類器のイメージだと自分で撮った写真が分類されてほしい感じありますよね。
手元に環境構築したりして、もうちょっとカジュアルに触れる何か〜を作ってみたい気持ちがあります。がんばるぞ。