エイバースの中の人

アプリとWEBサービスを作っているABARSのBLOGです。

KerasでHDF5Matrixを使用して学習を高速化

小さな画像が大量にある場合、ImageDataGeneratorを使用するとディスクのランダムアクセスネックでGPU使用率が伸びず、学習速度が低下します。学習画像を事前にHDF5に書き込んでおくことで、ランダムアクセスを抑制し、高速化することができます。

まず、flow_from_directoryから取得できる学習用データからh5pyを使用してHDF5を作成します。以下の例ではtrain.h5を作成します。同様にvalidation.h5も作成します。

train_generator = train_datagen.flow_from_directory(
   INPUT_PATH+'/annotations/'+ANNOTATIONS+'/train',
   target_size=(IMAGE_SIZE, IMAGE_SIZE),
   batch_size=BATCH_SIZE,
   class_mode='categorical',
   shuffle=True
)

training_data_n = len(train_generator.filenames)
training_class_n=len(train_generator.class_indices)

import h5py
f = h5py.File("train.h5", 'w')
train_x = f.create_dataset('training_x', (training_data_n,IMAGE_SIZE,IMAGE_SIZE,3), dtype='f')
train_y = f.create_dataset('training_y', (training_data_n,training_class_n), dtype='f')

cnt=0
for x_batch, y_batch in train_generator:
  for i in range(BATCH_SIZE):
    train_x[cnt] = x_batch[i]
    train_y[cnt] = y_batch[i]
    cnt = cnt+1
    if cnt>=training_data_n:
        break
  if cnt>=training_data_n:
    break

f.close()

学習時はHDF5Matrixを使用します。


  from keras.utils.io_utils import HDF5Matrix
  x_train = HDF5Matrix("train.h5", 'training_x')
  y_train = HDF5Matrix("train.h5", 'training_y')
  x_validation = HDF5Matrix("validation.h5", 'validation_x')
  y_validation = HDF5Matrix("validation.h5", 'validation_y')
  fit = model.fit(
    epochs=EPOCS,
    x=x_train, 
    y=y_train,
    validation_data=(x_validation,y_validation),
    batch_size=BATCH_SIZE,
    shuffle='batch'
  )

VGGFace2を使用した性別推定モデル(3146003画像)では160GBのHDF5を生成し、学習時間を1EPOCで3時間から30分に削減でき、6倍高速化できました。評価環境はGeforceRTX2080、32GBメモリ、1TB SSDです。

KerasでGPUのメモリ使用量を抑制する

kerasではデフォルトでGPUメモリを100%確保します。ディスクネックでGPU使用率が低く、複数の学習を同時に走らせたい場合は、モデル作成前に以下のようにすることで、GPUのメモリ使用率を抑制することができます。

import tensorflow as tf
import keras.backend as backend

config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.5
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)
backend.set_session(sess)

keras2caffeでKeyErrorが起きる

Caffe 1.0.274でkeras2caffeを使った際、KeyErrorが発生します。

Traceback (most recent call last):
  File "convert_to_caffemodel.py", line 76, in 
    keras2caffe.convert(keras_model, PROTOTXT, WEIGHTS)
  File "keras2caffe\convert.py", line 76, in convert
    if layer_type=='InputLayer' or not hasattr(caffe_net, 'data'):
  File "Anaconda3\envs\py35\lib\site-packages\caffe\net_spec.py", line 180, in __getattr__
    return self.tops[name]
KeyError: 'data'

これは、hasattrがgetattrで実装されていますが、caffeがKeyErrorをAttributeErrorに置き換えていないために発生します。

How to make a class which has __getattr__ properly pickable?

class NetSpec(object):
    """A NetSpec contains a set of Tops (assigned directly as attributes).
    Calling NetSpec.to_proto generates a NetParameter containing all of the
    layers needed to produce all of the assigned Tops, using the assigned
    names."""

    def __init__(self):
        super(NetSpec, self).__setattr__('tops', OrderedDict())

    def __setattr__(self, name, value):
        self.tops[name] = value

    def __getattr__(self, name):
        return self.tops[name]

そこで、NetSpecでKeyErrorをAttributeErrorに変換します。

class NetSpec(object):
    """A NetSpec contains a set of Tops (assigned directly as attributes).
    Calling NetSpec.to_proto generates a NetParameter containing all of the
    layers needed to produce all of the assigned Tops, using the assigned
    names."""

    def __init__(self):
        super(NetSpec, self).__setattr__('tops', OrderedDict())

    def __setattr__(self, name, value):
        self.tops[name] = value

    def __getattr__(self, name):
        try:
            return self.tops[name]
        except KeyError:
            raise AttributeError(name)

これで正常にcaffemodelに変換することができます。

MacOS Mojaveで英語キーボードとして認識される

MacにChromeRemoteDesktopでログインしたあと、JISキーボードが英語キーボードとして認識される問題が起きました。この問題は再起動しても解決しませんでした。

Mojave以前の場合は「キーボードの種類を変更ボタン」がキーボード設定にあったのですが、Mojaveではなくなっているため、以下からキーボード設定アシスタントを実行する必要があります。

/system/Library/CoreService

キーボード設定アシスタントの指示に従うと、JISキーボードとして認識されました。

RTX2080でTensorflow-gpu

機械学習用にRTX2080を搭載したTridentXを購入したので、KerasとTensorflowをインストールしました。



RTX2080の性能を発揮するにはCUDA10が必要ですが、公式のtensorflow-gpuはPython3.5 + CUDA9でビルドされています。そのため、CUDA10を使用してビルドされたtensorflowを別途、入手する必要があります。

ビルド済みバイナリは以下に公開されています。

https://github.com/fo40225/tensorflow-windows-wheel

Anaconda(Python3.7)で以下のようにインストールします。

pip install https://github.com/fo40225/tensorflow-windows-wheel/raw/master/1.12.0/py37/GPU/cuda100cudnn73sse2/tensorflow_gpu-1.12.0-cp37-cp37m-win_amd64.whl


CUDAのランタイムとcuDNNは以下からインストールできます。

CUDA Toolkit 10.0
cuDNN for CUDA10

また、最新のKeras 2.2.4で185万枚の画像からトレーニングしようとした場合、1050tiで1EPOCHで45分だったのが、2080の1EPOCHで50時間必要になってしまい、CUDA10の問題かと思ったのですが、pip install Keras==2.1.4で2.1.4にダウングレードしたら2080で1EPOCHが15分になりました。データがメモリに乗る2週目からは7分程度で1EPOCHが回ります。トータルで6倍程度の高速化のイメージです。

VGGFace2に年齢ラベルを付加

商用利用可能(The VGGFace2 dataset is available to download for commercial/research purposes under a Creative Commons Attribution-ShareAlike 4.0 International License. )な顔画像の大規模データセットとして、VGGFace2がありますが、メタデータとして年齢が含まれていないという問題があります。

そこで、現在、もっとも高精度と思われるIMDBの学習済みモデルを使用して、年齢ラベルを付与しました。付与されたラベルの分布は以下となります。若年層が含まれていないため、APPA-REAL (real and apparent age)あたりと合わせて使用すると良さそうです。

estimated_distribution


メタデータ:identity_meta_with_estimated_age.csv
リポジトリ:VGGFace2AgeLabel

GlobalAveragePoolingを使用してVGG16のモデルサイズを小さくする

物体識別を行う際、VGG16による転移学習を行いますが、生成されるモデルのサイズが大きすぎるという問題があります。VGG16のモデルサイズが大きくなる原因は、全結合層です。

以下はVGG16のSummaryです。最終段で、25088 * 4096の内積が発生しており、重みで392MB近く消費します。モデルサイズは528MBになります。

N_CATEGORIES = 64
IMAGE_SIZE = 224
input_tensor = Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3))
original_model = VGG16(weights='imagenet', include_top=True,input_tensor=input_tensor)
original_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
_________________________________________________________________
flatten (Flatten)            (None, 25088)             0         
_________________________________________________________________
fc1 (Dense)                  (None, 4096)              102764544 
_________________________________________________________________
fc2 (Dense)                  (None, 4096)              16781312  
_________________________________________________________________
predictions (Dense)          (None, 1000)              4097000   
=================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
_________________________________________________________________

VGG16を使用して、転移学習を行う場合、全結合層を置き換えるのが一般的です。64カテゴリで学習を行った場合、モデルサイズは154MBになります。

  N_CATEGORIES = 64
  IMAGE_SIZE = 224
  input_tensor = Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3))
  base_model = VGG16(weights='imagenet', include_top=False,input_tensor=input_tensor)
  x = base_model.output
  x = Flatten()(x)
  x = Dense(1024, activation='relu')(x)
  predictions = Dense(N_CATEGORIES, activation='softmax')(x)
  model = Model(inputs=base_model.input, outputs=predictions)
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 25088)             0         
_________________________________________________________________
dense_1 (Dense)              (None, 1024)              25691136  
_________________________________________________________________
dense_2 (Dense)              (None, 64)                65600     
=================================================================
Total params: 40,471,424
Trainable params: 40,471,424
Non-trainable params: 0
_________________________________________________________________

GlobalAveragePoolingは2014年に提案されました。GlobalAveragePoolingを使用することで、全結合層を省略して、モデルサイズを小さくすることができます。GlobalAveragePoolingは特徴マップをチャンネルごとに平均化するレイヤーです。1x1の畳み込みで認識カテゴリ数分の特徴マップに集約したあと、平均化しsoftmaxをかけることで、全結合層を代用することができます。モデルサイズは56MBになります。

class_activation_mapping
(出典:Global Average Pooling Layers for Object Localization

  N_CATEGORIES = 64
  IMAGE_SIZE = 224
  input_tensor = Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3))
  base_model = VGG16(weights='imagenet', include_top=False,input_tensor=input_tensor)
  x = base_model.output
  x = Convolution2D(N_CATEGORIES, (1, 1), padding='valid', name='conv10')(x)
  x = Activation('relu', name='relu_conv10')(x)
  x = GlobalAveragePooling2D()(x)
  predictions = Activation('softmax', name='loss')(x)
  model = Model(inputs=base_model.input, outputs=predictions)
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
_________________________________________________________________
conv10 (Conv2D)              (None, 7, 7, 64)          32832     
_________________________________________________________________
relu_conv10 (Activation)     (None, 7, 7, 64)          0         
_________________________________________________________________
global_average_pooling2d_1 ( (None, 64)                0         
_________________________________________________________________
loss (Activation)            (None, 64)                0         
=================================================================
Total params: 14,747,520
Trainable params: 14,747,520
Non-trainable params: 0
_________________________________________________________________

GlobalAveragePoolingは特徴マップを平均化する機能しかないため、GlobalAveragePoolingへの入力は、自動的にヒートマップが学習されることになります。そのため、画像のどの部分を見てそう判断したかを特徴マップを見て把握できるという特徴があります。この特徴から、内積に比べて過学習も防止すると言われています。

dog_localization
(出典:Global Average Pooling Layers for Object Localization

さらにモデルサイズを小さくしたい場合は、SqueezeNetを使用することになります。畳み込みをチャンネル内とチャンネル間に分解して行うことで、畳込み部分の重みを削減します。モデルサイズは216KBになります。

IMAGE_SIZE=227
import sys
sys.path.append('./keras-squeezenet-master')
from keras_squeezenet import SqueezeNet
input_tensor = Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3))
base_model = SqueezeNet(weights="imagenet", include_top=False, input_tensor=input_tensor)
x = base_model.output
x = Dropout(0.5, name='drop9')(x)
x = Convolution2D(N_CATEGORIES, (1, 1), padding='valid', name='conv10')(x)
x = Activation('relu', name='relu_conv10')(x)
x = GlobalAveragePooling2D()(x)
predictions = Activation('softmax', name='loss')(x)
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_2 (InputLayer)            (None, 227, 227, 3)  0                                            
__________________________________________________________________________________________________
conv1 (Conv2D)                  (None, 113, 113, 64) 1792        input_2[0][0]                    
__________________________________________________________________________________________________
relu_conv1 (Activation)         (None, 113, 113, 64) 0           conv1[0][0]                      
__________________________________________________________________________________________________
pool1 (MaxPooling2D)            (None, 56, 56, 64)   0           relu_conv1[0][0]                 
__________________________________________________________________________________________________
fire2/squeeze1x1 (Conv2D)       (None, 56, 56, 16)   1040        pool1[0][0]                      
__________________________________________________________________________________________________
fire2/relu_squeeze1x1 (Activati (None, 56, 56, 16)   0           fire2/squeeze1x1[0][0]           
__________________________________________________________________________________________________
fire2/expand1x1 (Conv2D)        (None, 56, 56, 64)   1088        fire2/relu_squeeze1x1[0][0]      
__________________________________________________________________________________________________
fire2/expand3x3 (Conv2D)        (None, 56, 56, 64)   9280        fire2/relu_squeeze1x1[0][0]      
__________________________________________________________________________________________________
fire2/relu_expand1x1 (Activatio (None, 56, 56, 64)   0           fire2/expand1x1[0][0]            
__________________________________________________________________________________________________
fire2/relu_expand3x3 (Activatio (None, 56, 56, 64)   0           fire2/expand3x3[0][0]            
__________________________________________________________________________________________________
fire2/concat (Concatenate)      (None, 56, 56, 128)  0           fire2/relu_expand1x1[0][0]       
                                                                 fire2/relu_expand3x3[0][0]       
...
__________________________________________________________________________________________________
fire9/squeeze1x1 (Conv2D)       (None, 13, 13, 64)   32832       fire8/concat[0][0]               
__________________________________________________________________________________________________
fire9/relu_squeeze1x1 (Activati (None, 13, 13, 64)   0           fire9/squeeze1x1[0][0]           
__________________________________________________________________________________________________
fire9/expand1x1 (Conv2D)        (None, 13, 13, 256)  16640       fire9/relu_squeeze1x1[0][0]      
__________________________________________________________________________________________________
fire9/expand3x3 (Conv2D)        (None, 13, 13, 256)  147712      fire9/relu_squeeze1x1[0][0]      
__________________________________________________________________________________________________
fire9/relu_expand1x1 (Activatio (None, 13, 13, 256)  0           fire9/expand1x1[0][0]            
__________________________________________________________________________________________________
fire9/relu_expand3x3 (Activatio (None, 13, 13, 256)  0           fire9/expand3x3[0][0]            
__________________________________________________________________________________________________
fire9/concat (Concatenate)      (None, 13, 13, 512)  0           fire9/relu_expand1x1[0][0]       
                                                                 fire9/relu_expand3x3[0][0]       
__________________________________________________________________________________________________
drop9 (Dropout)                 (None, 13, 13, 512)  0           fire9/concat[0][0]               
__________________________________________________________________________________________________
conv10 (Conv2D)                 (None, 13, 13, 64)   32832       drop9[0][0]                      
__________________________________________________________________________________________________
relu_conv10 (Activation)        (None, 13, 13, 64)   0           conv10[0][0]                     
__________________________________________________________________________________________________
global_average_pooling2d_2 (Glo (None, 64)           0           relu_conv10[0][0]                
__________________________________________________________________________________________________
loss (Activation)               (None, 64)           0           global_average_pooling2d_2[0][0] 
==================================================================================================
Total params: 755,328
Trainable params: 755,328
Non-trainable params: 0
__________________________________________________________________________________________________

AppEngineからCloudStorageを使う

AppEngineのPython StandardEnvironmentからCloudStorageを使用します。DataStoreからCloudStorageに移行することで、保存データのバイト単価を1/10にすることができます。

CloudStorageを使用するには、appengine-gcs-clientをダウンロードして、アプリケーションのlibフォルダにcloudstorageフォルダをコピーします。

CloudStorageはバケットというフォルダのようなものに書き込みを行います。書き込み先のバケットをget_default_gcs_bucket_nameで取得することで、開発サーバでもCloudStorageは使用可能です。本番環境ではCloudStorageにあるアプリ名と同じ名称のデフォルトバケットに書き込まれます。

from google.appengine.api import app_identity

if 'lib' not in sys.path:
		sys.path[0:0] = ['lib']

import cloudstorage as gcs 

class ChunkManager(db.Model):
	def gcs_get_bucket_name(self):
		bucket_name = os.environ.get('BUCKET_NAME',	
					app_identity.get_default_gcs_bucket_name())
		return "/"+bucket_name+"/"

	def gcs_upload(self, filename, data):
		write_retry_params = gcs.RetryParams(backoff_factor=1.1)
		gcs_file = gcs.open(self.gcs_get_bucket_name()+filename,
						'w',
						content_type='application/octet-stream',
						retry_params=write_retry_params)
		gcs_file.write(data)
		gcs_file.close()

	def gcs_contents(self, filename):
		gcs_file = gcs.open(self.gcs_get_bucket_name()+filename)
		contents = gcs_file.read()
		gcs_file.close()
		return contents

	def gcs_download(self, filename, out):
		contents = self.gcs_contents(filename)
		out.write(contents)

	def gcs_delete(self, filename):
		try:
			gcs.delete(self.gcs_get_bucket_name()+filename)
		except gcs.NotFoundError:
			pass

参考:Google App EngineからGoogle Cloud Storageを利用する

AppEngineのDataStoreに新しいプロパティを追加する

既存のDataStoreのエンティティに新しいプロパティを追加した場合、保存済みのデータにはNoneが代入されます。Noneが代入されたプロパティはIndexに格納されていないため、Queryで参照することができません。

デザインパターンとしては、プロパティの追加に備えて、スキーマにversionというIntegerPropertyを追加しておきます。新しいプロパティを追加する際、versionが一定以下の場合をクエリして、新しいプロパティを初期化してputします。

しかし、そもそもversionを書き込んでいないスキーマの場合、まずはversionをスキーマに追加する必要があります。そのためには、全てのDataStoreのエンティティをループで回してversionを書き込んでputする必要があります。AppEngineのQueryは1000件以上取得できないため、このような場合はDataStoreのCursorを使用します。また、リクエストの60秒制約を解決するためにdeferred queueを使います。

以下、Python2.7 + AppEngine standard environment + dbの例です。

app.yamlにdeferredを追加します。

- url: /_ah/queue/deferred
  script: google.appengine.ext.deferred.deferred.application
  login: admin

builtins:
- deferred: on

cursorで回しながらversionを追加して上書きします。

from google.appengine.ext import deferred

def update_schema_loop(xbrl,num_fetched,num_updated,batch_size,tag):
	i=0

	to_put = []
	for one in xbrl:
		one=db.get(one.key())	#強い整合性を保証
		if not one.version:	#Noneチェック
			one.version=1	#新しいプロパティ
			to_put.append(one)
		i=i+1
		if i>=batch_size:
			break

	num_fetched += i

	if to_put:
		db.put(to_put)
		num_updated += len(to_put)

	return xbrl,num_fetched,num_updated,i

def update_schema_pdf(cursor=None, num_fetched=0, num_updated=0, batch_size=100):
	data_list = PdfDocument.all()
	data_list.with_cursor(start_cursor=cursor)
	data_list,num_fetched,num_updated,detected_cnt=update_schema_loop(data_list,num_fetched,num_updated,batch_size,"pdf")
	next_cursor = data_list.cursor()
	more=(detected_cnt!=0)
	if more:
		deferred.defer(
			update_schema_pdf, cursor=next_cursor, num_fetched=num_fetched, num_updated=num_updated)
	else:
		count = PdfDocument.all().count(limit=1000000)
		logging.info(
			'Complete update schema pdf with {} fetchs {} updates of {}!'.format(
				num_fetched,num_updated,count))

class UpdateSchemaHandler(webapp2.RequestHandler):
	"""Queues a task to start updating the model schema."""
	def get(self):
		deferred.defer(update_schema_pdf)
		self.response.write("""
		Schema update started. Check the console for task progress.
		""")

参考:Updating Your Model's Schema

ETLデータセットによる手書き漢字認識の学習

ETLデータセットは産総研が公開している手書き文字認識のデータセットです。ETL8Gを使用して、9561画像から957カテゴリの分類を行います。

ETL8Gはバイナリデータなので、画像を取り出すため、ETL文字データベース (etlcdb)を画像に変換するを参考に抽出しました。

etl

IMAGE_SIZE=48として、最初は以下のネットワークで学習しました。

model.add(Conv2D(32, (3, 3), input_shape=(IMAGE_SIZE,IMAGE_SIZE,1)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))

model.add(Conv2D(64,(3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))

model.add(Flatten())

model.add(Dense(1024))
model.add(Activation('relu'))
model.add(Dropout(0.2))
model.add(Dense(957))

model.add(Activation('softmax'))

全く収束しません。

etl_3x3

試行錯誤した結果、Conv2Dを5x5にしたら収束しました。

model.add(Conv2D(32, (5, 5), input_shape=(IMAGE_SIZE,IMAGE_SIZE,1)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))

model.add(Conv2D(64,(5, 5)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))

model.add(Flatten())

model.add(Dense(1024))
model.add(Activation('relu'))
model.add(Dropout(0.2))
model.add(Dense(957))

model.add(Activation('softmax'))

etl_5x5

TensorFlowでひらがな・漢字文字認識でも5x5の畳み込みを使用しているようです。3x3だと局所特徴量すぎるのかと思っていたのですが、BatchNormalizationを入れたら3x3でも収束するようです。

model.add(Conv2D(32, (3, 3), input_shape=(IMAGE_SIZE,IMAGE_SIZE,1)))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2,2)))

model.add(Conv2D(64,(3, 3)))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2,2)))

model.add(Flatten())

model.add(Dense(1024))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Dropout(0.2))
model.add(Dense(num_classes))

model.add(Activation('softmax'))

etl_all_3x3_batch

BatchNormalization優秀ですね。

Colaboratoryを使用して無料でTPUを使用した学習を行う

Googleが提供するColaboratoryを使用すると、無料でTPUを使用した学習を行うことができます。

colaboratory

ColaboratoryではKerasがデフォルトでインストールされているため、Pythonのコードをコピペするだけで学習が動作します。

学習にかけるデータセットは接続のたびに初期化されるため、GoogleDriveに配置し、以下のコマンドでマウントします。

from google.colab import drive
drive.mount('/content/drive')

マウントしたフォルダから展開してデータを準備します。

!cp "./drive/My Drive/colaboratory/extract.zip" ./extract.zip
!unzip extract.zip


連続使用は最大12時間、アイドル状態が90分続くと停止という制約はありますが、とても簡単に学習環境を増設できて便利です。

2018年に買ってよかったもの

iPad Pro 2018。2016モデルから買い替えました。今回もLTEモデル。圧倒的なGPU性能でPCよりも高速にCNNが動くという。今年はPCで生活していましたが、来年はDropBox+PowerPointでiPadでの軽量生活に挑戦します。といいつつ、Unityも動くSurfaceGOも気になるので、LTEモデルが一般販売されたら購入するかもしれません。



SurfaceBook2。Yoloの学習用と、WindowsMRでのBeatSaber用に買いました。キーボードの品質がとても高いです。



メガネに優しいDELLのWindowsMRです。BeatSaber用です。今年のベストゲームはBeatSaberで、音楽ゲームの未来を見ました。来年のOculusQuestでBeatSaberが発売されたら大ヒットしそうな予感です。



SurfaceBook2の充電器が重いので小型版です。出張用。少し熱くなりますが、問題なく使えています。



機械学習用のSamsungのSSD。青色がおしゃれ。



MacPro 2013に合わせて黒いキーボードを買いました。



複数台を切り替えられるマウスとキーボード。複数OSで開発する際に便利です。



ドアストッパー。シンプルです。



TimeCapsuleからの乗り換え。小型になりました。



GoogleWifiになりバックアップが取れなくなったので、USBメモリにバックアップを取るようにしました。超小型で256GBです。MacPro 2013の容量が256GBなのでちょうどよいです。



スタバの豆を格納するためのキャニスターです。ちょうど、1袋分、格納できるので便利です。



今年のベスト漫画です。働こうと思います。

高精度なMNISTの学習と認識

手書き数字認識を行う際、MNISTを使用しますが、現実のデータはデータセットほど綺麗に正規化されていないためうまく認識できないことがあります。

Kerasの標準のサンプルでは、精度は98.40%ですが、ImageDataGeneratorを使用していないため、一部がクロップで欠けているような画像ではうまく認識できなくなります。

mnist_mlp.py

対して、以下のサンプルでは、精度は99.55%になります。ImageDataGeneratorを使用しており、width_shift_rangeやrotation_rangeを使用しているため、汎化性能が高くなっています。また、Pseudo Labelingにより学習データを増強しています。こちらはコンペとしての性能を上げるためであり、汎化性能への寄与は少ないものと考えられます。

MNIST_keras_CNN-99.55%25.ipynb

こちらだと、概ね、多くの手書き文字を正確に認識します。現実のアプリケーションでは、数値的な精度差以上に性能差が出ることがあるのは意識しておく必要がありそうです。

ちなみに、CapsNetは99.66%のようです。

CapsNet-Keras

PythonでGoogleCloudStorageにファイルをアップロードする

PythonでGoogleCloudStorageにファイルをアップロードします。

まず、IAMでサービスアカウントを作成し、サービスアカウントの鍵情報をjsonでダウンロードします。その後、CloudStorageのBucketの権限にサービスアカウントを追加します。

必要なライブラリをインストールします。

pip3 install google-cloud-storage
python3 upload.py src.zip dest.zip

ダウンロードしたJSONを使用してアップロードします。

import os
import sys
from google.cloud import storage

os.environ["GOOGLE_APPLICATION_CREDENTIALS"]='xxx.json'
client = storage.Client()
bucket = client.get_bucket('backup')

print("from : "+sys.argv[1])
print("to   : "+sys.argv[2])
blob = bucket.blob(sys.argv[2])
blob.upload_from_filename(filename=sys.argv[1])

SakuraVPSからGCE f1microへの乗り換え

ABARSのWEBサイトを、SakuraVPSからGCEのf1microに乗り換えました。当時は2010年にSakuraの専用サーバからSakuraVPS 512MBに乗り換えて、かなりコストダウンになったのですが、GCEのf1microではついに無料になりました。

スペック的には、当時のVCPU 2コア+SSD 20GB+メモリ512MBから、VCPU 1コア+HDD 30GB+メモリ512MBへの移行になります。WordPressやMySQLを使わないレガシーなサービスしか動かしていないため、メトセラのゲームサーバを動かしても、GCEのf1microでもCPU使用率は余裕がありそうです。

Sakura VPS

スクリーンショット 2018-12-30 10.07.52


GCE f1micro

スクリーンショット 2018-12-30 10.06.08


静的なWEBサイトを動かす分には、かなりお得です。

セットアップは下記サイトを参考にしました。
GCE の無料枠のサーバを立るときに、初見でハマりそうなところ

なお、GCEはFTP接続できないので、SFTP接続が必要になります。
GoogleComputeEngineのVMインスタンスにFTP接続しようとして奮闘した話 結局SFTP

今回、CentOS7にして、Let's Encryptを使用してhttps対応もしました。
CentOS 7 + Apache 2.4 に Let’s Encrypt の証明書を導入する手順

性別推定モデルにおけるランダムイレージング

SqueezeNetにおける性別推定モデルにおいて、学習時にランダムイレージングを使用して効果を計測しました。ランダムイレージングは、ランダムに一部の領域を塗りつぶすことで、汎化性能を上げる手法です。

使用前
agegender_gender_squeezenet_


使用後
agegender_gender_squeezenet_augumented


ランダムイレージングを使用すると、accuracy for trainingが下がり、accuracy for validationが上がります。ただ、使用前、使用後でaccuracy for validationに大差はないため、このモデルではあまり効果がないようです。

YoloKerasFaceDetection

性別推定モデルのSqueezeNetとMobileNetの比較

IMDB wikiデータセットを使用して性別推定モデルを構築した場合の、lossとaccuracyをSqueezeNetとMobileNetで計測しました。

SqueezeNet

agegender_gender_squeezenet_


MobileNet

agegender_gender_mobilenet_


MobileNetの方がTrainingのaccuracyは高くなるのですが、Validationのaccuracyが伸びていかないため、SqueezeNetでも十分そうです。

SqueezeNetは10.2MB、MobileNetは34.4MBの重みになります。

YoloKerasFaceDetection

Darknetにおける転移学習の効果

Darknetを使用した学習では、引数に学習済み係数を与えて転移学習を行うことができます。Darknetで使用するYOLOv2の前段はVGGライクな特徴検出器となっており、Imagenetなどの大規模データで学習したフィルタ係数を流用することで、効率的に学習を行うことができると言われています。

今回はFDDBを使用した顔検出において、転移学習の効果を確認します。モデルはYOLOv2-tinyを使用します。FDDBのデータセットは2845枚で、3/4をトレーニング、1/4をバリデーションとして用います。

まずは、転移学習をしない場合の学習の推移です。

yolov2-tiny-train-one-class_32600


次に、転移学習をする場合の学習の推移です。

yolov2-tiny-train_431800


転移学習をしない場合は、最初のlossが大きいですが、転移学習をする場合は最初のlossが低い地点からスタートすることがわかります。また、lossの低下速度が速くなっています。

ただ、転移学習をしない場合でも、Geforce1050tiで一晩で学習が終わっており、また、最終的にできあがるモデルの精度もあまり違わないため、Darknetにおいては転移学習をしなくてもよさそうという結論です。

ソースコード:YoloKerasFaceDetection

YAD2KでYolov2の重みをKerasに変換する

YAD2Kを使用することで、Darknetで学習したYolov2の重みをKerasに変換することができます。

変換を行うには以下のコマンドを使用します。
python3 yad2k.py yolov2-tiny.cfg yolov2-tiny.weights yolov2_tiny.h5

注意点として、64bitでビルドしたDarknetを使用している場合、yad2k.pyのweights_file.read(16)をweights_file.read(20)に書き換える必要があります。
    weights_header = np.ndarray(
        shape=(4, ), dtype='int32', buffer=weights_file.read(20))
(参考:Tiny Yolo conversion fails to detect any objects

以下のコマンドでテストを行います。
python3 test_yolo.py yolov2_tiny.h5

MMdnnを使用してモデルをcaffeからkerasに変換する

MMdnnはマイクロソフトが開発しているモデルコンバータです。モデルデータをIRに変換することで、モデルの相互変換を可能にしています。

MMdnn

mmdnnをインストールし、caffemodelとprototxtからhdf5に変換するには以下のようにします。inputShapeを指定しない場合、None例外が発生します。

pip2 install mmdnn
mmconvert --srcFramework caffe --inputWeight face.caffemodel --inputNetwork face.prototxt --dstFramework keras --outputModel face.hdf5 --inputShape 1,3,448,448
Recent Comments
Search
Profile

abars

アプリとWEBサービスを開発しています。最近はUnityとGAE/pyが主戦場。

ブラウザ向けMMOのメトセライズデストラクタ、イラストSNSのイラストブック、東証の適時開示情報を検索できるTDnetSearchを開発しています。

かつてエンターブレインのTECH Win誌でATULADOを連載しました。

サイト:ABARS
Twitter:abars
Github:abars

Twitter
TopHatenar
HotEntry
Counter

アクセス解析付きカウンター。あなたのBLOGにもどうですか?登録はこちらから。

TOP/ BLOG/ LECTURE/ ONLINE/ RUINA/ ADDON/ THREAD/ METHUSELAYZE/ IPHONE/ MET_IPHONE/ ENGLISH/ RANKING