GPU のメモリは貴重だ. 12 GB のメモリをもつ NVIDIA TITAN X は高価だが,12 GB ではまだたりないことがある. 下位機種ではなおさらだ. 本来 1 バイトですむ値なら 8 bit 整数のかたちで GPU メモリにつめこめばそれを有効につかうことができるので,そのための方法を記述する.
機械学習とくに深層学習 (deep learning) のプログラムを実行するのに GPU がよくつかわれる. GPU のプログラミングはやっかいだが,ニューラルネットなどのプログラムを記述するには Theano というシステムをつかうのがひとつの方法だ.
学習データとして画像などをつかうとき,1 ピクセルを 32 bit 浮動小数で表現すると 4 バイトつかうので,貴重な GPU メモリを多量につかってしまう. それをふせぐために NVIDIA のライブラリ cuDNN ver. 2 などでは 16 bit 浮動小数をサポートしているが,それでも 2 バイトつかってしまうし,Theano などではまだ 16 bit 浮動小数のサポートがない. 画像が 256 階調なら 8 bit 整数であらわすることができるので,そのまま GPU メモリにつめこむことができれば,よりおおくのデータをつめこむことができる.
Theano による深層学習のプログラムは Deep learning tutorial などに記述されている. その一部である logistic regression のプログラムのなかにデータセットを GPU メモリによみこむ load_data() という関数があるが,この関数も 32 bit 浮動小数の配列を生成する. しかし,それをすこしかえることで,8 bit 整数の配列にすることができる. load_data() にふくまれる,配列を生成するもとのプログラムはつぎのとおりだ.
def shared_dataset(data_xy, borrow=True): data_x, data_y = data_xy shared_x = theano.shared(numpy.asarray(data_x, dtype=theano.config.floatX), borrow=borrow) shared_y = theano.shared(numpy.asarray(data_y, dtype=theano.config.floatX), borrow=borrow) return shared_x, T.cast(shared_y, 'int32') # “T” means Theano
これをつぎのようにかきかえれば,shared_x は 8 bit 整数の配列になる. 使用する直前にそれが 32 bit 浮動小数に変換される (変換せずにつかおうとすると,Theano がエラー・メッセージをだす). もちろん,その要素の値があらかじめ −128 から 127 のあいだになるようにしておく必要がある (もとの値が uint8 ならば 128 をひいておく必要がある).
def shared_dataset(data_xy, borrow=True): data_x, data_y = data_xy shared_x = theano.shared(numpy.asarray(data_x, dtype='int8'), borrow=borrow) shared_y = theano.shared(numpy.asarray(data_y, dtype='int32'), borrow=borrow) return T.cast(shared_x, 'float32'), shared_y
shared_x を上記のように式にかきかえると,それに対して get_value がつかえなくなる. そのため,つぎのようなかきかえが必要になる.
train_set_x, train_set_y = datasets[0] # datasets[0] は load_data() がかえした値 (shared_x) ... # compute number of minibatches for training, validation and testing n_train_batches = train_set_x.get_value(borrow=True).shape[0] / batch_size ...
これをつぎのようにかきかえる.
train_set_x, train_set_y = datasets[0] ... # compute number of minibatches for training, validation and testing n_train_batches = train_set_y.eval().shape[0] / batch_size ...
get_value(borrow=True) を eval() にかきかえるだけでなく train_set_x を train_set_y にかきかえたのは,そのほうがオーバヘッドがすくないだろうとかんがえたからである.
浮動小数の場合は平均値が 0 になるように正規化しておくことができるが,整数だとそれは困難なので,使用する直前に平均値をひいておくのがよいだろう. また,値の範囲もたとえば −1 から 1 のあいだにそろえておくのがよいだろう. このチュートリアルのプログラムでは入力変数に x というなまえがついているから, x を ((x − T.mean(x)) / 128.0) によって置換すればよい. たとえば,logistic regression のプログラムではつぎの行をおきかえる.
classifier = LogisticRegression(input=x, n_in=28 * 28, n_out=10)
これをつぎのようにする.
classifier = LogisticRegression(input=((x − T.mean(x)) / 128.0), n_in=28 * 28, n_out=10)
配列から内容をとりだすたびに計算が必要になるが,たたみこみニューラルネットなどの大規模な計算ならオーバヘッドは 10% 以下ですむ.