EnsekiTT Blog

EnsekiTTが書くブログです。

CIFAR10を混ぜたままChainerのDCGANに突っ込んだら名状しがたい何かが生成された話

つまりなにしたの?

前回、モノクロ画像(MNIST)をDCGANで学習させたし、今度はカラー画像をやろうと思ってCIFAR10を学習させた。
ただ、飛行機と自動車と鳥と猫と鹿と犬とカエルと馬と船とトラックを混ぜたまま入れたせいか、生成されたものはクリーチャーなのか前衛移動手段のような何かになった。
前回の記事はこちら
ensekitt.hatenablog.com
f:id:ensekitt:20171107072528j:plain

モノクロ画像の時と何を変えたの?

Chainerでは画像を(枚数, カラーチャンネル(RGBなら3), 高さ, 幅)で入力することが期待される(Convolution2Dとかを使う場合)
そこで、バイナリから読みだして、整形して、ChainerのSerialIteratorに突っ込んだ。
ホントは一気にバイナリを読みだしてよかったんだけど、50000枚で回す前に10枚とかで動くことの確認をしたかったので、
割とちまちま継ぎ足していったらappend祭りの効率悪い感じになってた。
これで5秒位読み込みにかかる。

imgs = []
dataset_path = os.environ.get(key="DATA_SET_DIR")
dataset_dir = os.path.join(dataset_path, 'cifar10/cifar-10-batches-bin')
n_train = 10000
files = 5
for i in range(files):
    dataset_path = os.path.join(dataset_dir, 'data_batch_'+str(i+1)+'.bin')
    with open(dataset_path, 'rb') as cifar10:
        for i in range(n_train):
            data = cifar10.read(3073)
            values = list(struct.unpack("3073b", data))
            label = values[0]
            img = np.asarray(values[1:], dtype=np.uint8).reshape(32,32,3, order='F')/255.
            imgs.append(img)

imgs = np.asarray(imgs, dtype=np.float32)
X = imgs.reshape(n_train*files, 3, 32, 32)
train = iterators.SerialIterator(X, 100)

CIFAR10とは?

一般物体認識(身近にあるものを写真で認識するタスク)によく使われるデータセットで、CIFAR10には10種類のもの(生き物)が写った画像データとそのラベルデータが含まれている。
なんでこの高画質ってわけでもない画像データセットがありがたがられるかというと、80 Million Tiny Images(8000万枚の小さい画像データセット)から6万枚の画像を抽出してラベル付けしたという並の気合じゃ乗り越えられない壁を乗り越えて頂いているから。
CIFAR10には飛行機と自動車と鳥と猫と鹿と犬とカエルと馬と船とトラックの画像が含まれている。
画像サイズとしては32x32でカラー(3色)。ラベルは1対1でつけられていて重複はない。
PythonとかMatlabだと直接読み込める形式で提供されている。
CIFAR-10 and CIFAR-100 datasets

ネットワークについて

MNISTのときのネットワークだと浅すぎたのかうまく行かなかった。
仕方ないので、Chainerのチュートリアルにあるものを使ってとりあえずすすめることにした。
chainer/examples/dcgan at master · chainer/chainer · GitHub
ほぼ原文ママ()

Generator

class Generator(chainer.Chain):
    def __init__(self, z_dim, bottom_width=4, ch=512, wscale=0.02):
        super(Generator, self).__init__()
        self.z_dim = z_dim
        self.ch = ch
        self.bottom_width = bottom_width

        with self.init_scope():
            w = chainer.initializers.Normal(wscale)
            self.l0 = L.Linear(z_dim, bottom_width * bottom_width * ch,
                               initialW=w)
            self.dc1 = L.Deconvolution2D(ch, ch // 2, 4, 2, 1, initialW=w)
            self.dc2 = L.Deconvolution2D(ch // 2, ch // 4, 4, 2, 1, initialW=w)
            self.dc3 = L.Deconvolution2D(ch // 4, ch // 8, 4, 2, 1, initialW=w)
            self.dc4 = L.Deconvolution2D(ch // 8, 3, 3, 1, 1, initialW=w)
            self.bn0 = L.BatchNormalization(bottom_width * bottom_width * ch)
            self.bn1 = L.BatchNormalization(ch // 2)
            self.bn2 = L.BatchNormalization(ch // 4)
            self.bn3 = L.BatchNormalization(ch // 8)

    def __call__(self, z):
        h = F.reshape(F.relu(self.bn0(self.l0(z))),
                      (len(z), self.ch, self.bottom_width, self.bottom_width))
        h = F.relu(self.bn1(self.dc1(h)))
        h = F.relu(self.bn2(self.dc2(h)))
        h = F.relu(self.bn3(self.dc3(h)))
        x = F.sigmoid(self.dc4(h))
        return x

Discriminator

class Discriminator(chainer.Chain):

    def __init__(self, bottom_width=4, ch=512, wscale=0.02):
        w = chainer.initializers.Normal(wscale)
        super(Discriminator, self).__init__()
        with self.init_scope():
            self.c0_0 = L.Convolution2D(3, ch // 8, 3, 1, 1, initialW=w)
            self.c0_1 = L.Convolution2D(ch // 8, ch // 4, 4, 2, 1, initialW=w)
            self.c1_0 = L.Convolution2D(ch // 4, ch // 4, 3, 1, 1, initialW=w)
            self.c1_1 = L.Convolution2D(ch // 4, ch // 2, 4, 2, 1, initialW=w)
            self.c2_0 = L.Convolution2D(ch // 2, ch // 2, 3, 1, 1, initialW=w)
            self.c2_1 = L.Convolution2D(ch // 2, ch // 1, 4, 2, 1, initialW=w)
            self.c3_0 = L.Convolution2D(ch // 1, ch // 1, 3, 1, 1, initialW=w)
            self.l4 = L.Linear(bottom_width * bottom_width * ch, 1, initialW=w)
            self.bn0_1 = L.BatchNormalization(ch // 4, use_gamma=False)
            self.bn1_0 = L.BatchNormalization(ch // 4, use_gamma=False)
            self.bn1_1 = L.BatchNormalization(ch // 2, use_gamma=False)
            self.bn2_0 = L.BatchNormalization(ch // 2, use_gamma=False)
            self.bn2_1 = L.BatchNormalization(ch // 1, use_gamma=False)
            self.bn3_0 = L.BatchNormalization(ch // 1, use_gamma=False)

    def __call__(self, x):
        h = add_noise(x)
        h = F.leaky_relu(add_noise(self.c0_0(h)))
        h = F.leaky_relu(add_noise(self.bn0_1(self.c0_1(h))))
        h = F.leaky_relu(add_noise(self.bn1_0(self.c1_0(h))))
        h = F.leaky_relu(add_noise(self.bn1_1(self.c1_1(h))))
        h = F.leaky_relu(add_noise(self.bn2_0(self.c2_0(h))))
        h = F.leaky_relu(add_noise(self.bn2_1(self.c2_1(h))))
        h = F.leaky_relu(add_noise(self.bn3_0(self.c3_0(h))))
        return self.l4(h)

だんだん良くなるかと思った

けど、そうでもない。
f:id:ensekitt:20171107072618j:plain
2epochだとそもそもカラーの配列間違えたかと思った
500epochこれこのまま行ったらうまくいくやろ!!!とテンション上がって出社した。
帰ってきて、なにか名状しがたいものが生成されていた。
1000epochだとなんかもはや写っているものはあんまり決め打ちできないからか
景色の部分が生成されている気がする。
CIFAR-10ってそもそも見にくいしね!(雑

コードはこちら

github.com