クイックノート

ちょっとした発見・アイデアから知識の発掘を

【R】abindの高速化

Rで二次元配列(行列)データに、
行を追加するならrbind、
列を追加するならcbindですが、
三次元以上の多次元配列にデータを追加するには・・・?

そう、abindを使えば、
多次元配列でも好きな次元にデータを追加できますね。

機械学習で画像を入力する場合などには、
画像の数 x 縦 x 横 の三次元データを扱うことが多いので、
abindにお世話になることも多いと思います。

ところが、このabindを使って、
画像を一つずつ追加して訓練データを作るみたいな使い方をすると、
処理にかなりの時間を要することがあります。

ここでは、そんなabindの処理を高速化する方法を紹介します。

遅いコードの例

画像を一つずつ読み込んで、
画像数x縦x横の三次元配列を作る事を考えます。

素朴に実装すると、下のコードのように、
for文内でabindを実行するコードになりそうです。

library(abind)
library(keras)

N = 1000
SIZE = 30

load_image = function(){
  array_reshape(diag(1,SIZE),dim = c(1,SIZE,SIZE))
}

x = NULL
for(i in 1:N){
  if(i %% 100 == 0) print(i)
  x = abind(x,load_image(),along=1)
}
dim(x)

load_imageは画像を読み込む関数の「つもり」で、
ここでは、画像は使わず単純にSIZExSIZEの単位行列を生成しています。
そして、array_reshapeで1xSIZExSIZEの三次元の形に直しています。

for文では、1xSIZExSIZEのデータを一次元方向に一つずつ結合しています。 最終的には、NxSIZExSIZEのデータが出来上がります。

このコードはもちろん望み通り動いてくれるのですが、
非常に遅いです。

高速化したコード

上のコードが遅い原因は、
for文の中でabindを実行しているためです。
この場合、毎回すでに結合済みのデータをコピーすることになるので、
ループが進むに連れてコピー処理のために時間が膨れ上がります。

このコピーの問題を避けるために、
forループでは結合する対象を一旦リストに格納しておき、
forループが終わった後にリストをまとめてabindで結合するという方法を取ります。

x = list()
for(i in 1:N){
  if(i %% 100 == 0) print(i)
  x[[i]] = load_image()
}
x = abind(x,along=1)
dim(x)

これで無駄なコピーを避けることができるので、
かなり高速に実行できるようになります。

計算時間の比較

実際にどのくらい早くなったかを記しておきます。

コード改善前は、全ての処理に約20秒かかっていましたが、
改善後は、約2秒で完了しました。

f:id:u874072e:20200826143518p:plain:w300
計算時間の比較

もちろん、この結果から、単純に10倍早くなると言える訳ではありません。

改善前のコードでは反復回数が増えるに連れて、
コピーする箇所が増えていくので、
反復回数が多ければ多いほど高速化の効果も大きくなります。

まとめ

お世話になるシーンの多いabindを高速化する方法を紹介しました。
素朴にabindをfor文の中で反復するとかなり遅く、
一度listに集めてから最後にabindで結合すると劇的に速くなりました。

プライバシーポリシー