자연어처리에서의 어텐션 메커니즘
개요
어텐션 메커니즘attention mechanism은 쿼리, 키, 밸류라는 세 벡터 시퀀스로부터, 각각의 쿼리가 어느 키에 얼마나 주목할지를 점수로 재고 그 점수를 확률로 바꾼 뒤, 그 확률을 가중치로 삼아 밸류들을 섞어 내보내는 연산이다. 좀 더 구체적으로는, 먼저 쿼리와 키가 서로 얼마나 잘 맞는지를 나타내는 스코어score를 계산하고, 그 스코어에 $\operatorname{softmax}$를 취해 합이 $1$인 가중치로 바꾼 다음, 이 가중치대로 밸류들을 볼록결합convex combination하여 가중평균을 낸다.
자연어처리에서 어텐션 메커니즘이 어떻게 작동하는지 구체적인 예시로 알아보자. 이 문서에서는 어텐션 메커니즘 그 자체, 즉 키와 쿼리, 밸류가 어떤 의미를 갖고 어떻게 계산되는지 설명하는 데에 집중한다. 트랜스포머의 정의나 논문 리뷰는 아래를 참고하라.
- 🔒(26/08/19)트랜스포머
- 🔒(26/08/27)트랜스포머 논문리뷰
그리고 수학에서는 열벡터가 기본이므로 아래에서는 열벡터를 기준으로하는 표기법이 사용되었다.
정의
주어진 행렬 $\mathbf{X} = \begin{bmatrix} \mathbf{x}_{1} & \cdots & \mathbf{x}_{N}\end{bmatrix}$에 대해서, 각 열벡터에 소프트맥스 $\operatorname{softmax}$를 취해서 각 열의 합이 $1$이 되게 하는 함수를 $\operatorname{Softmax}$라 정의하자.
$$ \operatorname{Softmax}(\mathbf{X}) := \begin{bmatrix} \underset{\vert}{\overset{\vert}{\operatorname{softmax}(\mathbf{x}_{1})}} & \cdots & \underset{\vert}{\overset{\vert}{\operatorname{softmax}(\mathbf{x}_{N})}} \end{bmatrix} $$
$\mathbf{Q}$, $\mathbf{K}$, $\mathbf{V}$를 각각 쿼리 행렬, 키 행렬, 밸류 행렬이라 하자. 키 행렬과 쿼리 행렬로부터 스코어를 만드는 함수를 $f(\mathbf{K}, \mathbf{Q})$라 하면, 어텐션은 다음과 같이 정의된다.
$$ \operatorname{Attention}(\mathbf{Q},\mathbf{K},\mathbf{V}) := \mathbf{V} \operatorname{Softmax}\left(f(\mathbf{K},\mathbf{Q})\right) \tag{1} $$
- $\mathbf{Q} \in M^{d_{k} \times n}$
- $\mathbf{K} \in M^{d_{k} \times m}$
- $\mathbf{V} \in M^{d_{v} \times m}$
밸류 행렬 $\mathbf{V}$를 왼쪽에서 행렬곱하는 것이 곧 각 쿼리에 대해 밸류들을 그 가중치대로 가중평균하는 연산에 해당한다.
인코더-디코더 어텐션
자연어처리, 그 중에서도 기계번역을 예로 어텐션의 작동 방식을 구체적으로 알아보자. 우선 이 파트에서는 간단한 인코더-디코더 구조의 자기회귀모델을 가정한다.
키와 밸류
한국어 문장 "사과는 맛있다"를 영어로 번역한다고 하자. 기대하는 예측은 "Apples are delicious"이다. 한국어 문장을 토큰화하면 ["사과", "는", "맛있다"]가 된다. 이들을 각각 임베딩한 벡터를 $\mathbf{x}_{i}$라 하자. 이들의 수열이 입력 벡터 시퀀스 $\begin{bmatrix} \mathbf{x}_{1} & \mathbf{x}_{2} & \mathbf{x}_{3} \end{bmatrix}$가 된다. 인코더가 입력 벡터들로 만들어낸 은닉상태를 다음과 같이 나타내자.
$$ \mathbf{H}^{\text{enc}} = \begin{bmatrix} \mathbf{h}_{1}^{\text{enc}} & \mathbf{h}_{2}^{\text{enc}} & \mathbf{h}_{3}^{\text{enc}} \end{bmatrix} $$
여기서 각각의 은닉상태는 대략적으로 원문의 각 위치에 대응하는 문맥 정보를 담는다. 다만 $\mathbf{h}_{i}^{\text{enc}}$는 해당 토큰의 정보만 담는 것이 아니라, 인코더의 구조에 따라 문장 전체의 문맥도 함께 반영할 수 있다.
$$ \mathbf{h}_{1}^{\text{enc}} \leftrightarrow \text{사과}, \quad \mathbf{h}_{2}^{\text{enc}} \leftrightarrow \text{는}, \quad \mathbf{h}_{3}^{\text{enc}} \leftrightarrow \text{맛있다}, \quad $$
학습가능한 파라미터를 $\mathbf{W}^{K}$, $\mathbf{W}^{V}$라고 하면 키 행렬과 밸류 행렬은 적절한 함수 $k$와 $v$에 의해서 다음과 같이 만들어진다.
$$ \begin{align*} \mathbf{K} &= \begin{bmatrix} \mathbf{k}_{1} & \mathbf{k}_{2} & \mathbf{k}_{3} \end{bmatrix} = k(\mathbf{H}^{\text{enc}}, \mathbf{W}^{K}) \\[1em] \mathbf{V} &= \begin{bmatrix} \mathbf{v}_{1} & \mathbf{v}_{2} & \mathbf{v}_{3} \end{bmatrix} = v(\mathbf{H}^{\text{enc}}, \mathbf{W}^{V}) \end{align*} $$
구체적으로 $k$와 $v$가 선형변환이라면 아래와 같이 계산되는 식이다
$$ \begin{align*} \mathbf{K} &= \begin{bmatrix} \mathbf{k}_{1} & \mathbf{k}_{2} & \mathbf{k}_{3} \end{bmatrix} = \mathbf{W}^{K} \begin{bmatrix} \mathbf{h}_{1}^{\text{enc}} & \mathbf{h}_{2}^{\text{enc}} & \mathbf{h}_{3}^{\text{enc}} \end{bmatrix} \\[1em] \mathbf{V} &= \begin{bmatrix} \mathbf{v}_{1} & \mathbf{v}_{2} & \mathbf{v}_{3} \end{bmatrix} = \mathbf{W}^{V} \begin{bmatrix} \mathbf{h}_{1}^{\text{enc}} & \mathbf{h}_{2}^{\text{enc}} & \mathbf{h}_{3}^{\text{enc}} \end{bmatrix} \end{align*} $$
쿼리
키와 밸류가 원문의 정보를 담은 인코더의 출력(은닉상태)으로 만들어진 것과 달리, 쿼리는 번역문을 생성하는 디코더의 은닉상태로부터 만들어진다. 디코더가 번역문 "Apples are delicious"를 왼쪽부터 한 단어씩 생성한다고 가정하자. 디코더 $D$가 $t-1$번째 출력 $\mathbf{y}_{t-1}$와 $t-1$번째 디코더의 은닉상태 $\mathbf{h}_{t-1}^{\text{dec}}$로부터 $t$번째 은닉상태를 출력한다고 하자. $\boldsymbol{\theta}_{D}$는 디코더의 파라미터이다. 여기서는 어텐션 자체의 작동 원리를 단순화해서 설명하기 위해 디코더 은닉상태의 구체적인 갱신 방식은 생략한다. (첫번째 스텝에서 $\mathbf{y}_{0}$는 특수 토큰으로 가정한다.)
$$ \mathbf{h}_{t}^{\text{dec}} = D(\mathbf{y}_{t-1}, \mathbf{h}_{t-1}^{\text{dec}}; \boldsymbol{\theta}_{D}) $$
학습가능한 파라미터를 $\mathbf{W}^{Q}$라 하면, 스텝 $t$의 쿼리는 적절한 함수 $q$에 의해 다음과 같이 만들어진다.
$$ \mathbf{q}_{t} = q(\mathbf{h}_{t}^{\text{dec}}, \mathbf{W}^{Q}) $$
키, 밸류와 마찬가지로 $q$가 선형변환이라면 아래와 같이 계산된다.
$$ \mathbf{q}_{t} = \mathbf{W}^{Q} \mathbf{h}_{t}^{\text{dec}} $$
키와 밸류가 원문 세 토큰에 대해 한꺼번에 세 개씩 만들어진 것과 달리, 쿼리는 디코더가 단어를 하나씩 생성하는 매 스텝마다 하나씩 만들어진다. 즉 스텝 $t$에서 쿼리 행렬은 열이 하나뿐인 $\mathbf{Q} = \begin{bmatrix} \mathbf{q}_{t} \end{bmatrix}$이며, 이 하나의 쿼리가 원문의 세 키 전체를 훑어보고 어디에 주목할지 정하게 된다.
예를 들어 디코더가 "delicious"를 생성하려는 스텝에서는, 그 시점의 쿼리 $\mathbf{q}_{t}$가 원문 토큰 "맛있다"에 대응하는 키 $\mathbf{k}_{3}$와 잘 맞아떨어질 것으로 기대할 수 있다. 이렇게 쿼리와 키가 얼마나 맞는지를 실제로 재는 것이 다음에 볼 스코어 계산이다.
쿼리와 키 사이의 적합도를 계산하는 스코어 함수를 $s$라 하자. 첫 번째 쿼리 $\mathbf{q}_{1}$에 대한 세 스코어는 다음과 같다.
$$ e_{11} = s(\mathbf{k}_{1}, \mathbf{q}_{1}),\quad e_{21} = s(\mathbf{k}_{2}, \mathbf{q}_{1}),\quad e_{31} = s(\mathbf{k}_{3}, \mathbf{q}_{1}) $$
$$ \mathbf{e}_{1} = \begin{bmatrix} e_{11} \\ e_{21} \\ e_{31} \end{bmatrix} = \begin{bmatrix} s(\mathbf{k}_{1}, \mathbf{q}_{1}) \\ s(\mathbf{k}_{2}, \mathbf{q}_{1}) \\ s(\mathbf{k}_{3}, \mathbf{q}_{1}) \end{bmatrix} $$
정의 섹션에서의 표기법에 따르면 이는 다음과 같은 표현이다.
$$ \mathbf{e}_{1} = f(\mathbf{K}, \mathbf{q}_{1}) $$
여기에 소프트맥스를 적용하면 정규화된 가중치가 된다.
$$ \boldsymbol{\alpha}_{1} = \operatorname{softmax} (\mathbf{e}_{1}) = \begin{bmatrix} \alpha_{11} \\ \alpha_{21} \\ \alpha_{31} \end{bmatrix}, \qquad \sum_{i}\alpha_{i1} = 1 $$
예를 들어 출력의 첫번째 스텝에서는 "Apples"가 생성되는게 이상적인데, 잘 학습된 모델이라면 "사과"에 대응하는 첫 번째 키와 쿼리 사이의 스코어가 상대적으로 크게 아래와 같이 나타날 것이다.
$$ \boldsymbol{\alpha}_{1} = \begin{bmatrix} 0.8 \\ 0.1 \\ 0.1 \end{bmatrix} $$
이제 번역문의 첫번째 토큰을 생성하기 위한 첫번째 컨텍스트 벡터 $\mathbf{c}_{1}$는 이 가중치로 밸류를 선형결합한 것이다.
$$ \begin{align*} \mathbf{c}_{1} &= \alpha_{11}\mathbf{v}_{1} + \alpha_{21}\mathbf{v}_{2} + \alpha_{31}\mathbf{v}_{3} \\ &= \begin{bmatrix} \mathbf{v}_{1} & \mathbf{v}_{2} & \mathbf{v}_{3} \end{bmatrix} \begin{bmatrix} \alpha_{11} \\ \alpha_{21} \\ \alpha_{31} \end{bmatrix} \\ &= \mathbf{V} \boldsymbol{\alpha}_{1} \end{align*} $$
나머지 스텝도 마찬가지다. 스텝 $t$에서는 쿼리 $\mathbf{q}_{t}$로부터 스코어 $\mathbf{e}_{t} = f(\mathbf{K}, \mathbf{q}_{t})$를 얻고, 소프트맥스를 취해 가중치 $\boldsymbol{\alpha}_{t} = \operatorname{softmax}(\mathbf{e}_{t})$를 만든 뒤, 이 가중치로 밸류를 선형결합하여 컨텍스트 벡터 $\mathbf{c}_{t} = \mathbf{V}\boldsymbol{\alpha}_{t}$를 얻는다. 이 예에서 쿼리와 컨텍스트 벡터는 디코더가 단어를 생성하는 매 스텝마다 하나씩 만들어지지만, 세 스텝의 컨텍스트 벡터를 모은 행렬 $\mathbf{C} = \begin{bmatrix} \mathbf{c}_{1} & \mathbf{c}_{2} & \mathbf{c}_{3} \end{bmatrix}$는 아래와 같이 정리되며, 이것이 정의 $(1)$이 의미하는 것이다.
$$ \begin{align*} \mathbf{C} &= \begin{bmatrix} \mathbf{c}_{1} & \mathbf{c}_{2} & \mathbf{c}_{3} \end{bmatrix} \\ &= \begin{bmatrix} \mathbf{V} \boldsymbol{\alpha}_{1} & \mathbf{V} \boldsymbol{\alpha}_{2} & \mathbf{V} \boldsymbol{\alpha}_{3} \end{bmatrix} \\ &= \mathbf{V} \begin{bmatrix} \boldsymbol{\alpha}_{1} & \boldsymbol{\alpha}_{2} & \boldsymbol{\alpha}_{3} \end{bmatrix} \\ &= \mathbf{V} \operatorname{Softmax}\left( f(\mathbf{K}, \mathbf{Q}) \right) \\ &= \operatorname{Attention}(\mathbf{Q}, \mathbf{K}, \mathbf{V}) \end{align*} $$
$f(\mathbf{K}, \mathbf{Q})$는 각각의 쿼리와 키의 스코어를 모두 모아 놓은 행렬이다.
$$ f(\mathbf{K}, \mathbf{Q}) = \begin{bmatrix} s(\mathbf{k}_{1}, \mathbf{q}_{1}) & s(\mathbf{k}_{1}, \mathbf{q}_{2}) & s(\mathbf{k}_{1}, \mathbf{q}_{3}) \\ s(\mathbf{k}_{2}, \mathbf{q}_{1}) & s(\mathbf{k}_{2}, \mathbf{q}_{2}) & s(\mathbf{k}_{2}, \mathbf{q}_{3}) \\ s(\mathbf{k}_{3}, \mathbf{q}_{1}) & s(\mathbf{k}_{3}, \mathbf{q}_{2}) & s(\mathbf{k}_{3}, \mathbf{q}_{3}) \end{bmatrix} $$
이 행렬의 각 열에 소프트맥스를 취하면 다음과 같다.
$$ \mathbf{A} = \operatorname{Softmax}\left( f(\mathbf{K}, \mathbf{Q}) \right) = \begin{bmatrix} \alpha_{11} & \alpha_{12} & \alpha_{13} \\ \alpha_{21} & \alpha_{22} & \alpha_{23} \\ \alpha_{31} & \alpha_{32} & \alpha_{33} \end{bmatrix} $$
이 $\mathbf{A}$의 각 열이 앞서 구한 가중치 벡터 $\boldsymbol{\alpha}_{j}$이며, 여기에 왼쪽에서 밸류 행렬 $\mathbf{V}$를 곱하면 컨텍스트 행렬 $\mathbf{C}$가 된다.
키와 밸류를 구분하는 이유
여기까지만 보면 키와 밸류가 구분되어 따로 있어야 할 필요성을 느끼지 못할 수도 있다. 두 벡터 $\mathbf{k}_{i}$, $\mathbf{v}_{i}$ 모두 원문의 $i$번째 토큰의 정보를 담고 있는 것이기도 하고, 수식을 봐도 $\mathbf{k}_{i} = \mathbf{v}_{i}$로 두었을 때 문제는 없다. 실제로 초기의 어텐션 구조인 바다나우 어텐션에서는 키와 밸류를 명시적으로 구분하지 않았다. 인코더 은닉상태는 디코더 상태와의 스코어를 계산하는 데 사용되는 동시에 컨텍스트 벡터를 구성하는 데에도 사용되었다. 오늘날의 쿼리–키–밸류 관점에서 보면 인코더 은닉상태가 사실상 키와 밸류의 역할을 함께 수행한 것이다. 즉 $e_{it} = s(\mathbf{h}_{t}^{\text{dec}}, \mathbf{h}_{i}^{\text{enc}})$, $\mathbf{c}_{t} = \sum_{i} \alpha_{it} \mathbf{h}_{i}^{\text{enc}}$ 이므로, 개념적으로는 $\mathbf{k}_{i} = \mathbf{v}_{i} = \mathbf{h}_{i}^{\text{enc}}$ 에 해당한다.
그럼에도 불구하고 두 벡터를 구분하는 이유는 두 벡터가 담당해야 할 역할이 명확히 다르기 때문이다.
$\mathbf{k}_{i}$: 출력 $y_{t}$가 참조해야 할(=매칭되어야 할) 위치를 선택하기 위한 정보.
$\mathbf{v}_{i}$: 참조해야 할 위치가 선택되었을 때 실제로 전달해야 할 정보.
따라서 정의에서도 보이듯이 키 벡터와 밸류 벡터의 차원 $d_{k}$, $d_{v}$가 같을 필요가 없다. 그래서 추상적으로 은닉벡터가 아래와 같은 의미를 갖도록 임베딩되었다고 하자. 물론 실제 신경망의 은닉벡터에서 각 좌표가 어휘 의미, 품사, 시제와 같이 명시적으로 하나의 의미에 대응하는 것은 아니다. 아래의 표기는 같은 은닉상태로부터 키 함수와 밸류 함수가 서로 다른 종류의 정보를 강조하여 추출할 수 있음을 직관적으로 나타낸 것이다. 이 예시는 어디까지나 직관을 주기 위한 것이고, 실제 학습된 $\mathbf{h}_{i}^{\text{enc}}$, $\mathbf{k}_{i}$, $\mathbf{v}_{i}$는 이렇게 사람이 읽을 수 있는 항목으로 깔끔히 나뉘지는 않는다.
$$ \mathbf{h}_{i}^{\text{enc}} = \begin{bmatrix} \text{어휘 의미} \\ \text{품사} \\ \text{문법적 역할} \\ \text{시제} \\ \text{문맥} \\ \end{bmatrix} $$
그러면 키와 밸류는 각각 아래와 같은 식으로 정보가 압축되어 만들어질 수 있다는 것이다.
$$ \mathbf{k}_{i} = k(\mathbf{h}_{i}^{\text{enc}}, \mathbf{W}^{K}) = \begin{bmatrix} \text{어휘 의미} \\ \text{품사} \\ \text{문법적 역할} \\ \end{bmatrix}, \qquad \mathbf{v}_{i} = v(\mathbf{h}_{i}^{\text{enc}}, \mathbf{W}^{V}) = \begin{bmatrix} \text{번역에 필요한 의미} \\ \text{시제} \\ \text{수 일치} \\ \text{주변 문맥} \\ \text{어조 및 뉘앙스} \\ \end{bmatrix} $$
키와 밸류를 동일하게 두면 하나의 표현이 두 가지 목적을 동시에 수행해야 한다.
$$ \mathbf{k}_{i} = \mathbf{v}_{i} = \mathbf{h}_{i}^{\text{enc}} $$
즉, $\mathbf{h}_{i}^{\text{enc}}$는 쿼리와 비교하기에도 적합해야 하며, 동시에 컨텍스트 벡터에 직접 포함되기에도 적합해야 한다. 반면 아래와 같이 분리하면 $\mathbf{W}^{K}$는 원문의 어느 위치가 현재 출력과 관련 있는지를 판단하기 좋은 표현을 만들도록 학습되고, $\mathbf{W}^{V}$는 그 위치에서 디코더에 실제로 전달할 정보를 만들도록 학습될 수 있다.
$$ \mathbf{k}_{i} = k(\mathbf{h}_{i}^{\text{enc}}, \mathbf{W}^{K}), \qquad \mathbf{v}_{i} = v(\mathbf{h}_{i}^{\text{enc}}, \mathbf{W}^{V}) $$
따라서 키와 밸류를 구분하는 것은 어텐션을 성립시키기 위한 필수 조건이라기보다, 검색에 사용할 표현과 전달할 표현을 독립적으로 학습하기 위한 장치라고 볼 수 있다.
컨텍스트 벡터로부터 출력 생성
첫 번째 컨텍스트 벡터 $\mathbf{c}_{1}$은 영어 단어 "Apples" 자체가 아니라, 첫 번째 번역 토큰을 생성하는 데 필요한 원문의 정보를 담은 벡터이다. 디코더는 자신의 은닉상태 $\mathbf{h}_{1}^{\text{dec}}$와 컨텍스트 벡터 $\mathbf{c}_{1}$을 결합하여 출력 어휘에 대한 확률분포를 만든다.
$$ \mathbf{p}_{1} = g(\mathbf{h}_{1}^{\text{dec}}, \mathbf{c}_{1}; \boldsymbol{\theta}_{g}) \in \mathbb{R}^{M} $$
여기서 $\boldsymbol{\theta}_{g}$는 출력 함수 $g$의 학습 가능한 파라미터이고, $\mathbf{p}_{1}$은 영어 어휘의 각 토큰이 첫 번째 출력이 될 확률을 나타내는 벡터이다. 즉 각각의 $\mathbf{w}_{i}$를 영어단어라고 하고, $M$개의 단어가 어휘사전에 있다고 하면 확률벡터는 다음과 같다. 여기서 $P(\mathbf{y}_{1}=\mathbf{w}_{i})$는 첫 번째 출력이 단어 $\mathbf{w}_{i}$일 확률을 의미한다.
$$ \mathbf{p}_{1} = \begin{bmatrix} P(\mathbf{y}_{1}=\mathbf{w}_{1}) \\ P(\mathbf{y}_{1}=\mathbf{w}_{2}) \\ \vdots \\ P(\mathbf{y}_{1}=\mathbf{w}_{M}) \end{bmatrix} \in \mathbb{R}^{M}, \quad \sum_{j=1}^{M} [\mathbf{p}_{1}]_{j} = 1. $$
$\mathbf{p}_{1}$을 얻는 구체적인 예시로 선형변환과 소프트맥스를 사용한다면 아래와 같이 나타낼 수 있다.
$$ \mathbf{p}_{1} = \operatorname{softmax}\left( \mathbf{W}^{\text{out}} \begin{bmatrix} \mathbf{h}_{1}^{\text{dec}} \\ \mathbf{c}_{1} \end{bmatrix} + \mathbf{b}^{\text{out}} \right) $$
$$ \mathbf{W}^{\text{out}} \in M^{M \times (\dim(\mathbf{h}) + \dim(\mathbf{c}))}, \quad \mathbf{b}^{\text{out}} \in \mathbb{R}^{M} $$
잘 학습된 모델이라면 첫 번째 스텝에서 $P(\mathbf{y}_{1} = \text{“Apples”})$ 가 크게 나타날 것이다. 여기에서 중요한 것은 $\mathbf{c}_{1}$이 출력될 단어 "Apples" 그 자체가 아니라는 점이다. $\mathbf{c}_{1}$은 원문의 정보를 추출한 중간 벡터이고, 실제 단어 확률은 후속 출력층에서 계산된다.

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

