Caffe : Notebook Example : 分類 : Caffe でインスタント認識 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
日時 : 04/03/2017
* 本ページは、jupyter サンプル: examples/00-classification.ipynb – Classification: Instant Recognition with Caffe を
実行・翻訳した上で適宜、補足説明したものです:
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
事前トレーニングされたモデルでインスタントな認識そして特徴とパラメータを層毎に可視化するための net インターフェイスのツアー。
このサンプルではバンドルされた CaffeNet モデルで画像を分類します (これは ImageNet のための Krizhevsky et al. のネットワーク・アーキテクチャを基にしています)。CPU と GPU モードを比較しそして特徴と出力を検査するためにモデルを掘り下げます。
1. セットアップ
- 最初に、Python, numpy, と matplotlib をセットアップします。
# set up Python environment: numpy for numerical routines, and matplotlib for plotting import numpy as np import matplotlib.pyplot as plt # display plots in this notebook %matplotlib inline # 表示デフォルトを設定 plt.rcParams['figure.figsize'] = (10, 10) # large images plt.rcParams['image.interpolation'] = 'nearest' # don't interpolate: show square pixels plt.rcParams['image.cmap'] = 'gray' # use grayscale output rather than a (potentially misleading) color heatmap
- caffe をロードします。
# caffe モジュールは Python パス上にある必要があります ; # ここで明示的に追加します。 import sys caffe_root = '../' # this file should be run from {caffe_root}/examples (otherwise change this line) sys.path.insert(0, caffe_root + 'python') import caffe # If you get "No module named _caffe", either you have not built pycaffe or you have the wrong path.
- 必要ならば、reference モデルをダウンロードします (“CaffeNet”、AlexNet の変形です)。
import os if os.path.isfile(caffe_root + 'models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel'): print 'CaffeNet found.' else: print 'Downloading pre-trained CaffeNet model...' !../scripts/download_model_binary.py ../models/bvlc_reference_caffenet
Downloading pre-trained CaffeNet model... ...100%, 232 MB, 1335 KB/s, 178 seconds passed
2. net をロードして入力前処理をセットアップする
- Caffe を CPU モードに設定してディスクから net をロードします。
caffe.set_mode_cpu() model_def = caffe_root + 'models/bvlc_reference_caffenet/deploy.prototxt' model_weights = caffe_root + 'models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel' net = caffe.Net(model_def, # モデルの構造を定義する model_weights, # 訓練された重みを含む caffe.TEST) # TEST モードを使用する (e.g. dropout を実行しない)
-
入力前処理をセットアップします (これを行なうために Caffe の caffe.io.Transformer を使用します、しかしこのステップは Caffe の他の部分から独立なので、どのようなカスタム前処理コードでも使用可能です)。
デフォルト CaffeNet は BGR フォーマットの画像を取るように構成されています。値は範囲 [0, 255] で始まることを仮定されてそしてそれらから ImageNet ピクセル値の平均 (mean) を減算します。更に、チャネル次元は最初の (outermost, 最も外側の) 次元として仮定されます。
matplotlib はチャネルを innermost 次元として RGB フォーマットで範囲 [0, 1] の値で画像をロードしますので、必要な変換をここでアレンジします。
# (Caffe と一緒に配布されている) ImageNet 画像平均 (mean) を減算のためにロードする。 mu = np.load(caffe_root + 'python/caffe/imagenet/ilsvrc_2012_mean.npy') mu = mu.mean(1).mean(1) # 平均 (BGR) ピクセル値を得るためにピクセルに渡り平均する print 'mean-subtracted values:', zip('BGR', mu) # 'data' と呼ばれる入力のための変換器を作成する transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape}) transformer.set_transpose('data', (2,0,1)) # 画像チャネルを outermost 次元に移す transformer.set_mean('data', mu) # 各チャネルにおいてデータセット-平均値を減算する transformer.set_raw_scale('data', 255) # [0, 1] から [0, 255] へリスケールする transformer.set_channel_swap('data', (2,1,0)) # RGB から BGR にチャネルを swap する
mean-subtracted values: [('B', 104.0069879317889), ('G', 116.66876761696767), ('R', 122.6789143406786)]
3. CPU 分類
- さて分類を実行するための準備ができました。一つの画像だけを分類しますが、バッチをデモするためにバッチサイズを 50 に設定します。
# 入力サイズの設定 (デフォルトで良いのであればこれはスキップ可能です; # e.g. 異なるバッチサイズのために、後でそれを変更もできます) net.blobs['data'].reshape(50, # バッチサイズ 3, # 3-チャネル (BGR) 画像 227, 227) # 画像サイズは 227x227
- (Caffe にある) 画像をロードしてセットアップした前処理を実行します。
image = caffe.io.load_image(caffe_root + 'examples/images/cat.jpg') transformed_image = transformer.preprocess('data', image) plt.imshow(image)
- 可愛いですね! それを分類してみましょう!
# 画像データを net に割り当てられたメモリにコピーします。 net.blobs['data'].data[...] = transformed_image ### 分類を実行します。 output = net.forward() output_prob = output['prob'][0] # バッチの最初の画像に対する出力確率ベクトル print 'predicted class is:', output_prob.argmax()
predicted class is: 281
- net は確率のベクトルを与えてくれます ; 最も起こりうるクラスは 281 番目のものです。しかしこれは正しいでしょうか? ImageNet ラベルをチェックしてみましょう…
# load ImageNet labels labels_file = caffe_root + 'data/ilsvrc12/synset_words.txt' if not os.path.exists(labels_file): !../data/ilsvrc12/get_ilsvrc_aux.sh labels = np.loadtxt(labels_file, str, delimiter='\t') print 'output label:', labels[output_prob.argmax()]
Downloading... --2017-04-03 13:40:53-- http://dl.caffe.berkeleyvision.org/caffe_ilsvrc12.tar.gz dl.caffe.berkeleyvision.org (dl.caffe.berkeleyvision.org) をDNSに問いあわせています... 169.229.222.251 dl.caffe.berkeleyvision.org (dl.caffe.berkeleyvision.org)|169.229.222.251|:80 に接続しています... 接続しました。 HTTP による接続要求を送信しました、応答を待っています... 200 OK 長さ: 17858008 (17M) [application/octet-stream] `caffe_ilsvrc12.tar.gz' に保存中 100%[======================================>] 17,858,008 1.83MB/s 時間 11s 2017-04-03 13:41:05 (1.61 MB/s) - `caffe_ilsvrc12.tar.gz' へ保存完了 [17858008/17858008] Unzipping... Done. output label: n02123045 tabby, tabby cat
- “Tabby cat (トラ猫)” は正しいです!しかし他の top も見てみましょう (しかし信頼性はより低い予測です)。
# sort top five predictions from softmax output top_inds = output_prob.argsort()[::-1][:5] # reverse sort and take five largest items print 'probabilities and labels:' zip(output_prob[top_inds], labels[top_inds])
probabilities and labels: [(0.3124465, 'n02123045 tabby, tabby cat'), (0.23797031, 'n02123159 tiger cat'), (0.12387876, 'n02124075 Egyptian cat'), (0.10075201, 'n02119022 red fox, Vulpes vulpes'), (0.070957221, 'n02127052 lynx, catamount')]
- 信頼性が低い予測も理にかなっていることが見て取れます。
4. GPU モードにスイッチする
- どのくらい分類がかかるか見てみましょう、そしてそれを GPU モードと比較しましょう。
%timeit net.forward()
1 loop, best of 3: 4.25 s per loop
- それはしばらくかかります、50 画像のバッチのためでさえ。GPU モードにスイッチしましょう。
caffe.set_device(0) # if we have multiple GPUs, pick the first one caffe.set_mode_gpu() net.forward() # run once before timing to set up memory %timeit net.forward()
10 loops, best of 3: 70.2 ms per loop
- 遥に速いでしょう!
5. 中間出力を検査する
- net は単なるブラックボックスではありません ; パラメータと中間的な活性の幾つかを見てみましょう。
最初に活性とパラメータ shape の点からどのように net の構造を読み出すかを見ます。
- 各層に対して、活性 shape を見てみましょう、これは典型的には (batch_size, channel_dim, height, width) = (バッチサイズ, チャネル次元, 高さ, 幅) の形を持ちます。
活性は OrderedDict, net.blobs として晒されます。
# 各層に対して、出力 shape を表示します for layer_name, blob in net.blobs.iteritems(): print layer_name + '\t' + str(blob.data.shape)
data (50, 3, 227, 227) conv1 (50, 96, 55, 55) pool1 (50, 96, 27, 27) norm1 (50, 96, 27, 27) conv2 (50, 256, 27, 27) pool2 (50, 256, 13, 13) norm2 (50, 256, 13, 13) conv3 (50, 384, 13, 13) conv4 (50, 384, 13, 13) conv5 (50, 256, 13, 13) pool5 (50, 256, 6, 6) fc6 (50, 4096) fc7 (50, 4096) fc8 (50, 1000) prob (50, 1000)
- さてパラメータ shape を見ます。パラメータはもう一つの OrderedDict, net.params として晒されます。結果の値に重みのために [0] あるいはバイアスのためには [1] のインデックスをつける必要があります。
param shape は典型的には (output_channels, input_channels, filter_height, filter_width) = (出力チャネル, 入力チャネル, フィルタ高さ, フィルタ幅) (重みのため)の形を取りそして 1-次元 shape (出力チャネル) = (output_channels,) (バイアスのため) を取ります。
for layer_name, param in net.params.iteritems(): print layer_name + '\t' + str(param[0].data.shape), str(param[1].data.shape)
conv1 (96, 3, 11, 11) (96,) conv2 (256, 48, 5, 5) (256,) conv3 (384, 256, 3, 3) (384,) conv4 (384, 192, 3, 3) (384,) conv5 (256, 192, 3, 3) (256,) fc6 (4096, 9216) (4096,) fc7 (4096, 4096) (4096,) fc8 (1000, 4096) (1000,)
- 4-次元データをここでは扱うので、矩形 heatmaps のセットを可視化するためのヘルパー関数を定義します。
def vis_square(data): """Take an array of shape (n, height, width) or (n, height, width, 3) and visualize each (height, width) thing in a grid of size approx. sqrt(n) by sqrt(n)""" # normalize data for display data = (data - data.min()) / (data.max() - data.min()) # force the number of filters to be square n = int(np.ceil(np.sqrt(data.shape[0]))) padding = (((0, n ** 2 - data.shape[0]), (0, 1), (0, 1)) # add some space between filters + ((0, 0),) * (data.ndim - 3)) # don't pad the last dimension (if there is one) data = np.pad(data, padding, mode='constant', constant_values=1) # pad with ones (white) # tile the filters into an image data = data.reshape((n, n) + data.shape[1:]).transpose((0, 2, 1, 3) + tuple(range(4, data.ndim + 1))) data = data.reshape((n * data.shape[1], n * data.shape[3]) + data.shape[4:]) plt.imshow(data); plt.axis('off')
- まず最初の層フィルタを見ます、conv1 です。
# the parameters are a list of [weights, biases] filters = net.params['conv1'][0].data vis_square(filters.transpose(0, 2, 3, 1))
- 最初の層出力、conv1 (上のフィルタの rectified responses, 最初の 36 のみ)
feat = net.blobs['conv1'].data[0, :36] vis_square(feat)
- pooling 後の5番目の層、pool5
feat = net.blobs['pool5'].data[0] vis_square(feat)
- 最初の完全結合層、fc6 (rectified)
出力値と正値のヒストグラムを示します。
feat = net.blobs['fc6'].data[0] plt.subplot(2, 1, 1) plt.plot(feat.flat) plt.subplot(2, 1, 2) _ = plt.hist(feat.flat[feat.flat > 0], bins=100)
- 最終的な確率出力、prob
feat = net.blobs['prob'].data[0] plt.figure(figsize=(15, 3)) plt.plot(feat.flat)
[<matplotlib.lines.Line2D at 0x7fa1f2087bd0>]
Note the cluster of strong predictions; the labels are sorted semantically. 上で見られるように、top peaks は top 予測ラベルに相当します。
6. 貴方の画像にトライ
さて web から画像を捕まえて上のステップを使用してそれを分類します。
- my_image_url を任意の JPEG 画像 URL に設定してみます。
* 訳注: 定番の画像を使用して試してみました。
# download an image my_image_url = "..." # paste your URL here # for example: # my_image_url = "https://upload.wikimedia.org/wikipedia/commons/b/be/Orang_Utan%2C_Semenggok_Forest_Reserve%2C_Sarawak%2C_Borneo%2C_Malaysia.JPG" !wget -O image.jpg $my_image_url # transform it and copy it into the net image = caffe.io.load_image('image.jpg') net.blobs['data'].data[...] = transformer.preprocess('data', image) # perform classification net.forward() # obtain the output probabilities output_prob = net.blobs['prob'].data[0] # sort top five predictions from softmax output top_inds = output_prob.argsort()[::-1][:5] plt.imshow(image) print 'probabilities and labels:' zip(output_prob[top_inds], labels[top_inds])
probabilities and labels: [(0.73805922, 'n03769881 minibus'), (0.12251383, 'n04065272 recreational vehicle, RV, R.V.'), (0.025057102, 'n04487081 trolleybus, trolley coach, trackless trolley'), (0.016914189, 'n03770679 minivan'), (0.016330782, 'n03977966 police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria')]
以上