verilog書く人

自称ASIC設計者です。どなたかkaggle一緒に出ましょう。

chainerでVAT(準教師学習)

準教師学習とは一部のデータにのみ正解ラベルがつけられていて、残りはラベルがないという状態で、学習を行うことです。

ディープラーニングに必要な1クラスあたり数千のラベルを用意するのは大変なので、準教師学習の応用範囲は広いと考えています。

全てのデータセットにラベルがついてる場合でも準教師学習の手法を導入することによって性能が上がる場合があります。

 

今回はVAT(Virtual Adversarial Training)を使ってみます。


なぜVAT?
導入が非常に楽で、そこそこ性能がいいからです。性能だけならLadder Networkの方が上です。


ここではmattyaさんの実装

chainer-semi-supervised/vat.py at master · mattya/chainer-semi-supervised · GitHub

のパクってみます。
が、trainerを使ってみます。

 

前準備(必要な関数の宣言)

パクリ元から持ってきます。

class KL_multinominal(chainer.function.Function):
    """ KL divergence between multinominal distributions """

    def __init__(self):
        pass

    def forward_gpu(self, inputs):
        p, q = inputs
        loss = cuda.cupy.ReductionKernel(
            'T p, T q',
            'T loss',
            'p*(log(p)-log(q))',
            'a + b',
            'loss = a',
            '0',
            'kl'
        )(p, q)
        return loss / numpy.float32(p.shape[0]),

    # backward only q-side
    def backward_gpu(self, inputs, grads):
        p, q = inputs
        dq = -numpy.float32(1.0) * p / (np.float32(1e-8) + q) / numpy.float32(
            p.shape[0]) * grads[0]
        return cuda.cupy.zeros_like(p), dq


def kl(p, q):
    return KL_multinominal()(F.softmax(p), F.softmax(q))


def kl(p, q):
    return KL_multinominal()(p, q)


def distance(y0, y1):
    return kl(F.softmax(y0), F.softmax(y1))


def vat(model, distance, x, xi=10, eps=1.0, Ip=1):
    xp = cuda.cupy
    y = model.predict(x)
    y.unchain_backward()

    # calc adversarial direction
    d = xp.random.normal(size=x.shape, dtype=numpy.float32)
    sum_axis = [i for i in range(d.ndim) if i]
    shape = (x.shape[0],) + (1,) * (d.ndim - 1)
    d = d / xp.sqrt(xp.sum(d ** 2, axis=sum_axis)).reshape((shape))
    for ip in range(Ip):
        d_var = Variable(d.astype(numpy.float32))
        y2 = model.predict(x + xi * d_var)
        kl_loss = distance(y, y2)
        kl_loss.backward()
        d = d_var.grad
        d = d / xp.sqrt(xp.sum(d ** 2, axis=sum_axis)).reshape((shape))
    d_var = Variable(d.astype(numpy.float32))

    # calc regularization
    y2 = model.predict(x + eps * d_var)
    return distance(y, y2)

 

classの変更

まず、VAT学習ができるクラスを用意します。

class VATLossClassifier(chainer.Chain):

    def __call__(self, x, t=None):
        if t is not None:
            h = self.predict(x)
            loss = F.softmax_cross_entropy(h, t)
            chainer.report({'loss': loss, 'accuracy': F.accuracy(h, t)}, self)
            return loss
        else:
            return vat(self, distance, x, self.eps)

alexnetにVATLossClassifierを継承させます。実際にはモデルの形に依存せずVATを適用することができます。

class Alex(VATLossClassifier):

    """Single-GPU AlexNet without partition toward the channel axis."""

    insize = 227

    def __init__(self):
        super(Alex, self).__init__()
        with self.init_scope():
            self.conv1 = L.Convolution2D(None,  96, 11, stride=4)
            self.conv2 = L.Convolution2D(None, 256,  5, pad=2)
            self.conv3 = L.Convolution2D(None, 384,  3, pad=1)
            self.conv4 = L.Convolution2D(None, 384,  3, pad=1)
            self.conv5 = L.Convolution2D(None, 256,  3, pad=1)
            self.fc6 = L.Linear(None, 4096)
            self.fc7 = L.Linear(None, 4096)
            self.fc8 = L.Linear(None, 1000)

    def __call__(self, x, t):
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 3, stride=2)
        h = F.dropout(F.relu(self.fc6(h)))
        h = F.dropout(F.relu(self.fc7(h)))
        h = self.fc8(h)
        return h

 

alexnetを継承して、ラベルがないときにもlossが吐けるようにします。

trainerを使う場合、updaterを変更して、VAT学習に対応させます。

class VATUpdater(updater.StandardUpdater):

    def update(self):
        self.update_core()
        self.update_vat()
        self.iteration += 1

    def update_vat(self):
        batch = self._iterators['main'].next()
        in_arrays = self.converter(batch, self.device)

        optimizer = self._optimizers['main']
        loss_func = optimizer.target

        optimizer.zero_grads()
        optimizer.update(loss_func, in_arrays[0])
        optimizer.zero_grads()

 

以上で、準備はおしまい。

後はtrainerにVATUpdaterを食わせて通常通り、trainer.run()するだけです。

一部のデータのみラベルが存在する場合、__init__を編集して、2つのデータセットに対して別々のイテレーターを設定することで、準教師学習を実行できます。