Python♪ディープラーニングを高速化するバッチ処理とは

ディープラーニングではバッチ処理という方法が使えます。難しそうに聞こえますが、バッチ処理とは「データをまとめて計算する処理」のことです。そして、まとめて計算することで速くなります。具体例によりバッチ処理の概要を説明し、速度の比較を行います。

0.「ゼロから作るDeep Learning」のポイント整理シリーズ

この記事は「ゼロから作るDeep Learning」のポイント整理シリーズの記事です。シリーズの名前のとおり私の自習用のメモです。書籍では説明されない基礎文法や補足・関連事項などを説明しており、書籍がなくてもわかる内容になっています。

1.バッチ処理の前にブロードキャスト

バッチ処理の前にブロードキャストについて簡単に説明します。ブロードキャストを使うとコードの記述量が減り、シンプルでわかりやすいコードになります。

人工知能でPythonがよく使われるのは、Numpyのブロードキャストの存在によるところが大きいのではないでしょうか。

では、早速コード01を見てください。2行2列の行列a1、a2とベクトルbとの積をもとめるシンプルなコードです。9、10行目で行列の積を求めています。

ただ、この記述方法では、データの数が増えるとa1, a2, a3, a4・・・のように変数を増やす必要があり現実的ではありません。

#コード01
import numpy as np

a1 = np.array([[1, 1],
               [1, 1]])
a2 = np.array([[2, 2],
               [2, 2]])
b = np.array([9, 9])
c1 = np.dot(a1, b) #行列計算
c2 = np.dot(a2, b) #行列計算
print(c1, c2)
#出力01
[18 18] [36 36]

そこで、実際にはコード02のように変数aに配列を用いて大量のデータを扱います。ブロードキャストの機能を持たないプログラム言語ではコード02のような書き方が一般的ではないでしょうか。

#コード02
import numpy as np

a = np.array([[[1, 1],
               [1, 1]],
              [[2, 2],
               [2, 2]]])
b = np.array([9, 9])
for i in range(2):
    c = np.dot(a[i], b) #行列計算
    print(c)
#出力02
[18 18]
[36 36]

では、いよいよブロードキャストの機能をつかいます。ブロードキャストを使うとコード03のように、さらに簡単な記述になります。for文を使わなくても2回分の行列計算をしてくれます。

4~8行目はデータの入力部分ですから、計算部分は9行目のたった1行のみです。Pythonのすごさを感じます。

#コード03
import numpy as np

a = np.array([[[1, 1],
               [1, 1]],
              [[2, 2],
               [2, 2]]])
b = np.array([9, 9])
c = np.dot(a, b) #行列計算
print(c)
#出力03
[[18 18]
 [36 36]]

さて、ここで重要なのはコードの記述量が減ったことだけではありません。Numpyの行列計算ではコード02のようにそれぞれのデータを1つずつ計算するよりも、コード03のようにまとめて計算した方が計算速度があがります。

2.まとめて計算するとどのくらい速くなるのか

プログラムの計算速度を計測したPCのスペックは以下の通りです。ノートPCはソフト的に「高速」「中速」「低速」の3種類のモードがあったため「高速モード」を選択しました。

ノートPC(実装RAM:12.0GB) ※高速モード
プロセッサ:Intel® Core™ i5-8250U CPU @ 1.60GHz 1.80GHz
OS:Windows 10 Home (64bit OS、x64 ベースプロセッサ)

コード04では、2行2列の行列aを1000000個生成し、それぞれ2行2列の行列bとの積を求めました。要素には0以上1未満の値をランダムに入力します。

積の求め方は以下の3通りで、1000000個のデータの計算時間の合計を求めました。カッコ内の秒数が計算時間であり、コード04を計10回実行した平均値です。(方法1)のように1データずつバラバラに計算するよりも、(方法2)(方法3)のようにまとめて計算することで計算速度を1/8~1/9に短縮することができました。

計算方法 計算時間
方法11000000個のデータを1データずつ計算3.6053秒
方法21000000個のデータを一度にまとめて計算0.4305秒
方法3バッチサイズ1000データ、1000バッチ計算0.3980秒
#コード04
import random
import time
import numpy as np

start = time.time()  #時間計測開始
#------(データ生成)-------
nnn = 1000000  #データ数
print('全データ数', nnn)
a = np.zeros((nnn, 2, 2))
for i in range(nnn):
    a[i, 0, 0] = random.random()
    a[i, 0, 1] = random.random()
    a[i, 1, 0] = random.random()
    a[i, 1, 1] = random.random()
b = np.zeros((2, 2))
b[0, 0] = random.random()
b[0, 1] = random.random()
b[1, 0] = random.random()
b[1, 1] = random.random()
#--------------------------
print('データ生成:', time.time() - start, '秒')

start = time.time()  #時間計測開始
#------(1データずつ計算)-------
for i in range(nnn):
    np.dot(a[i], b)
#--------------------------
print('1データずつ計算:', time.time() - start, '秒')

start = time.time()  #時間計測開始
#------(まとめて計算)-------
np.dot(a, b)
#--------------------------
print('まとめて計算:', time.time() - start, '秒')

start = time.time()  #時間計測開始
#------(バッチ処理)-------
#バッチサイズを1000データとし、1000バッチ1000回計算する。
batch_size = 1000
for i in range(0, len(a), batch_size):
    np.dot(a[i:i + batch_size], b)
#--------------------------
print('バッチ処理:', time.time() - start, '秒')
#出力04
全データ数 1000000
データ生成: 5.89745306968688 秒
1データずつ計算: 3.59637999534606 秒
まとめて計算: 0.424914360046386 秒
バッチ処理: 0.384211301803588 秒

全てのデータをまとめて一度に計算する(方法2)は、コードもシンプルでわかりやすいのですが、PCの能力によっては一度の計算できない場合があります。そのような場合には一度に計算するデータ数をバッチ処理(方法3)によって調整することができます。

3.バッチサイズの違いと計算時間

次にバッチサイズの違いが計算時間に与える影響について調べてみました。

コード05は、n行n列の行列aを1000000個生成し、それぞれn行n列の行列bとの積を求めます。なお、要素には0以上1未満の値をランダムに入力します。

行数(列数)nは、17行目のn_dimによって指定します。

このプログラムのn_dimの値を変えて、2行2列、4行4列、6行6列、8行8列の行列計算を行いました。

#コード05
'''バッチサイズの違いと計算時間'''
import random
import time
import numpy as np

def batch(a, b, batch_size):
    '''a, bの行列計算の計算時間測定。 batch_size:バッチサイズ'''
    start = time.time()  #計算時間計測開始
    for i in range(0, len(a), batch_size):
        np.dot(a[i:i + batch_size], b)
    print(time.time() - start)

#データ作成
nnn = 1000000  #データ数
print('データ数', nnn)
n_dim = 2
print('行数(列数)',n_dim)
a = np.zeros((nnn, n_dim, n_dim))
b = np.zeros((n_dim, n_dim))
for iii in range(10):  #平均値をとるために10回計算
    print(iii + 1,'回目')
    for i in range(n_dim):
        for j in range(n_dim):
            b[i, j] = random.random()
            for k in range(nnn):
                a[k, i, j]= random.random()

    #行列計算
    batch(a, b, 1)
    batch(a, b, 2)
    batch(a, b, 4)
    batch(a, b, 10)
    batch(a, b, 100)
    batch(a, b, 1000)
    batch(a, b, 10000)
    batch(a, b, 100000)
    batch(a, b, 1000000)

以下のグラフは、2行2列、4行4列、6行6列、8行8列の行列計算の計算時間とバッチサイズの関係です。

行列が大きい方が計算量が多くなるため、当然の結果ではありますが、2行2列よりも8行8列の方が計算時間が長くなっています。

また、いずれもバッチサイズが100~1000まではバッチサイズが大きくなるに従い計算時間が早くなりますが、それ以上になるとほとんど変わりません。また、バッチサイズが10000を超えると多少計算時間が増加しているものも見られます。

行列の大きさが変化しても、バッチサイズが100~1000のあたりに計算速度のピークがあるのは意外でした。

バッチサイズと計算時間の関係(ノートPC高速モード)

下のグラフの縦軸は[最も短い計算時間]÷[最も長い計算時間]です。データを1つずつ計算した場合(バッチサイズ = 1)に対して、バッチ処理により計算時間がどれだけ短くなったかを示します。

2行2列の行列の計算時間が1/9.6であるのに対して、8行8列では1/1.6であり、小さいサイズの行列ほどバッチ処理の効果が高いことがわかります。

行列サイズとバッチ処理の効果(ノートPC高速モード)

4.PCのスペックの影響

PCのスペックの影響について、「ノートPCの高速モード」 「ノートPCの低速モード」「ディスクトップPC」の3種類について比較しました。なお、高速モード、低速モードとはノートPCの機能で、ソフト的にCPUへの負荷を切り替えることができます。

ノートPCノートPCディスクトップPC
高速モード低速モード
実装RAM:12.0GB同左実装RAM:8.0GB
Intel i5-8250U CPU同左Intel i5-6500 CPU
1.60GHz 1.80GHz同左3.20GHz 3.19 GHz

下図は、2行2列の行列の計算時間の比較です。バッチサイズ1の場合、ノートPCの高速モード1.37秒に対して、低速モードでは4.57秒となり、計算速度が約3.3倍になりました。

また、偶然ではありますが、ディスクトップPCはノートPCの高速モードとほぼ同等の計算時間になりました。

バッチサイズと計算時間の関係(2行2列)

以下の図は、バッチ処理の効果を行列のサイズごとに比較したグラフです。PCによって、もう少し、傾向の違いが生じると思っていましたが、ほとんど同様の結果になりました。

行列サイズとバッチ処理の効果(各種PC)

比較対象となるPCの種類が少なく、他の条件では、違う傾向となる可能性はあります。自分のPCでも検証してみると面白いのではないでしょうか。

5.高速化の効果

高速化の効果について、例えば計算時間が5倍になれば、5日間かかった計算が1日で終わることになります。学習に多くの時間を必要とするディープラーニングにおいて、バッチ処理を行うだけで高速化できるというのは非常に有益なテクニックであると思います。

PCのハード的に1.5倍のスペックにするには諭吉さんが何枚も飛んでいくことになると思いますので、安上がりな手法ではないでしょうか。

6.まとめ

Numpyのブロードキャストの機能を使いバッチ処理を行うことによって、以下のような効果が期待できます。

  • バッチ処理を行うことで計算速度をあげることができる。
  • 行列のサイズが小さいほどバッチ処理の効果が大きい。
  • 今回の解析モデルでは、バッチサイズが100~1000より大きくなると計算速度はそれほど変化しない。

適当なバッチサイズについては、環境や解析モデルによっても変わる可能性がありますが、まずはバッチサイズを100~1000程度に設定し、必要に応じて調整すればよいのではないかと思います。

私が実際に購入した教材のご紹介

以下、私が実際に購入したPythonの教材をまとめてみました。 Pythonを学習する上で、少しでもお役に立つことができればうれしいです。

Python♪私が購入したPythonの書籍のレビュー
UdemyのPythonの動画講座を書籍を買う感覚で購入してみた

その他

Twitterへのリンクです。SNSもはじめました♪

液晶ペンタブレットを買いました
 (1) モバイルディスプレイを買うつもりだったのに激安ペンタブレット購入

以下、私が光回線を導入した時の記事一覧です。
 (1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
 (2) NURO光の開通までWiFiルーターを格安レンタルできる
 (3) NURO光の屋外工事の状況をご紹介。その日に開通!
 (4) 光回線開通!実測するとNURO光はやっぱり速かった
 (5) ネット上のNURO光紹介特典は個人情報がもれないの?