Gated RNN

NLP 글 목록

한빛미디어의 <밑바닥부터 시작하는 딥러닝 2>를 요약 정리한 글이다.

기본 RNN은 시퀀스 데이터를 순서대로 처리할 수 있지만, 긴 시계열 데이터에서 멀리 떨어진 정보 사이의 의존 관계를 학습하기 어렵다.

이 문제를 장기 의존성(long-term dependency) 문제라고 한다.

LSTM(Long Short-Term Memory)은 이런 문제를 완화하기 위해 게이트와 기억 셀을 추가한 RNN 계열 모델이다.

기본 RNN의 한계

기본 RNN의 은닉 상태는 다음과 같이 갱신된다.

ht=tanh(ht1Wh+xtWx+b)\mathbf{h}_t = \tanh( \mathbf{h}_{t-1}\mathbf{W}_h + \mathbf{x}_t\mathbf{W}_x + \mathbf{b} )

현재 은닉 상태 ht\mathbf{h}_t는 이전 은닉 상태 ht1\mathbf{h}_{t-1}에 크게 의존한다.

따라서 시퀀스가 길어질수록 과거 정보는 계속 압축되어 전달된다.

이 과정에서 중요한 정보가 희석될 수 있고, 역전파 과정에서는 기울기 소실 또는 기울기 폭발이 발생할 수 있다.

장기 의존 학습이 어려운 이유

RNN은 BPTT(BackPropagation Through Time)를 통해 학습한다.

시간 tt에서 먼 과거 시점 kk까지 기울기를 전달한다고 하면, 기울기에는 여러 시점의 행렬곱과 활성화 함수 도함수가 반복적으로 포함된다.

개념적으로는 다음과 같은 형태가 된다.

Lhk=i=ktWhTf(hi)\frac{\partial L}{\partial \mathbf{h}_k} = \prod_{i=k}^{t} \mathbf{W}_h^T f'(\mathbf{h}_i)

이 곱이 반복되면서 값이 점점 작아지면 기울기 소실이 발생하고, 반대로 너무 커지면 기울기 폭발이 발생한다.

결과적으로 먼 과거의 정보가 현재 학습에 거의 영향을 주지 못하거나, 학습이 불안정해진다.

RNN의 시간 방향 기울기 전파

RNN의 역전파에서는 기울기가 시간 방향으로 반복해서 전달된다.

tanh가 기울기에 미치는 영향

RNN의 은닉 상태 계산에는 보통 tanh\tanh가 사용된다.

y=tanh(x)y = \tanh(x)

tanh\tanh의 미분은 다음과 같다.

yx=1y2\frac{\partial y}{\partial x} = 1 - y^2

tanh와 tanh 미분

tanh의 도함수는 입력이 0에서 멀어질수록 작아진다.

tanh\tanh의 출력 yy1-1 또는 11에 가까워질수록 1y21-y^2는 0에 가까워진다.

따라서 역전파에서 기울기가 tanh\tanh 노드를 여러 번 통과하면 기울기가 점점 작아질 수 있다.

이것이 RNN에서 기울기 소실이 발생하는 원인 중 하나이다.

MatMul이 기울기에 미치는 영향

RNN의 역전파에서 행렬곱 노드는 다음 형태의 기울기 전달을 만든다.

dhWhT\mathbf{dh}\mathbf{W}_h^T

이 연산이 시계열 길이만큼 반복된다.

초기화된 Wh\mathbf{W}_h의 특성에 따라 기울기는 폭발하거나 소실될 수 있다.

기울기 폭발 실험

가중치 행렬의 영향으로 기울기 norm이 지수적으로 커질 수 있다.

기울기 소실 실험

반대로 가중치 행렬의 영향으로 기울기 norm이 지수적으로 작아질 수도 있다.

행렬 Wh\mathbf{W}_h를 여러 번 곱하는 것이 핵심 원인이다.

스칼라에서는 절댓값이 1보다 크면 반복 곱으로 값이 커지고, 1보다 작으면 작아진다.

행렬에서는 특잇값, 특히 가장 큰 특잇값의 크기가 기울기 크기 변화에 큰 영향을 준다.

기울기 폭발 대책: Gradient Clipping

기울기 폭발은 gradient clipping으로 완화할 수 있다.

Gradient clipping은 전체 gradient의 norm이 특정 한계값을 넘으면, 방향은 유지하되 크기만 줄이는 방법이다.

if g^threshold:\mathrm{if} \ \lVert \hat{\mathbf{g}} \rVert \geq \mathrm{threshold}: g^=thresholdg^g^\hat{\mathbf{g}} = \frac{\mathrm{threshold}} {\lVert \hat{\mathbf{g}} \rVert} \hat{\mathbf{g}}

여기서 g^\hat{\mathbf{g}}는 신경망에서 사용하는 모든 파라미터의 gradient를 모은 벡터이다.

만약 전체 gradient의 L2 norm이 한계값보다 크면, 벡터의 방향은 보존하고 길이만 threshold 수준으로 줄인다.

def clip_grads(grads, max_norm):
    total_norm = 0
    for grad in grads:
        total_norm += np.sum(grad ** 2)
    total_norm = np.sqrt(total_norm)

    rate = max_norm / (total_norm + 1e-6)
    if rate < 1:
        for grad in grads:
            grad *= rate

Gradient clipping은 기울기 폭발에는 효과가 있지만, 기울기 소실 자체를 해결하는 구조적 방법은 아니다.

장기 의존성 문제를 더 직접적으로 완화하기 위해 LSTM 같은 Gated RNN을 사용한다.

LSTM

LSTM은 RNN에 기억 셀(memory cell)과 게이트를 추가한 구조이다.

기본 RNN은 은닉 상태 h\mathbf{h}만 다음 시점으로 전달한다.

반면 LSTM은 은닉 상태 h\mathbf{h}와 기억 셀 c\mathbf{c}를 함께 사용한다.

RNN과 LSTM 인터페이스 비교

LSTM은 은닉 상태 h 외에 기억 셀 c를 별도로 가진다.

기억 셀 c\mathbf{c}는 LSTM 내부에서만 전달되는 상태이다.

외부 계층으로 직접 출력되지는 않지만, 다음 시점의 LSTM 계산에 계속 사용된다.

은닉 상태 ht\mathbf{h}_t는 일반 RNN처럼 외부 계층으로 출력되고, 다음 시점으로도 전달된다.

LSTM 전체 구조

LSTM은 기억 셀을 중심으로 여러 게이트를 통해 정보를 저장하거나 버린다.

LSTM에서는 이전 기억 셀 ct1\mathbf{c}_{t-1}, 이전 은닉 상태 ht1\mathbf{h}_{t-1}, 현재 입력 xt\mathbf{x}_t를 이용해 현재 기억 셀 ct\mathbf{c}_t와 은닉 상태 ht\mathbf{h}_t를 계산한다.

기억 셀은 과거부터 현재까지 필요한 정보를 저장하는 경로이다.

은닉 상태는 기억 셀의 정보를 외부로 어느 정도 내보낼지 결정한 결과이다.

게이트

게이트는 데이터의 흐름을 제어하는 장치이다.

각 게이트는 0과 1 사이의 값을 출력한다.

값이 0에 가까우면 정보를 거의 통과시키지 않고, 1에 가까우면 정보를 거의 그대로 통과시킨다.

게이트의 열림 정도는 학습 데이터로부터 자동으로 학습된다.

게이트 출력에는 sigmoid 함수가 사용된다.

σ(x)=11+exp(x)\sigma(x) = \frac{1}{1+\exp(-x)}

sigmoid는 출력 범위가 00에서 11 사이이므로, 정보를 얼마나 통과시킬지 조절하는 게이트에 적합하다.

반면 실질적인 정보 후보값에는 보통 tanh\tanh가 사용된다.

Output Gate

Output gate는 현재 기억 셀 ct\mathbf{c}_t의 정보를 은닉 상태 ht\mathbf{h}_t로 얼마나 내보낼지 결정한다.

LSTM output gate

Output gate는 기억 셀의 정보를 은닉 상태로 얼마나 출력할지 조절한다.

Output gate의 열림 상태는 다음과 같이 계산된다.

ot=σ(xtWx(o)+ht1Wh(o)+b(o))\mathbf{o}_t = \sigma( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{o})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{o})} + \mathbf{b}^{(\mathbf{o})} )

은닉 상태는 기억 셀에 tanh\tanh를 적용한 값에 output gate를 원소별로 곱해 계산한다.

ht=ottanh(ct)\mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t)

tanh(ct)\tanh(\mathbf{c}_t)는 기억 셀에 저장된 정보를 1-1에서 11 사이 값으로 변환한 것이다.

여기에 ot\mathbf{o}_t를 곱해 각 원소를 얼마나 외부로 내보낼지 조절한다.

Forget Gate

Forget gate는 이전 기억 셀 ct1\mathbf{c}_{t-1}에서 어떤 정보를 잊을지 결정한다.

LSTM forget gate

Forget gate는 이전 기억 셀의 정보를 얼마나 유지할지 조절한다.

Forget gate는 다음과 같이 계산된다.

ft=σ(xtWx(f)+ht1Wh(f)+b(f))\mathbf{f}_t = \sigma( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{f})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{f})} + \mathbf{b}^{(\mathbf{f})} )

이 값을 이전 기억 셀과 원소별로 곱한다.

ftct1\mathbf{f}_t \odot \mathbf{c}_{t-1}

어떤 원소에서 ft\mathbf{f}_t가 0에 가까우면 해당 정보는 거의 사라진다.

반대로 1에 가까우면 해당 정보는 거의 그대로 유지된다.

기억 셀 후보값

기억 셀 후보값 gt\mathbf{g}_t는 현재 입력과 이전 은닉 상태로부터 만들어지는 새로운 정보 후보이다.

LSTM 기억 셀 후보값

기억 셀 후보값은 현재 시점에서 새로 추가될 수 있는 정보이다.

계산식은 다음과 같다.

gt=tanh(xtWx(g)+ht1Wh(g)+b(g))\mathbf{g}_t = \tanh( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{g})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{g})} + \mathbf{b}^{(\mathbf{g})} )

gt\mathbf{g}_t는 새로 저장될 수 있는 정보이다.

하지만 아직 기억 셀에 직접 더해진 것은 아니다.

이 후보값은 input gate를 통해 얼마나 반영할지 조절된다.

Input Gate

Input gate는 기억 셀 후보값 gt\mathbf{g}_t를 현재 기억 셀에 얼마나 반영할지 결정한다.

LSTM input gate

Input gate는 새로운 정보 후보를 기억 셀에 얼마나 추가할지 조절한다.

Input gate는 다음과 같이 계산된다.

it=σ(xtWx(i)+ht1Wh(i)+b(i))\mathbf{i}_t = \sigma( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{i})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{i})} + \mathbf{b}^{(\mathbf{i})} )

새로운 기억 셀은 forget gate로 유지된 이전 기억과 input gate로 선택된 새로운 기억을 더해 계산한다.

ct=ftct1+itgt\mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \mathbf{g}_t

즉, LSTM의 기억 셀 업데이트는 다음 두 부분으로 나뉜다.

  1. 이전 기억 중 유지할 부분: ftct1\mathbf{f}_t \odot \mathbf{c}_{t-1}
  2. 새로 추가할 부분: itgt\mathbf{i}_t \odot \mathbf{g}_t

LSTM의 전체 수식

LSTM의 한 시점 계산은 다음처럼 정리된다.

ft=σ(xtWx(f)+ht1Wh(f)+b(f))gt=tanh(xtWx(g)+ht1Wh(g)+b(g))it=σ(xtWx(i)+ht1Wh(i)+b(i))ot=σ(xtWx(o)+ht1Wh(o)+b(o))ct=ftct1+itgtht=ottanh(ct)\begin{gather} \mathbf{f}_t = \sigma( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{f})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{f})} + \mathbf{b}^{(\mathbf{f})} ) \\[4pt] \mathbf{g}_t = \tanh( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{g})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{g})} + \mathbf{b}^{(\mathbf{g})} ) \\[4pt] \mathbf{i}_t = \sigma( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{i})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{i})} + \mathbf{b}^{(\mathbf{i})} ) \\[4pt] \mathbf{o}_t = \sigma( \mathbf{x}_t\mathbf{W}_x^{(\mathbf{o})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{o})} + \mathbf{b}^{(\mathbf{o})} ) \\[8pt] \mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \mathbf{g}_t \\[8pt] \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t) \end{gather}

Forget, input, output gate에는 sigmoid를 사용하고, 새 정보 후보값 gt\mathbf{g}_t에는 tanh를 사용한다.

LSTM이 기울기 소실을 완화하는 이유

LSTM의 기억 셀 경로에서는 역전파가 주로 덧셈과 원소별 곱을 통과한다.

LSTM 기억 셀의 역전파 흐름

LSTM의 기억 셀 경로는 기울기가 비교적 안정적으로 흐를 수 있는 구조를 만든다.

기본 RNN에서는 같은 가중치 행렬 Wh\mathbf{W}_h와 활성화 함수 도함수가 시간 방향으로 반복 곱해진다.

반면 LSTM의 기억 셀 경로에서는 다음 형태로 gradient가 전달된다.

Lct1=Lctft\frac{\partial L}{\partial \mathbf{c}_{t-1}} = \frac{\partial L}{\partial \mathbf{c}_t} \odot \mathbf{f}_t

여기서 ft\mathbf{f}_t는 forget gate의 출력이다.

잊어야 할 정보라면 ft\mathbf{f}_t가 0에 가까워지고, 해당 방향의 gradient도 약해진다.

반대로 기억해야 할 정보라면 ft\mathbf{f}_t가 1에 가까워져 gradient가 거의 그대로 과거 방향으로 전달된다.

즉, LSTM은 학습을 통해 어떤 정보를 잊고 어떤 정보를 오래 유지할지 조절한다.

이것이 기본 RNN보다 장기 의존 관계를 잘 학습할 수 있는 이유이다.

LSTM 구현: 아핀 변환 합치기

LSTM에는 네 종류의 아핀 변환이 존재한다.

ft, gt, it, ot\mathbf{f}_t,\ \mathbf{g}_t,\ \mathbf{i}_t,\ \mathbf{o}_t

각 게이트를 따로 계산하면 다음 네 개의 아핀 변환을 각각 수행해야 한다.

xtWx(f)+ht1Wh(f)+b(f)xtWx(g)+ht1Wh(g)+b(g)xtWx(i)+ht1Wh(i)+b(i)xtWx(o)+ht1Wh(o)+b(o)\begin{align} &\mathbf{x}_t\mathbf{W}_x^{(\mathbf{f})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{f})} + \mathbf{b}^{(\mathbf{f})} \\[3pt] &\mathbf{x}_t\mathbf{W}_x^{(\mathbf{g})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{g})} + \mathbf{b}^{(\mathbf{g})} \\[3pt] &\mathbf{x}_t\mathbf{W}_x^{(\mathbf{i})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{i})} + \mathbf{b}^{(\mathbf{i})} \\[3pt] &\mathbf{x}_t\mathbf{W}_x^{(\mathbf{o})} + \mathbf{h}_{t-1}\mathbf{W}_h^{(\mathbf{o})} + \mathbf{b}^{(\mathbf{o})} \end{align}

하지만 실제 구현에서는 네 가중치를 열 방향으로 이어붙여 한 번의 큰 아핀 변환으로 계산한다.

A=xtWx+ht1Wh+b\mathbf{A} = \mathbf{x}_t\mathbf{W}_x + \mathbf{h}_{t-1}\mathbf{W}_h + \mathbf{b}

이때 A\mathbf{A}의 형상은 N×4HN \times 4H이다.

LSTM 아핀 변환 결합

네 게이트의 아핀 변환을 열 방향으로 이어붙여 한 번에 계산할 수 있다.

배치 수 NN은 그대로 유지되고, 열 방향 크기만 4H4H가 된다.

이후 AA를 네 부분으로 slice해서 각각 forget, candidate, input, output에 사용한다.

LSTM 계산 그래프

큰 아핀 변환 결과를 slice한 뒤 각 게이트에 맞는 활성화 함수를 적용한다.

LSTM 순전파

def forward(self, x, h_prev, c_prev):
    Wx, Wh, b = self.params
    N, H = h_prev.shape

    A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b

    f = A[:, :H]
    g = A[:, H:2*H]
    i = A[:, 2*H:3*H]
    o = A[:, 3*H:]

    f = sigmoid(f)
    g = np.tanh(g)
    i = sigmoid(i)
    o = sigmoid(o)

    c_next = f * c_prev + g * i
    h_next = o * np.tanh(c_next)

    self.cache = (x, h_prev, c_prev, i, f, g, o, c_next)
    return h_next, c_next

아핀 변환의 형상은 다음과 같다.

xt(N×D)Wx(D×4H)+ht1(N×H)Wh(H×4H)=A(N×4H)\mathbf{x}_t(N \times D) \mathbf{W}_x(D \times 4H) + \mathbf{h}_{t-1}(N \times H) \mathbf{W}_h(H \times 4H) = \mathbf{A}(N \times 4H)

Slice Node의 역전파

순전파에서 하나의 행렬 A\mathbf{A}를 네 조각으로 나누었다면, 역전파에서는 네 조각의 gradient를 다시 결합해야 한다.

Slice node의 역전파

slice의 역전파에서는 나뉘었던 gradient를 다시 하나로 이어붙인다.
dA = np.hstack((df, dg, di, do))

즉, forward에서 열 방향으로 나눈 것을 backward에서 다시 열 방향으로 합치는 것이다.

LSTM 역전파

def backward(self, dh_next, dc_next):
    Wx, Wh, b = self.params
    x, h_prev, c_prev, i, f, g, o, c_next = self.cache

    tanh_c_next = np.tanh(c_next)
    ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)

    dc_prev = ds * f

    di = ds * g
    df = ds * c_prev
    do = dh_next * tanh_c_next
    dg = ds * i

    di *= i * (1 - i)
    df *= f * (1 - f)
    do *= o * (1 - o)
    dg *= (1 - g ** 2)

    dA = np.hstack((df, dg, di, do))

    dWh = np.dot(h_prev.T, dA)
    dWx = np.dot(x.T, dA)
    db = dA.sum(axis=0)

    self.grads[0][...] = dWx
    self.grads[1][...] = dWh
    self.grads[2][...] = db

    dx = np.dot(dA, Wx.T)
    dh_prev = np.dot(dA, Wh.T)

    return dx, dh_prev, dc_prev

여기서 핵심은 ds이다.

ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)

dc_next는 다음 시점에서 기억 셀 경로를 통해 넘어온 기울기이다.

그런데 은닉 상태도 기억 셀에 의해 계산된다.

ht=ottanh(ct)\mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t)

따라서 은닉 상태 쪽 gradient dh_next도 기억 셀 ct\mathbf{c}_t에 영향을 준다.

이 두 경로의 gradient를 더한 값이 ds이다.

dc_prev = ds * f

이 식은 기억 셀 gradient가 이전 기억 셀로 전달될 때 forget gate가 계수 역할을 한다는 뜻이다.

TimeLSTM

TimeLSTM은 T개 시점의 LSTM 계산을 한 번에 처리하는 계층이다.

TimeRNN과 마찬가지로 내부에 각 시점의 LSTM 인스턴스를 가지고, 은닉 상태 h\mathbf{h}와 기억 셀 c\mathbf{c}를 인스턴스 변수로 유지한다.

class TimeLSTM:
    def __init__(self, Wx, Wh, b, stateful=False):
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.layers = None

        self.h, self.c = None, None
        self.dh = None
        self.stateful = stateful

stateful=True이면 다음 배치에서도 이전 배치의 마지막 h\mathbf{h}c\mathbf{c}를 유지한다.

Truncated BPTT에서는 역전파는 일정 길이로 끊지만, 순전파의 hidden state와 cell state는 계속 이어가야 하므로 이 설정이 중요하다.

TimeLSTM 순전파

def forward(self, xs):
    Wx, Wh, b = self.params
    N, T, D = xs.shape
    H = Wh.shape[0]

    self.layers = []
    hs = np.empty((N, T, H), dtype='f')

    if not self.stateful or self.h is None:
        self.h = np.zeros((N, H), dtype='f')
    if not self.stateful or self.c is None:
        self.c = np.zeros((N, H), dtype='f')

    for t in range(T):
        layer = LSTM(*self.params)
        self.h, self.c = layer.forward(xs[:, t, :], self.h, self.c)
        hs[:, t, :] = self.h
        self.layers.append(layer)

    return hs

각 시점마다 LSTM 인스턴스를 만들고, 현재 입력과 이전 h\mathbf{h}, c\mathbf{c}를 이용해 다음 상태를 계산한다.

외부로 반환하는 값은 각 시점의 은닉 상태를 모은 hs이다.

기억 셀 c\mathbf{c}는 LSTM 내부 상태로 유지되고, 외부 계층으로 직접 출력되지는 않는다.

TimeLSTM 역전파

def backward(self, dhs):
    Wx, Wh, b = self.params
    N, T, H = dhs.shape
    D = Wx.shape[0]

    dxs = np.empty((N, T, D), dtype='f')
    dh, dc = 0, 0
    grads = [0, 0, 0]

    for t in reversed(range(T)):
        layer = self.layers[t]
        dx, dh, dc = layer.backward(dhs[:, t, :] + dh, dc)
        dxs[:, t, :] = dx

        for i, grad in enumerate(layer.grads):
            grads[i] += grad

    for i, grad in enumerate(grads):
        self.grads[i][...] = grad
    self.dh = dh
    return dxs

역전파는 시간 역순으로 진행된다.

각 시점의 은닉 상태는 외부 출력 방향과 다음 시점 방향으로 분기되므로, 역전파에서는 다음 두 gradient가 더해진다.

dhs[:, t, :] + dh

그리고 모든 시점의 LSTM은 같은 가중치를 공유하므로, 각 시점에서 계산된 가중치 gradient도 모두 더한다.

grads[i] += grad

이 구조는 TimeRNN과 같다.

다만 LSTM은 은닉 상태 h\mathbf{h}뿐 아니라 기억 셀 c\mathbf{c}의 gradient도 함께 전달한다.

RNNLM 추가 개선

LSTM을 사용한 RNNLM은 기본 RNNLM보다 장기 의존성을 더 잘 다룰 수 있다.

여기에 몇 가지 개선을 추가하면 성능을 더 높일 수 있다.

LSTM 다층화

LSTM 계층을 여러 층으로 쌓으면 표현력을 높일 수 있다.

feedforward 신경망에서 계층을 깊게 쌓아 더 복잡한 표현을 학습하는 것과 비슷하다.

언어 모델에서는 여러 LSTM 계층을 쌓아 단어 시퀀스의 더 복잡한 패턴을 학습할 수 있다.

Dropout

드롭아웃은 과대적합을 억제하기 위해 사용한다.

RNN 계열 모델에서는 보통 시간축 방향이 아니라 깊이 방향에 드롭아웃을 삽입한다.

시간축마다 다른 dropout mask를 적용하면 시간이 지날수록 노이즈가 누적될 수 있기 때문이다.

변형 dropout에서는 같은 계층에 속한 시점들이 같은 mask를 공유하게 하여 시간 방향 dropout을 더 안정적으로 사용할 수 있다.

Weight Tying

Weight tying은 Embedding 계층의 가중치와 Affine 계층의 가중치를 공유하는 방법이다.

Weight tying

Embedding 가중치와 출력 Affine 가중치를 전치 관계로 공유할 수 있다.

어휘 수가 VV, 은닉 차원이 HH라고 하자.

Embedding 계층의 가중치 형상은 다음과 같다.

V×HV \times H

Affine 계층의 가중치 형상은 다음과 같다.

H×VH \times V

따라서 Affine 계층의 가중치를 Embedding 가중치의 전치로 둘 수 있다.

Waffine=WembedT\mathbf{W}_{\mathrm{affine}} = \mathbf{W}_{\mathrm{embed}}^T

이렇게 하면 학습해야 할 파라미터 수가 줄고, 입력 단어 표현과 출력 단어 분류에 같은 의미 공간을 사용할 수 있다.

개선된 RNNLM의 구성

개선된 RNNLM은 보통 다음 요소를 포함한다.

  • LSTM 다층화
  • 깊이 방향 dropout
  • Embedding과 Affine 계층의 weight tying
  • 검증 데이터 perplexity 기반 학습률 조절

학습 중 검증 데이터의 perplexity가 더 이상 좋아지지 않으면 학습률을 줄인다.

예를 들어 validation perplexity가 이전보다 낮아지지 않으면 learning rate에 0.250.25를 곱해 더 작은 폭으로 학습하도록 조정할 수 있다.

PTB 데이터셋 모델별 perplexity 비교

개선된 RNNLM은 기본 RNNLM보다 낮은 perplexity를 목표로 한다.

정리

기본 RNN은 긴 시퀀스에서 기울기 소실과 폭발이 발생하기 쉽고, 장기 의존 관계를 학습하기 어렵다.

Gradient clipping은 기울기 폭발을 완화하지만, 장기 기억을 구조적으로 해결하는 방법은 아니다.

LSTM은 기억 셀과 게이트를 사용해 어떤 정보를 잊고, 어떤 정보를 새로 저장하고, 어떤 정보를 출력할지 학습한다.

Forget gate는 이전 기억을 얼마나 유지할지 결정하고, input gate는 새로운 기억 후보를 얼마나 반영할지 결정하며, output gate는 기억 셀의 정보를 은닉 상태로 얼마나 내보낼지 결정한다.

이 구조 덕분에 LSTM은 기본 RNN보다 긴 시간에 걸친 의존 관계를 더 안정적으로 학습할 수 있다.