양방향 순환 신경망
도입
순환신경망(RNN)은 시계열과 같이 순서가 있는 데이터 시퀀스를 다루기 위해 고안되었다. 인코더가 입력벡터 $\mathbf{x}_{i}$와 직전상태의 은닉벡터$\mathbf{h}_{i-1}$를 은닉벡터 $\mathbf{h}_{i}$로 인코딩하고, 디코더가 은닉벡터 $\mathbf{h}_{i}$를 출력벡터 $\mathbf{y}_{i}$로 디코딩하는 식으로 작동한다. 즉 $\mathbf{y}_{i}$는 $t \le i$인 모든 $t$에 대해서만 입력벡터와 은닉벡터를 참조한다. 즉 어떤 함수 $f$에 대해서 아래와 같이 표현되는 식이다.
$$ \begin{align*} \mathbf{y}_{1} &= f(\mathbf{x}_{1}, \mathbf{h}_{1}) \\ \mathbf{y}_{2} &= f(\mathbf{x}_{2}, \mathbf{h}_{2}, \mathbf{x}_{1}, \mathbf{h}_{1}) \\ &\vdots \\ \mathbf{y}_{t} &= f(\mathbf{x}_{t}, \mathbf{h}_{t}, \dots, \mathbf{x}_{1}, \mathbf{h}_{1}) \\ \end{align*} $$
그런데 어떤 상황에서는 $\mathbf{y}_{t}$가 아직 입력으로 받지 않은 벡터 $\mathbf{x}_{t+s}$를 참조해야 할 수도 있다. 예를 들어 영어 문장 "I bought this book"을 한국어로 번역하는 상황을 생각해보자. 이 문장을 토큰화하면 아래와 같은 입력벡터 수열이 된다.
$$ (\mathbf{x}_{1}, \mathbf{x}_{2}, \mathbf{x}_{3}, \mathbf{x}_{4}) = (\text{I}, \text{bought}, \text{this}, \text{book}) $$
이 문장의 자연스러운 번역은 "나는 이 책을 샀다"이며, 마찬가지로 토큰화하면 아래와 같은 출력열이 된다.
$$ (\mathbf{y}_{1}, \mathbf{y}_{2}, \mathbf{y}_{3}, \mathbf{y}_{4}) = (\text{나는}, \text{이}, \text{책을}, \text{샀다}) $$
그런데 세 번째 출력 토큰 $\mathbf{y}_{3} = \text{책을}$은 네 번째 입력 토큰 $\mathbf{x}_{4} = \text{book}$에 대응하므로, 이를 올바르게 내놓으려면 아직 읽지 않은 미래의 입력 $\mathbf{x}_{4}$를 참조해야 한다. 그러나 앞서 보았듯 RNN의 은닉상태 $\mathbf{h}_{t}$는 현재까지 주어진 입력 $\mathbf{x}_{1}, \dots, \mathbf{x}_{t}$에만 의존하므로 이런 참조가 구조적으로 불가능하다. 즉 이러한 구조적 한계로 인해 성능 저하를 피할 수 없다. 이 한계를 극복하기 위해 제안된 것이 양방향 순환 신경망으로, 입력벡터 수열을 정방향과 역방향 양쪽으로 훑어 각 시점의 출력이 과거뿐 아니라 미래의 입력까지 참조할 수 있도록 만든 신경망이다.
정의1 2
활성화함수 $\sigma$를 입력 벡터의 성분별로 적용시키는 함수를 $\overline{\sigma}$라고 하자. 즉, 스칼라 함수 $\sigma : \mathbb{R} \to \mathbb{R}$에 대해서 다음과 같이 정의된 벡터 함수이다.
$$ \overline{\sigma}(\mathbf{z}) = \begin{bmatrix} \sigma(z_{1}) \\ \sigma(z_{2}) \\ \vdots \\ \sigma(z_{m}) \end{bmatrix} \qquad \text{where } \mathbf{z} = \begin{bmatrix} z_{1} \\ z_{2} \\ \vdots \\ z_{m} \end{bmatrix} $$
입력벡터들의 수열 $(\mathbf{x}_{1}, \mathbf{x}_{2}, \dots, \mathbf{x}_{T})$ $(\mathbf{x}_{t} \in \mathbb{R}^{n})$가 주어졌다고 하자. $\mathbf{W}^{f}, \mathbf{W}^{b} \in M^{m \times m}$, $\mathbf{U}^{f}, \mathbf{U}^{b} \in M^{m \times n}$을 가중치, $\mathbf{b}^{f}, \mathbf{b}^{b} \in \mathbb{R}^{m}$를 바이어스라고 하자. 순방향forward 은닉상태 $\mathbf{h}_{t}^{f} \in \mathbb{R}^{m}$와 역방향backward 은닉상태 $\mathbf{h}_{t}^{b} \in \mathbb{R}^{m}$를 다음 두 점화식으로 정의한다.
$$ \begin{align*} \mathbf{h}_{t}^{f} &= \overline{\sigma}\left( \mathbf{W}^{f} \mathbf{h}_{t-1}^{f} + \mathbf{U}^{f} \mathbf{x}_{t} + \mathbf{b}^{f} \right), \qquad \mathbf{h}_{0}^{f} = \mathbf{0} \\ \mathbf{h}_{t}^{b} &= \overline{\sigma}\left( \mathbf{W}^{b} \mathbf{h}_{t+1}^{b} + \mathbf{U}^{b} \mathbf{x}_{t} + \mathbf{b}^{b} \right), \qquad \mathbf{h}_{T+1}^{b} = \mathbf{0} \end{align*} $$
순방향 은닉상태 $\mathbf{h}_{t}^{f}$는 $t = 1$에서 $T$로 시점을 늘려가며 계산하고, 역방향 은닉상태 $\mathbf{h}_{t}^{b}$는 $t = T$에서 $1$로 시점을 줄여가며 계산한다.
각 시점 $t$의 출력 $\mathbf{y}_{t} \in \mathbb{R}^{p}$는 두 방향의 은닉상태를 각각 완전연결층에 통과시켜 더한 것이다. $\mathbf{V}^{f}, \mathbf{V}^{b} \in M^{p \times m}$을 가중치, $\mathbf{c} \in \mathbb{R}^{p}$를 바이어스라 하면, 출력을 다음과 같이 정의하는 신경망을 양방향 순환 신경망bidirectional recurrent neural network, BRNN이라 한다.
$$ \mathbf{y}_{t} = \mathbf{V}^{f} \mathbf{h}_{t}^{f} + \mathbf{V}^{b} \mathbf{h}_{t}^{b} + \mathbf{c} $$

- 정의에서 제시한 출력식 $\mathbf{y}_{t} = \mathbf{V}^{f} \mathbf{h}_{t}^{f} + \mathbf{V}^{b} \mathbf{h}_{t}^{b} + \mathbf{c}$는 두 방향의 은닉상태를 결합하는 여러 방식 가운데 한 가지 예시일 뿐이다. 이를테면 분류 문제에서는 출력이 확률로 해석될 수 있도록 소프트맥스를 씌우거나, 회귀 문제에서는 항등함수나 비선형 활성화함수를 통과시킬 수도 있다. 결국 두 방향의 은닉상태를 어떻게 결합해 출력을 내는가 하는 것은 분야/용도/구현 선택의 문제이다. 본 정의의 선형식은 그중 가장 단순한 대표형이라고 이해하면 된다.
설명
쉽게 말해서 두개의 RNN에 각각 벡터 수열 $\left\{ \mathbf{x}_{1}, \mathbf{x}_{2}, \dots, \mathbf{x}_{T} \right\}$와 $\left\{ \mathbf{x}_{T}, \mathbf{x}_{T-1}, \dots, \mathbf{x}_{1} \right\}$을 통과시켜 얻은 은닉상태를 예측에 사용하는 것이다.
기본형 순환신경망에서 시점 $t$의 출력 $\mathbf{y}_{t}$는 은닉상태 $\mathbf{h}_{t}$를 통해 현재까지의 입력 $\mathbf{x}_{1}, \dots, \mathbf{x}_{t}$에만 의존한다. 반면 양방향 순환 신경망은 서로 독립인 두 개의 순환신경망을 두어, 하나는 입력벡터 수열을 $t$가 증가하는 방향$(t = 1 \to T)$으로 정보를 압축하고, 다른 하나는 $t$가 감소하는 방향$(t = T \to 1)$으로 정보를 압축한다. 전자가 만들어내는 은닉상태가 순방향 은닉상태 $\mathbf{h}_{t}^{f}$이고, 후자가 만들어내는 은닉상태가 역방향 은닉상태 $\mathbf{h}_{t}^{b}$이다.
이때 $\mathbf{h}_{t}^{f}$는 과거의 입력 $\mathbf{x}_{1}, \dots, \mathbf{x}_{t}$를, $\mathbf{h}_{t}^{b}$는 미래의 입력 $\mathbf{x}_{t}, \dots, \mathbf{x}_{T}$를 각각 요약해 담는다. 출력 $\mathbf{y}_{t}$가 이 둘을 함께 참조하므로, 결국 각 시점의 출력은 입력벡터 수열 전체를 보고 계산되는 셈이다. 도입에서 본 번역 예시처럼 $\mathbf{y}_{t}$가 아직 읽지 않은 뒤쪽 토큰 $\mathbf{x}_{t+s}$를 참조해야 하는 상황에서도, RNN과 달리 역방향 은닉상태가 그 정보를 압축해서 전달할 수 있다.
RNN의 일부 단점을 극복하긴 했지만, BRNN만의 단점도 여전히 존재한다. 두 방향의 파라미터를 따로 두므로 학습해야 할 파라미터의 수가 대략 두 배가 되고, 무엇보다 역방향 은닉상태를 계산하려면 입력벡터 수열 전체가 미리 주어져 있어야한다. 따라서 양방향 순환 신경망은 입력을 실시간으로 받아 곧바로 출력을 내야 하는 문제나, 앞의 출력을 보고 다음 입력을 생성하는 자기회귀적 생성에는 쓸 수 없다. 반대로 기계 번역의 인코더, 품사 태깅, 개체명 인식, 음성 인식처럼 입력벡터 수열 전체를 한꺼번에 얻을 수 있는 과제에서는 양방향 구조가 이점을 발휘할 수 있다.

저희들의 저서 「줄리아 프로그래밍」이 2024 세종도서 학술부문에 선정되었습니다!

