Chainerで画像の前処理やDataAugmentationをしたいときはDatasetMixinを継承しよう
Chainerにはデフォルトでランダムクロップや標準化といった、画像の前処理やDataAugmentation用の関数が用意されていません。別途のChainer CVというライブラリを使う方法もありますが、chainer.dataset.DatasetMixinを継承させて独自のデータ・セットを定義する方法を紹介します。
chainer CV:https://github.com/chainer/chainercv
目次
chainer.dataset.DatasetMixinの継承
今回DenseNetのChainer実装を参考にしました。この例はCIFAR-10の前処理を行うクラスで、入力画像の形式は(3, 32, 32)とします。
class Preprocess(chainer.dataset.DatasetMixin):
def __init__(self, pairs):
self.pairs = pairs
def __len__(self):
return len(self.pairs)
def get_example(self, i):
x, y = self.pairs[i]
# label
y = np.array(y, dtype=np.int32)
# random crop
pad_x = np.zeros((3, 40, 40), dtype=np.float32)
pad_x[:, 4:36, 4:36] = x
top = random.randint(0, 8)
left = random.randint(0, 8)
x = pad_x[:, top:top+32, left:left+32]
# horizontal flip
if random.randint(0, 1):
x = x[:, :, ::-1]
return x, y
コンストラクタのpairsには、訓練データやテストデータ全体を代入します。コンストラクタの継承は特に行わなくてよいそうです。次に、__len__
関数を定義し、データの数を返せるようにします。
最後にget_exampleの関数にDataAugmentationや標準化などの前処理を定義します。ここでi番目は、pairsに定義されたサンプルのインデックスです。全てテンソルではなく、サンプル単位のNumpy配列を操作すればよいです。Chainerはchannels_firstなので、(チャンネル, 縦, 横)という配列になりますのでそこは注意してください。
順に解説していきましょう。「label」のところは、サンプルのyがラベルの番号を表す数字なので(MNISTだったら8を表す8)、ランク0の配列を避けるために、ランク1の配列にします。
ランダムクロップは、よく論文で使われる形式で上下左右に4ピクセルのPaddingを入れて、40×40の中からランダムに32×32のエリアを抜き出すというものです。numpy.randomとrandomパッケージのrandomの挙動の違いに注意してください。
「Horizontal Flip」はランダムで左右を反転させる前処理です。これもよく論文で使われます。random.rand(0, 1)はランダムに0か1かを返す関数です。
他にも状況に応じて、Numpyの配列を処理するコードをget_exampleの中に追加していきます。最後にサンプル単位のXとyを返して終わりです。
独自Datasetの使い方
この独自に定義したDatasetをSerialIteratorで回すことができます。SerialIteratorでは、シャッフルやミニバッチ単位の切り出しといった最低限の機能しかないので、他に前処理したければ独自のDataset作れということなんでしょうね。
例えばCIFAR-10ならこうします。
train, test = chainer.datasets.get_cifar10()
train = Preprocess(train)
test = Preprocess(test)
train_iter = chainer.iterators.SerialIterator(train, 128)
test_iter = chainer.iterators.SerialIterator(test, 100, repeat=False, shuffle=False)
このように読み込んだデータを、独自定義したDatasetのコンストラクタに食わせるだけです。あとはSerialIteratorが勝手にやってくれます。そのためのget_exampleの定義なのですから。
以上です。ChainerではChainer CVといったライブラリを使わない場合、前処理をNumpy配列から書いていくことになります。その際、chainer.dataset.DatasetMixinを継承し独自のDatasetを定義することで、SerialIteratorにスムーズにわたすことができます。
(こういうの公式チュートリアルでちゃんと説明してもいいと思いますが、自分の探し方が悪かったのか特に見つからなかったです。こういうとこChainer不親切だなと思いました。)
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー