Dueling Network 구현 (2)

환경



GPU GTX1070
우분투 14.04
chainer 1.14.0


소개



Deuling Network의 코드를 구현하는 두 번째. 전회는 chainer/functions/connection/bilinear.py를 확인했다.
ぃ tp // 코 m / 설마 46 / ms / 3에 c187 아 5 f30fb416 예
이번에는 그것을 근거로 forward()와 backward()를 변경해 나간다.

forward() 변경



우선 순전파의 계산을 변경한다. 개요는 다음과 같습니다.

Numpy의 브로드캐스트라는 구조를 사용하면 크기가 다른 행렬 사이에서도 일정한 법칙 아래에 더할 수 있다. 이것을 사용하고 있다.

우선 브로드캐스트로 e1과 e2를 더한다. 이제 e2의 각 열에 e1이 더해집니다. 한편, e2를 열 방향으로 평균화한 것을 E2로서 생성한다. 다시 방송을 사용하여 먼저 더한 것에서 E2를 당기면 y가 구해진다.

forward() 함수내는 이하와 같이 수정한다.
    def forward(self, inputs):
        e1 = array.as_mat(inputs[0])
        e2 = array.as_mat(inputs[1])
        W = inputs[2]

        #modified algorithm
        y = e1 + e2 - e2.sum(1).reshape(len(e2), 1) / len(e2[0])
        return y,

e1의 backward 구하기



우선 V측으로의 δ인 ge1을 구한다. 아래 그림과 같이 될 것이다. (계산이 잘못되면 알려주세요)

gy를 sum()으로 열 방향으로 더하면 좋을 것이다.
        ge1 = cupy.sum(gy, axis=1).reshape(len(gy), 1).astype(dtype=gy.dtype, copy=False)

e2의 backward 구하기



다음에 A측의 δ인 e2를 구한다. 아래 그림과 같이 될 것이다.

궁리가 필요한 것은 (gy1+gy2+gy3) 부분일까. 여기는 cupy.sum() 함수로 값을 더한 다음 cupy.tile()로 확장합니다. 따라서 forward () 코드는 다음과 같이 다시 작성됩니다.
    def backward(self, inputs, grad_outputs):
        e1 = array.as_mat(inputs[0])
        e2 = array.as_mat(inputs[1])
        W = inputs[2]
        gy = grad_outputs[0]
        '''
        xp = cuda.get_array_module(*inputs)
        if xp is numpy:
            gW = numpy.einsum('ij,ik,il->jkl', e1, e2, gy)
            ge1 = numpy.einsum('ik,jkl,il->ij', e2, W, gy)
            ge2 = numpy.einsum('ij,jkl,il->ik', e1, W, gy)
        else:
            kern = cuda.reduce('T in0, T in1, T in2', 'T out',
                               'in0 * in1 * in2', 'a + b', 'out = a', 0,
                               'bilinear_product')

            e1_b = e1[:, :, None, None]  # ij
            e2_b = e2[:, None, :, None]  # ik
            gy_b = gy[:, None, None, :]  # il
            W_b = W[None, :, :, :]  # jkl

            gW = kern(e1_b, e2_b, gy_b, axis=0)  # 'ij,ik,il->jkl'
            ge1 = kern(e2_b, W_b, gy_b, axis=(2, 3))  # 'ik,jkl,il->ij'
            ge2 = kern(e1_b, W_b, gy_b, axis=(1, 3))  # 'ij,jkl,il->ik'
        '''
        ge1 = cupy.sum(gy, axis=1).reshape(len(gy), 1).astype(dtype=gy.dtype, copy=False)
        gy_sum = cupy.sum(gy, axis=1).reshape(len(gy), 1).astype(dtype=gy.dtype, copy=False)
        gy_tile = cupy.tile(gy_sum, len(gy[0])).astype(dtype=gy.dtype, copy=False)
        ge2 = (gy - gy_tile / len(gy[0])).astype(dtype=gy.dtype, copy=False)
        gW = cupy.zeros(len(e1[0])*len(e2[0])*len(e2[0])).reshape(len(e1[0]), len(e2[0]), len(e2[0])).astype(dtype=gy.dtype, copy=False)

        ret = ge1.reshape(inputs[0].shape), ge2.reshape(inputs[1].shape), gW
        if len(inputs) == 6:
            V1, V2, b = inputs[3:]
            gV1 = e1.T.dot(gy)
            gV2 = e2.T.dot(gy)
            gb = gy.sum(0)
            ge1 += gy.dot(V1.T)
            ge2 += gy.dot(V2.T)
            ret += gV1, gV2, gb
        return ret

LIS로 성능 검증



LIS ver2의 example 게임에서 성능을 검증했다. LIS ver2에 관해서는 이쪽을 참조되었고.
ぃ tp // 이 m / 설마 46 / ms / 977 5010c1f000dc1d
40만 스텝 정도 학습시킨 결과가 아래의 그래프.

DQN(Mnih,2015)보다 성능이 오르고 있다고 생각하지 마라 ~. 제대로 된 비교는 하지 않았지만.

Atari 2600으로 성능 검증



Atari 2600의 BreakOut에서 성능을 검증했습니다. 결과는 아래의 그래프.

가로축이 episode이고 세로축이 각 episode에서 받은 reward. 확실히 상승하고 있군요.

코드 위치



LIS ver2용의 코드는 이쪽에 들었습니다.
htps : // 기주 b. 코 m / 마사타카 46 / 즈에 ぃ g

Atari2600용의 코드는 이쪽에 들었습니다.
htps : // 기주 b. 코 m / 마사타카 46 / 즈에 ぃ g 네토 rk

좋은 웹페이지 즐겨찾기