LoginSignup
1
2

More than 5 years have passed since last update.

chainer覚書(CycleGAN) updated: 12/02

Last updated at Posted at 2017-11-21

11/21

https://github.com/yamaken124/cycle_gan
ラボ内でGANコンテストがあったので、その時のコードをベースにちゃんと読み返し。

train.py

import argpase # parser用

from chainer import cuda # GPU対応用
from chainer import serializers # 学習した重みを保存したりロードしたりするのに使う

# -gというコマンドラインからの指定を可能にするために、--gpuも必要で.gpuで呼び出し可能
parser.add_argument('--gpu', '-g', type=int, default=0, help='GPU device ID')
# -xを指定しないでもいけるみたい
parser.add_argument('--size', type=int, default=128)
# --はないとダメみたい
# あと呼び出す時
args = parser.parse_args()
# これをして、args.sizeみたいに呼び出す必要あり
train_iterA = chainer.iterators.MultiprocessIterator(trainA, args.batch_size, n_processes=min(8, args.batch_size))
train_iterB = chainer.iterators.MultiprocessIterator(trainB, args.batch_size, n_processes=min(8, args.batch_size))

DataMixinを継承したImageDatasetのクラスをMultiprocessIteratorに渡すことでCPUでミニバッチ分の画像を読み込み、GPUで学習といったことができるようになります。

if args.gpu >= 0:
    cuda.get_device_from_id(args.gpu).use()
    genA.to_gpu()

chainer.cuda.to_gpu()関数はnumpy.ndarrayオブジェクトを特定のデバイスへコピーします。

optimizer_genA = chainer.optimizers.Adam(alpha=0.0002, beta1=0.5, beta2=0.9)
optimizer_genA.setup(genA)

Adamはclass
つまりクラスを初期化してオブジェクトを生成し、その後setupを呼び出している

class Adam(optimizer.GradientMethod):

こんな感じで、Adamもまたクラスを継承したオブジェクト

setup関数はここでセットされているのがわかる(optimizer.GradientMethod)
http://docs.chainer.org/en/stable/reference/core/generated/chainer.GradientMethod.html#chainer.GradientMethod

for epoch in range(args.epoch):
    if epoch > 100:
        decay_rate = 0.0002 / 100
        optimizer_genA.alpha -= decay_rate
    iter_num = N // args.batch_size
    for i in range(iter_num):
        imagesA = train_iterA.next()

next()で次のiteratorを呼び出す

class chainer.iterators.MultiprocessIterator(dataset, batch_size, repeat=True, shuffle=True, n_processes=None, n_prefetch=1, shared_mem=None)

iteratorを最初に設定する時に、batch_sizeも指定しているので、大丈夫

# Linear層がないから、クロップサイズをiterationごとに変更しても大丈夫なわけだ。だから、ch // 4 みたいな書き方をmodelの方でするわけだ。
if args.variable_size:
    crop_size = np.random.choice([160, 192, 224, 256])
    resize_size = np.random.choice([160, 192, 224, 256])
    imagesA = [random_augmentation(image, crop_size, resize_size) for image in imagesA]
realA = chainer.Variable(genA.xp.asarray(imagesA, 'float32'))

Variableはノードのこと
これに対して、足したり引いたりかけたり、その後バックプロパゲーションかけたりするので、クラス化していると思われる

if iterations < args.memory_size:
    fakeA = genA(realB)
    fakeB = genB(realA)
    fakeA.unchain_backward()
    fakeB.unchain_backward()
else:
    fake_imagesA = fake_poolA[np.random.randint(args.memory_size, size=args.batch_size)]
    fake_imagesB = fake_poolB[np.random.randint(args.memory_size, size=args.batch_size)]
    if args.variable_size:
        fake_imagesA = [random_augmentation(image, crop_size, resize_size) for image in fake_imagesA]
        fake_imagesB = [random_augmentation(image, crop_size, resize_size) for image in fake_imagesB]
        fakeA = chainer.Variable(genA.xp.asarray(fake_imagesA))
        fakeB = chainer.Variable(genA.xp.asarray(fake_imagesB))

メモリーサイズってのがよくわからない
xp: CPU,GPUに応じてnumpy,cupyを自動で使い分けてくれるっぽい
やっぱおかしいかも

np.random.randint(args.memory_size, size=args.batch_size)

普通、第一引数=Low, 第二引数=Highになる

だから1000以下じゃないとうまくいかなかったのかな?
いや、そんなことないか
時間はかかっていたが、学習自体は進んでいたはず

fake_poolA.shape
>(200, 3, 128, 128)

dataset.py

class ImageDataset(chainer.dataset.DatasetMixin):
    def __init__(
    def __len__(
    def get_example(self, i):

ここら辺は、基本的に継承したクラスのOverwriteぽい
なので、引数とかも最初から規定されているというかね。だからget_exampleとかみんな書くわけだ
pythonのクラスの継承方法は初めて知った

model.py

class Generator とか class Discriminator とかもchainer.Chainの継承からのクラスだったのか

layersの書き方は割とみんなしてるんだなぁ
創意工夫の結晶か
http://musyoku.github.io/2017/06/18/Chainer%E3%81%AEChain%E3%82%92%E3%82%82%E3%81%86%E5%B0%91%E3%81%97%E6%A5%BD%E3%81%AB%E6%9B%B8%E3%81%8F/

*layers とか **layers という風に、ついている*がなんなのかと思ったが、
https://qiita.com/HirofumiYashima/items/dd1becaa3d147c90ebc6
可変長引数の設定みたいだ

class Generator(chainer.Chain):
    def __init__(self, ch=128, block_num=9, bn=True):
        layers = {}
        self.block_num = block_num
        layers['conv1'] = CBR(3, ch // 4, bn=bn, sample='c7s1')
        layers['conv2'] = CBR(ch // 4, ch // 2, bn=bn, sample='down')
        layers['conv3'] = CBR(ch // 2, ch, bn=bn, sample='down')
        for i in range(self.block_num):
            layers['res{}'.format(i)] = ResBlock(ch, ch, bn=bn)
        layers['dc1'] = CBR(ch, ch // 2, bn=bn, sample='up')
        layers['dc2'] = CBR(ch // 2, ch // 4, bn=bn, sample='up')
        layers['dc3'] = CBR(ch // 4, 3, bn=False, sample='c7s1', activation=F.tanh)
        super(Generator, self).__init__(**layers)

こういう場合、layersをタプル型として宣言し、
{'conv1': xxx, 'conv2': xxx}みたいになるようにしてしまうわけか

リンク先のように
c='c', d='d'
というような指定でなくてもいけるんだな

だいぶ昔

気持ちい書き方

if args.gpu >= 0:
    cuda.check_cuda_available()
    chainer.Function.type_check_enable = False
    cuda.get_device(args.gpu).use()
    xp = cuda.cupy
else:
    xp = np
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2