Chainer imagenet example
機械学習で多クラス画像分類を行いたいと思った時に、Chainerのサンプルコード https://github.com/pfnet/chainer/tree/master/examples/imagenet は非常によくできていて、ほぼ手をつけずに応用が効いて便利です。
こちらの投稿「PFN発のディープラーニングフレームワークchainerで画像分類をするよ(chainerでニューラルネット1) - 人工言語処理入門」に従ってデータを用意・処理すれば、大体思うように動いてくれます(データセットが適切である必要はもちろんありますが)。
このサンプルでは、下処理として学習画像の平均値を用いたデータの正規化を行っています。また、データ拡張として、ランダムな領域のクリッピングと左右反転を行っています。
TensowFlowのCIFAR-10サンプルコード
一方、TensowFlowのCIFAR-10サンプルコード https://github.com/tensorflow/tensorflow/tree/master/tensorflow/models/image/cifar10 は、入力データがCIFAR-10にほぼに特化しているので、直接応用させるのは難しいところがあります。
とはいえ、構造はシンプルでチュートリアルとしては非常によくできていると思います。コードを読み進めていくと、Chainerのサンプルよりもより幅広いデータ拡張をしてることがわかります。平均値を用いた正規化は行わず、ランダムクリッピングと反転に加え、以下の処理を行っています。
- 輝度(brightness)の変更
- コントラストの変更
- whitening(白色化)
これらをChainerのサンプルに適用させることを考えてみました。
データ拡張の実装
実装はnumpyだけでやってみます。
輝度
輝度の変更は非常に単純です。ある範囲の乱数を各画素に単純に加算するだけです。
def random_brightness(image, max_delta=63, seed=None):
delta = np.random.uniform(-max_delta, max_delta)
newimg = image + delta
return newimg
コントラスト
コントラストの変更は、各ピクセルのR面G面B面平均値mを求めて、各面の値をそれぞれ(x - m) * factor + mとします。
def random_contrast(image, lower, upper, seed=None):
f = np.random.uniform(lower, upper)
mean = (image[0] + image[1] + image[2]).astype(np.float32) / 3
ximg = xp.zeros(image.shape, xp.float32)
for i in range(0, 3):
ximg[i] = (image[i] - mean) * f + mean
return ximg
Chainerでは画像をrgb * width * heightという形式で扱うので、画素ごとのRGB平均値を求めるのは簡単です。
TensorFlowの場合、ここの処理はC++で記述されています。画像をwidth * height * rgbの形式で扱うからではないかと予想しています。その分、直接pillowやOpenCVなどで読み込んだ結果を扱えるという利点があるのですけど。
whitening
whiteningはTensorFlowのper_image_whiteningの解説どおりに実装すればよいです。
def image_whitening(img):
img = img.astype(np.float32)
d, w, h = img.shape
num_pixels = d * w * h
mean = img.mean()
variance = np.mean(np.square(img)) - np.square(mean)
stddev = np.sqrt(variance)
min_stddev = 1.0 / np.sqrt(num_pixels)
scale = stddev if stddev > min_stddev else min_stddev
img -= mean
img /= scale
return img
whiteningに関しては、書籍「深層学習 (機械学習プロフェッショナルシリーズ)」(ISBN-13: 978-4061529021)の5-5に詳しい解説があります。
実コード
#
import numpy as np
import random
def random_brightness(image, max_delta=63, seed=None):
delta = np.random.uniform(-max_delta, max_delta)
newimg = image + delta
return newimg
def random_contrast(image, lower, upper, seed=None):
f = np.random.uniform(-lower, upper)
mean = (image[0] + image[1] + image[2]).astype(np.float32) / 3
ximg = xp.zeros(image.shape, xp.float32)
for i in range(0, 3):
ximg[i] = (image[i] - mean) * f + mean
return ximg
def image_whitening(img):
img = img.astype(np.float32)
d, w, h = img.shape
num_pixels = d * w * h
mean = img.mean()
variance = np.mean(np.square(img)) - np.square(mean)
stddev = np.sqrt(variance)
min_stddev = 1.0 / np.sqrt(num_pixels)
scale = stddev if stddev > min_stddev else min_stddev
img -= mean
img /= scale
return img
insize = 227
cropwidth = 256 - insize
def read_dist_image(path, center=False, flip=False):
image = np.asarray(Image.open(path)).transpose(2, 0, 1)
if center:
top = left = cropwidth / 2
else:
top = random.randint(0, cropwidth - 1)
left = random.randint(0, cropwidth - 1)
bottom = insize+top
right = insize+left
# clipping
image = image[:, top:bottom, left:right].astype(np.float32)
# left-right flipping
if flip and random.randint(0, 1) == 0:
image = image[:, :, ::-1]
# random brightness
if random.randint(0, 1) == 0:
image = random_brightness(image)
# random contrast
if random.randint(0, 1) == 0:
image = random_contrast(image, lower=0.2, upper=1.8)
# whitening
image = image_whitening(image)
image.flags.writeable = True
return image
オリジナルのtrain_imagenet.pyを修正して、read_image内部で新たに作成したコードを呼びだすよう修正すれば完成です。
--- train_imagenet.py 2016-02-18 14:51:48.595572992 +0900
+++ train_imagenet.py.new 2016-02-18 14:53:09.827572982 +0900
@@ -30,6 +30,7 @@
from chainer import optimizers
from chainer import serializers
+import distorion
parser = argparse.ArgumentParser(
description='Learning convnet from ILSVRC2012 dataset')
@@ -126,6 +127,7 @@
def read_image(path, center=False, flip=False):
+ return distorion.read_dist_image(path)
# Data loading routine
image = np.asarray(Image.open(path)).transpose(2, 0, 1)
if center:
注意
numpyだけで実装したので、結構メモリを食います。メモリが少ない環境では、trian_imagenet.pyの引数に-j 5などを指定して、読み込み処理をするスレッド数を少なくしてみてください。
認識
自分は、Network-In-Network構造を使って処理をさせてみました。サンプルには認識用のメソッドが定義されていないので、以下のように追加しています。
--- nin.py 2016-02-18 15:01:43.211572921 +0900
+++ nin.py.new 2016-02-16 13:35:06.035594161 +0900
@@ -35,3 +35,12 @@
self.loss = F.softmax_cross_entropy(h, t)
self.accuracy = F.accuracy(h, t)
return self.loss
+ def evaluate(self, x):
+ h = F.max_pooling_2d(F.relu(self.mlpconv1(x)), 3, stride=2)
+ h = F.max_pooling_2d(F.relu(self.mlpconv2(h)), 3, stride=2)
+ h = F.max_pooling_2d(F.relu(self.mlpconv3(h)), 3, stride=2)
+ h = self.mlpconv4(F.dropout(h, train=self.train))
+ h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000))
+
+ return F.softmax(h)
train_imagenet.pyをベースに認識用のコマンドを作成します。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import os
import random
import sys
import threading
import time
from PIL import Image
import six
import six.moves.cPickle as pickle
import chainer
from chainer import cuda
from chainer import optimizers
from chainer import serializers
parser = argparse.ArgumentParser(
description='Chainter NIN model identifyer')
parser.add_argument('imgfile', help='Path to identify image-file')
parser.add_argument('--gpu', '-g', default=0, type=int,
help='GPU ID (negative value indicates CPU)')
parser.add_argument('--model', '-m', default='model',
help='Path to load serialized model')
args = parser.parse_args()
if args.gpu >= 0:
cuda.check_cuda_available()
import numpy as np
xp = cuda.cupy if args.gpu >= 0 else np
size = 256
def load_single_image(file):
image = Image.open(file)
image = image.resize((size,size))
image = image.convert("RGB")
image = np.asarray(image).transpose(2, 0, 1)
img = image.astype(np.float32)
d, w, h = img.shape
num_pixels = d * w * h
mean = img.mean()
variance = np.mean(np.square(img)) - np.square(mean)
stddev = np.sqrt(variance)
min_stddev = 1.0 / np.sqrt(num_pixels)
scale = stddev if stddev > min_stddev else min_stddev
img -= mean
img /= scale
return img
import nin
model = nin.NIN()
if args.gpu >= 0:
cuda.get_device(args.gpu).use()
model.to_gpu()
#
serializers.load_hdf5(args.model, model)
model.train = False
# image handling
img = load_single_image(args.imgfile)
x = chainer.Variable(xp.asarray([img]),volatile='on')
y = model.evaluate(x).data
print(y[0])
出力は1000クラスそれぞれの確率になります。適時加工して使ってください。
おまけ: TensorFlowのCIFAR-10サンプルで任意の画像ファイルを読み込ませるには
TensorFlowのCIFAR-10サンプルも、FixedLengthRecordReaderを使う代わりにTextLineReader, decode_csv, read_file, decode_jpegを組み合わせれば任意のjpeg画像データを使って学習させることができます。
そのための記事「TensorFlowのReaderクラスを使ってみる」を以前書いたので参考にしてください。
TensorFlow 0.7.0
この記事を書いていて気づいたのですが、昨日(2016/2/17)の段階でTensorFlow 0.7.0が出ていたようです。