머신러닝(Machine Learning)

NLP 트랜스포머 코드 스터디 리뷰 common_layers.py

Blaze_블즈 2023. 8. 28. 10:14

안녕하세요

블레이즈 테크노트 

블레이즈 입니다. 

Attention is all you need 가 구글에서 발표한 논문인만큼

이 논문에서 사용된 코드가 tensorflow 공식으로 등록되어 있습니다.

 

아래는 그 코드의 내용입니다. 

https://github.com/tensorflow/tensor2tensor

 

GitHub - tensorflow/tensor2tensor: Library of deep learning models and datasets designed to make deep learning more accessible a

Library of deep learning models and datasets designed to make deep learning more accessible and accelerate ML research. - GitHub - tensorflow/tensor2tensor: Library of deep learning models and data...

github.com

 

이 사이트를 보면 파일이 아주 많습니다. 

 

오늘은 그 중 common_layers.py 를 함께 살펴보겠습니다. 

https://github.com/tensorflow/tensor2tensor/blob/master/tensor2tensor/layers/common_layers.py

 

common_layers.py는 Transformer 모델 뿐 아니라 다양한 모델에서 사용되는 layer가 정의되어 있습니다. 

 

코드가 4000줄이 넘기 때문에 저희가 앞선 포스팅에서 살펴본 

transformer.pytransformer_layers.py에서 사용된 모듈 위주로 간단하게 살펴보도록 하겠습니다. 

 


dropout_with_broadcast_dims() : 정규화를 위해 드롭아웃을 하는 함수. 

 

가장 중요한 함수는 layer_postprocess()  아닐까 싶습니다. 

아래가 그 코드인데요  hparams 중 필요한 정보를 불러서 layer_prepostprocess()로 넘겨줍니다. 

def layer_postprocess(layer_input, layer_output, hparams):
  """Apply layer postprocessing.

  See layer_prepostprocess() for details.

  A hyperparameters object is passed for convenience.  The hyperparameters
  that may be used are:

    layer_postprocess_sequence
    layer_prepostprocess_dropout
    norm_type
    hidden_size
    norm_epsilon

  Args:
    layer_input: a Tensor
    layer_output: a Tensor
    hparams: a hyperparameters object.

  Returns:
    a Tensor
  """
  return layer_prepostprocess(
      layer_input,
      layer_output,
      sequence=hparams.layer_postprocess_sequence,
      dropout_rate=hparams.layer_prepostprocess_dropout,
      norm_type=hparams.norm_type,
      depth=None,
      epsilon=hparams.norm_epsilon,
      dropout_broadcast_dims=comma_separated_string_to_integer_list(
          getattr(hparams, "layer_prepostprocess_dropout_broadcast_dims", "")),
      default_name="layer_postprocess")

 

 

아래는 layer_prepostprocess() 코드입니다. 

이 코드는 주어진 순서대로 작업을 수행합니다. 

d n a z 중에 작업을 선택할 수 있는데 논문에서 제시된 작업은 dna입니다. 

d 를 먼저 해서 dropout을 하고

n 으로 normalization, 

a로 add previous_value 로 잔차 연결(residual connection)을 해줍니다.

 

def layer_prepostprocess(previous_value,
                         x,
                         sequence,
                         dropout_rate,
                         norm_type,
                         depth,
                         epsilon,
                         default_name,
                         name=None,
                         dropout_broadcast_dims=None,
                         layer_collection=None):
  """Apply a sequence of functions to the input or output of a layer.

  The sequence is specified as a string which may contain the following
  characters:
    a: add previous_value
    n: apply normalization
    d: apply dropout
    z: zero add

  For example, if sequence=="dna", then the output is
    previous_value + normalize(dropout(x))

  Args:
    previous_value: A Tensor, to be added as a residual connection ('a')
    x: A Tensor to be transformed.
    sequence: a string.
    dropout_rate: a float
    norm_type: a string (see apply_norm())
    depth: an integer (size of last dimension of x).
    epsilon: a float (parameter for normalization)
    default_name: a string
    name: a string
    dropout_broadcast_dims:  an optional list of integers less than 3
      specifying in which dimensions to broadcast the dropout decisions.
      saves memory.
    layer_collection: A tensorflow_kfac.LayerCollection. Only used by the
      KFAC optimizer. Default is None.

  Returns:
    a Tensor
  """
  with tf.variable_scope(name, default_name=default_name):
    if sequence == "none":
      return x
    for c in sequence:
      if c == "a":
        x += previous_value
      elif c == "z":
        x = zero_add(previous_value, x)
      elif c == "n":
        x = apply_norm(
            x, norm_type, depth, epsilon, layer_collection=layer_collection)
      else:
        assert c == "d", ("Unknown sequence step %s" % c)
        x = dropout_with_broadcast_dims(
            x, 1.0 - dropout_rate, broadcast_dims=dropout_broadcast_dims)
    return x

때로 전처리 과정에서 드롭아웃이 필요하면 이 함수를 다시 활용하기도 하는 것 같습니다. 

 


 

다음으로는 ffn 에서 사용되었던 dense_relu_dense() 입니다. 

def dense_relu_dense(inputs,
                     filter_size,
                     output_size,
                     output_activation=None,
                     dropout=0.0,
                     dropout_broadcast_dims=None,
                     layer_collection=None,
                     name=None):
  """Hidden layer with RELU activation followed by linear projection."""
  # layer_name is appended with "conv1" or "conv2" in this method only for
  # historical reasons. These are in fact dense layers.
  layer_name = "%s_{}" % name if name else "{}"
  h = dense(
      inputs,
      filter_size,
      use_bias=True,
      activation=tf.nn.relu,
      layer_collection=layer_collection,
      name=layer_name.format("conv1"))

  if dropout != 0.0:
    h = dropout_with_broadcast_dims(
        h, 1.0 - dropout, broadcast_dims=dropout_broadcast_dims)
  o = dense(
      h,
      output_size,
      activation=output_activation,
      use_bias=True,
      layer_collection=layer_collection,
      name=layer_name.format("conv2"))
  return o

이 함수를 보면 첫 번째 dense 함수에는 인자로 inputs 와 filter_size가 들어가고 activation = tf.nn.relu 가 들어갑니다. 

이를 통해 차원을 인풋 차원에서 filter_size 차원으로 늘리고 

활성화 함수로 ReLU를 사용합니다. 

 

그리고 이렇게 얻어진 h를 다시 output_size 차원으로 축소합니다. 

 

중간에 활성화 함수로 ReLU를 거치기 때문에 비선형성이 증가합니다. 

따라서 모델이 더 복잡한 데이터를 학습하기 좋습니다. 

 


이 외에도 다양한 함수가 있지만 트랜스포머 모델에서 중요하게 사용되는 것은 더 이상 없는 것 같습니다. 

 

트랜스포머에서 위 함수들이 사용되는 부분은 아래 그림과 같습니다. 

이것으로 common_layers.py 설명을 마치도록 하겠습니다.

 

감사합니다.

 

블레이즈 테크노트 

블레이즈.