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

#目的
Keras-GPUは何も設定しないと空いているGPUメモリをすべて専有してしまう。
複数モデルを回すときや共有GPUでこれをやってしまうと困ったことになってしまう。

GPUメモリ使用量を最低限に抑えつつ回す方法、設定について記述する。

##GPU Issues
Kerasはちゃんと設定をしていないとGPUメモリを多数(というか空いてるだけ)つかみに行ってしまい、GPU memory shortageを起こしてしまう。
そうするとチームメンバーからブチギレメールが飛んでくるのでちゃんと設定しておこう（反省）

自分の環境ではkeras-gpuは初期状態で*全てのGPUの全メモリを専有*してしまう。
TitanXが4つあると*12GBx4をKerasが専有*してしまっていた。。死んだほうがいい

#環境について

OS: Ubuntu 16.04

Keras-gpu:2.15

```
pip install git+git://github.com/fchollet/keras.git --upgrade --no-deps
```

gitから最新をインストールしておく。

TF-gpu:1.8.0

```
pip install --upgrade tensorflow-gpu
```

## Scriptに追加しておくもの。

```python:cifar10.py

from keras import backend as K

if 'tensorflow' == K.backend():
    import tensorflow as tf
from keras.backend.tensorflow_backend import set_session
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
config.gpu_options.visible_device_list = "0,1"
set_session(tf.Session(config=config))

```

上記記述を*本スクリプト前に*追加する。
特にconfig.gpu_options.allow_growth = Trueは必要な分だけメモリリソースを掴むため重要。

これはGPU"0,1"だけを使用するはずなのだが、試してみたところ結局全てのGPUを掴みにいってしまう..

解法としてcmd上で

```
export CUDA_VISIBLE_DEVICES=0,1
python cifar10.py
```

と使用するGPUはコマンドライン上で制限しないといけなそう。

## 備忘録的cifar10スクリプト

```python:cifar10.py
from keras import backend as K

if 'tensorflow' == K.backend():
    import tensorflow as tf
from keras.backend.tensorflow_backend import set_session
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
config.gpu_options.visible_device_list = "0,1"
set_session(tf.Session(config=config))

import keras.backend.tensorflow_backend
from keras.backend.tensorflow_backend import set_session
import keras
from keras.datasets import cifar10
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Activation
from keras.layers import Conv2D, MaxPooling2D, ZeroPadding2D, BatchNormalization
from keras.optimizers import SGD
from keras import backend as K
from keras.callbacks import ModelCheckpoint
from keras.preprocessing.image import ImageDataGenerator


#size of parameters
batch_size = 128
input_size = 32*32 # 28*28
input_h= 32
hidden_size= 100
filter_size = 64
filter_pixel = 6
pool_pixel = 2
output_size = 10
epochs = 40

# input image dimensions
img_rows, img_cols = input_h, input_h

# the data split between train and test sets
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(y_train, output_size)
y_test = keras.utils.to_categorical(y_test, output_size)

#Start Neural Network
model = Sequential()

#convolution 1st layer
model.add(Conv2D(filter_size, kernel_size=(filter_pixel, filter_pixel), padding='same',
                 activation='relu',strides=2,
                 input_shape=x_train.shape[1:])) #0
model.add(BatchNormalization())

model.add(Conv2D(filter_size*2, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(Conv2D(filter_size*2, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(BatchNormalization())
model.add(Conv2D(filter_size*4, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(Conv2D(filter_size*4, kernel_size=(3, 3), padding='same',strides=2,
                 activation='relu')) 
model.add(BatchNormalization())

model.add(Conv2D(filter_size*4, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(Conv2D(filter_size*4, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(BatchNormalization())

model.add(Conv2D(filter_size*8, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(Conv2D(filter_size*8, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(BatchNormalization())
model.add(Conv2D(filter_size*8, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(Conv2D(filter_size*8, kernel_size=(3, 3), padding='same',strides=1,
                 activation='relu')) 
model.add(BatchNormalization())


#Fully connected 1st layer
model.add(Flatten()) #1

#Fully connected final layer
#model.add(Dense(100, activation='relu')) #2
#model.add(Dropout(0.25))
model.add(Dense(output_size)) #2
model.add(Dropout(0.25))
model.add(Activation('softmax')) #3

#Trainer set
opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)
#parallel_model = multi_gpu_model(model, gpus=2)
model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=opt,
              metrics=['accuracy'])

#Show Model
model.summary()

#Save Model=ON
check = ModelCheckpoint("CIFAR10_5l.hdf5")

#Run model from CIFFAR10 tutorials
print('Using real-time data augmentation.')
# This will do preprocessing and realtime data augmentation:
datagen = ImageDataGenerator(
    featurewise_center=False,  # set input mean to 0 over the dataset
    samplewise_center=False,  # set each sample mean to 0
    featurewise_std_normalization=False,  # divide inputs by std of the dataset
    samplewise_std_normalization=False,  # divide each input by its std
    zca_whitening=False,  # apply ZCA whitening
    rotation_range=0,  # randomly rotate images in the range (degrees, 0 to 180)
    width_shift_range=0.1,  # randomly shift images horizontally (fraction of total width)
    height_shift_range=0.1,  # randomly shift images vertically (fraction of total height)
    horizontal_flip=True,  # randomly flip images
    vertical_flip=False)  # randomly flip images

# Compute quantities required for feature-wise normalization
# (std, mean, and principal components if ZCA whitening is applied).
datagen.fit(x_train)

# Fit the model on the batches generated by datagen.flow().
history = model.fit_generator(datagen.flow(x_train, y_train,
                                 batch_size=batch_size),
                    epochs=epochs,
                    validation_data=(x_test, y_test),
                    workers=8)
keras.callbacks.ProgbarLogger(count_mode='samples')

score = model.evaluate(x_test, y_test, verbose=0)

###save and load
model.save_weights('param_vgg.hdf5')

```
スクリプトは適当

##jobがどうなったか。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/171915/cb110b05-d85f-5382-572e-4fb819b19d5c.png)
python 4GBとちゃんとモデルサイズ相応のメモリを確保するようになった。
これをやらないとTitanXのメモリすべて(12GB)掴んでしまい、非常に迷惑。




##参考にしたGPUについてのgithub issues
>
Limit the resource usage for tensorflow backend
https://github.com/keras-team/keras/issues/1538

>
importing ImageDataGenerator will occupy all the GPU memory
https://github.com/keras-team/keras/issues/8429

#　追記
https://qiita.com/namakemono/items/12ad8a9f6d0561929056
同等のことを書いている方がいました。
本記事の進歩性はGPU数を変えるところでしょうか。ちゃんと日本語でも調べな。。