본문 바로가기
AI/자연어처리

Transformer (33-3)

by 바다의 공간 2025. 1. 2.

Position-wise FFNN

포지션 와이즈 FFNN은 인코더와 디코더에서 공통적으로 가지고 있는 서브층입니다.

FFNN은 완전연결(Fully-connected FFNN)입니다.

식으로 표현하면 아래 그림과 같습니다.

여기서 x 는 앞서 멀티 헤드 어텐션의 결과로 나온 (seq_len,dmodel) 의 크기를 가지는 행렬을 말합니다.

가중치 행렬 W1 은 (dmodel,dff) 의 크기를 가지고

가중치 행렬 W2 은 (dff,dmodel) 의 크기를 가집니다.

논문에서 은닉층의 크기인 dff 는 앞서 하이퍼파라미터를 정의할 때 언급했듯이 2,048의 크기를 가집니다.

여기서 매개변수 W1 , b1 , W2 , b2 는 하나의 인코더 층 내에서는 다른 문장, 다른 단어들마다 정확하게 동일하게 사용됩니다.

하지만 인코더 층마다는 다른 값을 가집니다.

위의 그림에서 좌측은 인코더의 입력을 벡터 단위로 봤을 때,

각 벡터들이 멀티 헤드 어텐션 층이라는 인코더 내 첫번째 서브 층을 지나 FFNN을 통과하는 것을 보여줍니다.

이는 두번째 서브층인 Position-wise FFNN을 의미합니다.

물론, 실제로는 그림의 우측과 같이 행렬로 연산되는데, 두번째 서브층을 지난 인코더의 최종 출력은 여전히 인코더의 입력의 크기였던 (seq_len,dmodel) 의 크기가 보존되고 있습니다.

하나의 인코더 층을 지난 이 행렬은 다음 인코더 층으로 전달되고, 다음 층에서도 동일한 인코더 연산이 반복됩니다.

이를 구현하면 다음과 같습니다.

# 다음의 코드는 인코더와 디코더 내부에서 사용할 예정입니다.
outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention)
outputs = tf.keras.layers.Dense(units=d_model)(outputs)

잔차연결 (Residual Connection) 과 층 정규화 (Layer Normalization)

 

 

앞서 인코더의 두개의 서브층에 대해서 이해해보았습니다.

트랜스포머에서는 이러한 두개의 서브층을 가진 인코더에 추가적으로 사용하는 기법이있는데

바로 ADD&NORM입니다. 정확하게 풀어서 말하자면 잔차연결과 층 정규화를 의미합니다.

 

위의 그림은 앞서 PoSITION-WISE-FFNN을 설명할때 앞선 그림에서 화살표와 ADD&NORM(잔차연결과 층정규화)를 추가한 그림이고 이 화살표들은 서브층 이전의 입력에서 시작되어 서브층의 출력 부분을 향하고 있는 것에 주목해야합니다.

추가된 화살표가 어떤 의미를 갖고있는지를 좀 더 깊이 서술하자면

 

1) 잔차연결(Residual connection)

 

좌측 그림은 입력 x 와 x 에 대한 어떤 함수 F(x) 의 값을 더한 함수 H(x) 의 구조를 보여줍니다.

어떤 함수 F(x) 가 트랜스포머에서는 서브층에 해당됩니다. 다시 말해 잔차 연결은 서브층의 '입력'과 '출력'을 더하는 것을 말합니다.

앞서 언급했듯이 트랜스포머에서 서브층의 입력과 출력은 동일한 차원을 갖고 있으므로, 서브층의 입력과 서브층의 출력은 덧셈 연산을 할 수 있습니다.

이것이 바로 위의 인코더 그림에서 각 화살표가 서브층의 입력에서 출력으로 향하도록 그려졌던 이유입니다.

잔차 연결은 컴퓨터 비전 분야에서 주로 사용되는 모델의 학습을 돕는 기법입니다.

이를 식으로 표현하면 x+Sublayer(x) 입니다.가령, 서브층이 멀티 헤드 어텐션이었다면 잔차 연결 연산은 다음과 같습니다.

 

위 그림은 멀티 헤드 어텐션의 입력과 멀티 헤드 어텐션의 결과가 더해지는 과정을 보여줍니다.

관련논문 : https://arxiv.org/pdf/1512.03385.pdf

"Deep Residual Learning for Image Recognition"

 

2) 층 정규화(Layer Normalization)

잔차 연결을 거친 결과는 이어서 층 정규화 과정을 거치게됩니다.

잔차 연결의 입력을 x , 잔차 연결과 층 정규화 두 가지 연산을 모두 수행한 후의 결과 행렬을 LN 이라고 하였을 때, 잔차 연결 후 층 정규화 연산을 수식으로 표현하자면 다음과 같습니다.

층 정규화를 하는 과정에 대해서 이해해봅시다. 층 정규화는 텐서의 마지막 차원에 대해서 평균과 분산을 구하고, 이를 가지고 어떤 수식을 통해 값을 정규화하여 학습을 돕습니다. 여기서 텐서의 마지막 차원이란 것은 트랜스포머에서는 dmodel 차원을 의미합니다. 아래 그림은 dmodel 차원의 방향을 화살표로 표현하였습니다.

층 정규화를 위해서 우선, 화살표 방향으로 각각 평균 μ 과 분산 σ2 을 구합니다. 각 화살표 방향의 벡터를 xi 라고 명명해봅시다.

층 정규화를 수행한 후에는 벡터 xi 는 lni 라는 벡터로 정규화가 됩니다.

'층 정규화의 수식'을 알아봅시다. 여기서는 층 정규화를 두 가지 과정으로 나누어서 설명하겠습니다.

첫번째는 평균과 분산을 통한 정규화,

두번째는 감마와 베타를 도입하는 것입니다.

우선, 평균과 분산을 통해 벡터 xi 를 정규화 해줍니다.

xi 는 벡터인 반면, 평균 μi 과 분산 σ2i 은 스칼라입니다.

벡터 xi 의 각 차원을 k 라고 하였을 때, xik 는 다음의 수식과 같이 정규화 할 수 있습니다.

다시 말해 벡터 xi 의 각 k 차원의 값이 다음과 같이 정규화 되는 것입니다.

ϵ (입실론)은 분모가 0이 되는 것을 방지하는 값.

 

이제 γ (감마)와 β (베타)라는 벡터를 준비합니다. 단, 이들의 초기값은 각각 1과 0입니다.

γ 와 β 를 도입한 층 정규화의 최종 수식은 다음과 같으며 γ 와 β 는 학습 가능한 파라미터입니다.

 

관련 논문 : https://arxiv.org/pdf/1607.06450.pdf

케라스에서는 층 정규화를 위한 LayerNormalization()를 제공함


▶ 인코더 레이어 구현

# 인코더 레이어 '한개'

def encoder_layer(dff, d_model, num_heads, dropout, name='encoder_layer'):

  inputs = tf.keras.Input(shape=(None, d_model), name='inputs')

  # 인코더는 패딩마스크 사용
  padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')

  # 멀티 헤드 어텐션 (첫번째 서브층, 셀프 어텐션)
  attention = MultiHeadAttention(
      d_model, num_heads, name='attention')({
          'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
          'mask': padding_mask # 패딩 마스크 사용
      })

  # 드롭아웃 + 잔차연결과 층정규화
  attention = tf.keras.layers.Dropout(rate=dropout)(attention)
  attention = tf.keras.layers.LayerNormalization(epsilon=1e-6)(inputs + attention)

  # 포지션 와이즈 피드 포워트 신경망 (두번째 서브층)
  outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention)
  outputs = tf.keras.layers.Dense(units=d_model)(outputs)

  # 드롭아웃 + 잔차연결과 층정규화
  outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
  outputs = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(attention + outputs)

  return tf.keras.Model(inputs=[inputs, padding_mask], outputs=outputs, name=name)

 

 

▶ 인코더 모델 구현

def encoder(vocab_size, num_layers, dff,
            d_model, num_heads, dropout, name='encoder'):
  inputs = tf.keras.Input(shape=(None,), name='inputs')

  # 인코더는 패딩 마스크 사용
  padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')

  # 포지셔널 인코딩 + 드롭아웃
  embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
  embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
  embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
  outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)

  # 인코더 레이어를 num_layers 개 쌓기
  for i in range(num_layers):
    outputs = encoder_layer(dff=dff, d_model=d_model, num_heads=num_heads,
                  dropout=dropout, name=f'encoder_layer_{i}')([outputs, padding_mask])

  return tf.keras.Model(inputs=[inputs, padding_mask], outputs=outputs, name=name)

12. 디코더(decoder)구조

 

인코더 -> 디코더

 

위 사진에서 구현된 인코더는 총 num_heads만큼의 연산을 순차적으로 한 후에 마지막 층의 인코더 출력을 decoder에게 

전달하게 됩니다. 인코더 연산이 끝났으면 디코더 연산이 시작되어서 디코더 또한 num_heads만큼의 연산을 하는데 

이때마다 인코더가 보낸 출력을 '각'디코더 층 연산에 사용합니다.

 

디코더의 입력과 문제점

 

디코더의 입력)

디코더도 인코더와 동일하게 '임베딩'층과 '포지셔널 인코딩'을 거친 후의 문장 행렬이 입력됩니다.

 

트랜스포머 또한 seq2seq와 마찬가지로 교사강요(teacher Forcing)을 사용하여 훈련되므로 

학습과정에서 디코더는 번역할 문장에 해당되는 <sos> je suis studiant의 문장 행렬을 

'한번에' 입력 받습니다 그리고 디코더는 이 문장 행렬로부터 각 시점의 단어를 예측하도록 훈련됩니다.

 

 

 

 

 

그러나 여기서 문제점이 있는데..

seq2seq의 디코더에 사용되는 rnn계열의 신경망은 입력 단어를 '매 시점마다 순차적'으로 입력받으므로 단어 단어 예측에 현재 시점을 포함한 이전 시점에 입력된 단어들만 '참고'할 수 있습니다.

 

반면 트랜스포머는 문장행렬로 입력을 한번에 받으니까 현재 시점의 단어를 예측하고자할때 입력 문장행렬로부터 '미래시점'의 단어까지도 참고할 수 있는 현상이 발생합니다.

 

가령 suis를 예측해야하는 시점이라고 해보면

RNN 계열의 seq2seq의 디코더라면 현재까지 디코더에 입력된 단어는 <sos>와 je 뿐이겠지만

트랜스포머는 '이미' 문장행렬로 <sos> je suis studiant를 입력받게되는거죠.


13. 디코더의 첫번째 서브층: 셀프 어텐션과 룩-어헤드 마스크

이를 위해 트랜스포머의 디코더에서는 현재 시점의 예측에서 현재 시점보다 미래에 있는 단어들을 참고하지 못하도록 룩-어헤드 마스크(look-ahead mask)를 도입. 직역하면 '미리보기에 대한 마스크'입니다.

룩-어헤드 마스크는 디코더의 첫번째서브층에서 이루어집니다.

디코더의 첫번째 서브층인 멀티헤드설프 어텐션층은 인코더의 첫번째 서브층인 멀티헤드셀프어텐션 층과 동일한 연산을 수행합니다.

오직 다른점은 어텐녀 스코어 행렬에서 '마스킹'을 적용한다는점만 다릅니다.

우선 위와 같이 셀프 어텐션을 통해서 어텐션 스코어 행렬을 얻습니다.

자기 자신보다 미래에 있는 단어들은 참고하지 못하도록 다음과 같이 마스킹합니다.

마스킹 된 후의 어텐션 스코어 행렬의 각 행을 보면 '자기자신'과 그 이전의 단어들만 참고할 수 있음을 볼 수 있습니다.

그 외에는 근본적으로 셀프 어텐션이라는점과 멀티 헤드 어텐션을 수행한다는 점에서 인코더의 첫번째 서브층과 같습니다.

 

룩-어헤드 마스크 구현

룩-어헤드 마스크는 패딩 마스크와 마찬가지로 앞서 구현한 스케일드 닷 프로덕트 어텐션 함수에 mask=라는 인자로 전달됩니다.
패딩 마스킹을 써야하는 경우에는 스케일드 닷 프로덕트 어텐션 함수에 패딩 마스크를 전달하고,
룩-어헤드 마스킹을 써야하는 경우에는 스케일드 닷 프로덕트 어텐션 함수에 룩-어헤드 마스크를 전달합니다.

# 스케일드 닷 프로덕트 어텐션 함수를 다시 복습해봅시다.
def scaled_dot_product_attention(query, key, value, mask):
... 중략 ...
    logits += (mask * -1e9) # 어텐션 스코어 행렬인 logits에 mask*-1e9 값을 더해주고 있다.
... 중략 ...
# tf.linalg.band_part(input, num_lower, num_upper)
# : 텐서의 특정 대각선 부분을 추출하거나 마스킹하는 데 사용되는 함수
# 이 함수는 입력 텐서에서 하삼각 행렬(Lower Triangle)이나 상삼각 행렬(Upper Triangle)을 추출하거나, 원하는 대각선 부분만 남겨두고 나머지를 0으로 만듭니다.

# input: 입력 텐서(주로 2D 행렬이지만 고차원도 가능).
# num_lower: 남기고 싶은 하삼각 부분의 대각선 수(음수는 전체를 유지).
# num_upper: 남기고 싶은 상삼각 부분의 대각선 수(음수는 전체를 유지).

# 입력 행렬 (3x3)
matrix = tf.constant([[1, 2, 3],
                      [4, 5, 6],
                      [7, 8, 9]], dtype=tf.float32)

# 주 대각선 아래쪽만 유지
result = tf.linalg.band_part(matrix, num_lower=1, num_upper=0)

print(result.numpy())

[[1. 0. 0.]
 [4. 5. 0.]
 [0. 8. 9.]]

 

예시로 보면 행렬 3*3짜리를 num_lower=1, num_upper=0으로 하게되면

주대각선 아래만 유지하게되고 위로는  0으로 마스킹하게됩니다!

# 디코더의 첫번째 서브층에서 미래의(?) 토큰을 mask 하는 함수

def create_look_ahead_mask(x):
  seq_len = tf.shape(x)[1]

  look_ahead_mask = 1 - tf.linalg.band_part(tf.ones((seq_len, seq_len)), -1, 0);
  padding_mask = create_padding_mask(x)  # 패딩 마스크도 포함해야 한다
  return tf.maximum(look_ahead_mask, padding_mask)  # 룩-어헤드 마스크 와 패딩마스크 합치기

# 동작확인
print(create_look_ahead_mask(tf.constant([[1, 2, 0, 4, 5]])))

tf.Tensor(
[[[[0. 1. 1. 1. 1.]
   [0. 0. 1. 1. 1.]
   [0. 0. 1. 1. 1.]
   [0. 0. 1. 0. 1.]
   [0. 0. 1. 0. 0.]]]], shape=(1, 1, 5, 5), dtype=float32)

14. 디코더의 두번째 서브층 : 인코더-디코더 어텐션

디코더의 두번째 서브층은 멀티헤드 어텐션을 수행한다는 점에서는 이전의 어텐션들(인코더와 디코더의 첫번째 서브층)과는 공통점이 있으나 다른점은 셀프어텐션이 아니라는점입니다.

인코더의 첫번째 서브층 : Query = Key = Value

디코더의 첫번째 서브층 : Query = Key = Value

디코더의 두번째 서브층 : Query : 디코더 행렬 / Key = Value : 인코더 행렬 디코더의 두번째 서브층을 확대해보면, 다음과 같이 인코더로부터 두 개의 화살표가 그려져 있습니다.

두 개의 빨간 화살표는 각각 Key와 Value를 의미하며 이는 인코더의 마지막 층에서 온 행렬로부터 얻습니다.

반면 검정화살표인 Query는 디코더의 첫번째 서브층의 결과 행렬로부터 얻는다는 점이 다릅니다.

 

Query가 디코더 행렬, Key가 인코더 행렬일 때,
어텐션 스코어 행렬을 구하는 과정은 다음과 같습니다


15. 디코더 레이어 구현

디코더는 총 '세 개의 서브층'으로 구성.
첫번째와 두번째 서브층 모두 멀티 헤드 어텐션이지만,
첫번째 서브층은 mask의 인자값으로 look_ahead_mask가 들어가는 반면,
두번째 서브층은 mask의 인자값으로 padding_mask가 들어갑니다.

이는 첫번째 서브층은 마스크드 셀프 어텐션을 수행하기 때문입니다.
세 개의 서브층 모두 서브층 연산 후에는 드롭 아웃, 잔차 연결, 층 정규화가 수행됩니다.

def decoder_layer(dff, d_model, num_heads, dropout, name="decoder_layer"):
  # 디코더의 입력, 인코더로부터의 입력
  inputs = tf.keras.Input(shape=(None, d_model), name="inputs")
  enc_outputs = tf.keras.Input(shape=(None, d_model), name="encoder_outputs")

  # 룩어헤드 마스크(첫번째 서브층에서 사용)
  look_ahead_mask = tf.keras.Input(shape=(1, None, None), name='look_ahead_mask')

  # 패딩 마스크(두번째 서브층에서 사용)
  padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')

  # 멀티-헤드 어텐션 (첫번째 서브층 / 마스크드 셀프 어텐션)
  attention1 = MultiHeadAttention(
      d_model, num_heads, name="attention_1")(inputs={
          'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
          'mask': look_ahead_mask
      })

  # 잔차 연결과 층 정규화
  attention1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)(attention1 + inputs)

  # 멀티-헤드 어텐션 (두번째 서브층 / 디코더-인코더 어텐션)
  attention2 = MultiHeadAttention(
      d_model, num_heads, name='attention_2')(inputs={
          'query': attention1, 'key': enc_outputs, 'value': enc_outputs,  # Q != K = V
          'mask': padding_mask
      })

  # 드롭아웃 + 잔차 연결과 층 정규화
  attention2 = tf.keras.layers.Dropout(rate=dropout)(attention2)
  attention2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)(attention2 + attention1)

  # 포지션 와이즈 피드 포워드 신경망 (세번째 서브층)
  outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention2)
  outputs = tf.keras.layers.Dense(units=d_model)(outputs)

  # 드롭아웃 + 잔차 연결과 층 정규화
  outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
  outputs = tf.keras.layers.LayerNormalization(epsilon=1e-6)(outputs + attention2)

  return tf.keras.Model(
      inputs=[inputs, enc_outputs, look_ahead_mask, padding_mask],
      outputs=outputs,
      name=name
  )

16. 디코더 모델 구현

포지셔널 인코딩 후 디코더 층을 num_layers의 개수만큼 쌓은 decoder모델을 작성합니다.

def decoder(vocab_size, num_layers, dff,
            d_model, num_heads, dropout, name='decoder'):
  # 디코더의 입력과 인코더로부터의 입력
  inputs = tf.keras.Input(shape=(None,), name='inputs')
  enc_outputs = tf.keras.Input(shape=(None, d_model), name='encoder_outputs')

  # 디코더는 룩어헤드 마스크(첫번째 서브층)와 패딩 마스크(두번째 서브층) 둘 다 사용.
  look_ahead_mask = tf.keras.Input(shape=(1, None, None), name='look_ahead_mask')
  padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')

  # 포지셔널 인코딩 + 드롭아웃
  embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
  embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
  embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
  outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)

  # 디코더를 num_layers개 쌓기
  for i in range(num_layers):
    outputs = decoder_layer(
        dff=dff, d_model=d_model, num_heads=num_heads, dropout=dropout,
        name=f'decoder_layer_{i}'
    )(inputs=[outputs, enc_outputs, look_ahead_mask, padding_mask])

  return tf.keras.Model(
      inputs=[inputs, enc_outputs, look_ahead_mask, padding_mask],
      outputs=outputs,
      name=name
  )

17. 트랜스포머 모델 구현

위에서 구현한 인코더와 디코더 함수를 조합하여 트랜스포머를 구현

인코더의 출력은 디코더에서 인코더-디코더 어텐션에서 사용되기 위해 디코더로 전달해줍니다.

그리고 디코더의 끝단에는 다중 클래스 분류 문제를 풀 수 있도록 vocab_size만크의 뉴런을 가지는 출력층을 추가해줍니다.

def transformer(vocab_size, num_layers, dff, d_model, num_heads, dropout,
                name='transformer'):
  # 인코더의 입력
  inputs = tf.keras.Input(shape=(None,), name='inputs')

  # 디코더의 입력
  dec_inputs = tf.keras.Input(shape=(None,), name='dec_inputs')

  # 인코더의 패딩 마스크
  enc_padding_mask = tf.keras.layers.Lambda(create_padding_mask, output_shape=(1, 1, None),
                         name='enc_padding_mask')(inputs)

  # 디코더의 룩어헤드 마스크(첫번째 서브층)
  look_ahead_mask = tf.keras.layers.Lambda(create_look_ahead_mask, output_shape=(1, None, None),
                         name='look_ahead_mask')(dec_inputs)

  # 디코더의 패딩 마스크(두번째 서브층)
  dec_padding_mask = tf.keras.layers.Lambda(create_padding_mask, output_shape=(1, 1, None),
                         name='dec_padding_mask')(inputs)

  # 인코더의 출력은 enc_outputs. 디코더로 전달된다.
  enc_outputs = encoder(vocab_size=vocab_size, num_layers=num_layers, dff=dff,
          d_model=d_model, num_heads=num_heads, dropout=dropout,
  )(inputs=[inputs, enc_padding_mask])  # 인코더의 입력은 입력문장 과 패딩마스크

  # 디코더의 출력은 dec_outputs. 출력층으로 전달된다.
  dec_outputs = decoder(vocab_size=vocab_size, num_layers=num_layers, dff=dff,
          d_model=d_model, num_heads=num_heads, dropout=dropout,
  )(inputs=[dec_inputs, enc_outputs, look_ahead_mask, dec_padding_mask])

  # 다음 단어 예측을 위한 출력층
  outputs = tf.keras.layers.Dense(units=vocab_size, name='outputs')(dec_outputs)

  return tf.keras.Model(inputs=[inputs, dec_inputs], outputs=outputs, name=name)

18. 트랜스포머 하이퍼파라미터 정하기

트랜스포머의 하이퍼파라미터를 임의로 정하고 모델을 만들어봅시다.
현재 훈련 데이터가 존재하는 것은 아니지만, 단어 집합의 크기는 임의로 9,000으로 정합니다.
단어 집합의 크기로부터 룩업 테이블을 수행할 임베딩 테이블과 포지셔널 인코딩 행렬의 행의 크기를 결정할 수 있습니다.
논문에서 제시한 것과는 다르게 하이퍼파라미터를 정해보겠습니다.
인코더와 디코더의 층의 개수  num_layers 는 4개,
인코더와 디코더의 포지션 와이즈 피드 포워드 신경망의 은닉층  dff 은 512,
인코더와 디코더의 입, 출력의 차원  dmodel 은 128,
멀티-헤드 어텐션에서 병렬적으로 사용할 헤드의 수  num_heads 는 4로 정했습니다.
128/4의 값인 32가  dv 의 값이 되겠습니다.

small_transformer = transformer(
    vocab_size=9000,
    num_layers=4,
    dff=512,
    d_model=128,
    num_heads = 4,
    dropout = 0.3,
    name='small_transformer'
)

tf.keras.utils.plot_model(small_transformer, show_shapes=True)

 (1, 9000, 128)

 (1, 9000, 128)


손실함수 정의

# 다중클래스 분류 문제 -> 크로스 엔트로피 함수 사용

def loss_function(y_true, y_pred):
  tf.reshape(y_true, shape=(-1, MAX_LENGTH - 1))  # ※MAX_LENGTH (패딩값) 값은 나중에 정해줌

  loss = tf.keras.losses.SparseCategoricalCrossentropy(
      from_logits=True, reduction='none'
  )(y_true, y_pred)

  mask = tf.cast(tf.not_equal(y_true, 0), tf.float32)
  loss = tf.multiply(loss, mask)

  return tf.reduce_mean(loss)

학습률(learning rate)

논문 중..
학습률 스케줄러(Learning rate Scheduler)는 미리 학습 일정을 정해두고 그 일정에 따라 학습률이 조정되는 방법입니다.
트랜스포머의 경우 사용자가 정한 단계까지는 학습률을 증가시켰다가 단계에 이르면 학습률을 점차적으로 떨어트리는 방식을 사용합니다.
좀 더 구체적으로 봅시다. step_num(단계)이란 옵티마이저가 매개변수를 업데이트 하는 한 번의 진행 횟수를 의미합니다.
트랜스포머에서는 warmup_steps이라는 변수를 정하고 step_num이 warmup_steps보다 작을 경우는 학습률을 선형적으로 증가 시키고, step_num이 warmup_steps에 도달하게 되면 학습률을 step_num의 역제곱근에 따라서 감소시킵니다.
이를 식으로 표현하면 아래와 같습니다. warmup_steps의 값으로는 4,000을 사용하였습니다.

class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
  def __init__(self, d_model, warmup_steps=4000):
    super(CustomSchedule, self).__init__()
    self.d_model = d_model
    self.d_model = tf.cast(self.d_model, tf.float32)
    self.warmup_steps = warmup_steps

  def __call__(self, step):
    step = tf.cast(step, tf.float32)
    arg1 = tf.math.rsqrt(step)
    arg2 = step * (self.warmup_steps ** -1.5)
    return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)
# 학습률의 변화를 시각화해봅시다.

sample_learning_rate = CustomSchedule(d_model=128)

plt.plot(sample_learning_rate(tf.range(200000, dtype=tf.float32)))
plt.ylabel("Learning Rate")
plt.xlabel("Train Step")

'''
워밍을 4천까지준것에 대한것!
러닝레이트가 제일 높은 지점이 4천이고
외에는 역제곱으로 쭈욱떨어지는것을 볼 수 있음
'''


이어서는 위에서 트랜스포머를 이용해서 한국어 챗봇을 구현해보도록 하겠습니다.