안녕하세요
블레이즈 테크노트
블레이즈 입니다.
지난 포스팅에서
Attention is all you need 논문의 코드 중
transformer.py 의 앞부분을 살펴봤습니다.
이번 포스팅에서는 transformer.py의 뒷부분을 살펴보도록 하겠습니다.
원 코드는 아래의 링크를 통해 확인해보세요.
https://github.com/tensorflow/tensor2tensor/blob/master/tensor2tensor/models/transformer.py

@registry.register_model
class TransformerScorer(Transformer):
"""Transformer model, but only scores in PREDICT mode.
Checkpoints between Transformer and TransformerScorer are interchangeable.
"""
def __init__(self, *args, **kwargs):
super(TransformerScorer, self).__init__(*args, **kwargs)
self._name = "transformer"
self._base_name = "transformer"
def infer(self,
features=None,
decode_length=50,
beam_size=1,
top_beams=1,
alpha=0.0,
use_tpu=False):
"""Returns the targets and their log probabilities."""
del decode_length, beam_size, top_beams, alpha, use_tpu
assert features is not None
# Run the model
self.hparams.force_full_predict = True
with tf.variable_scope(self.name):
logits, _ = self.model_fn(features)
assert len(logits.shape) == 5 # [batch, time, 1, 1, vocab]
logits = tf.squeeze(logits, [2, 3])
# Compute the log probabilities
log_probs = common_layers.log_prob_from_logits(logits)
targets = features["targets"]
assert len(targets.shape) == 4 # [batch, time, 1, 1]
targets = tf.squeeze(targets, [2, 3])
# Slice out the log_probs of the targets
log_probs = common_layers.index_last_dim_with_indices(log_probs, targets)
# Sum over time to get the log_prob of the sequence
scores = tf.reduce_sum(log_probs, axis=1)
return {"outputs": targets, "scores": scores}
위 코드는 TransformerScorer 클래스입니다.
- 이 클래스는 이전 포스트에서 설명드린 Transformer 클래스를 상속합니다.
assert len(logits.shape) == 5 # [batch, time, 1, 1, vocab]
logits = tf.squeeze(logits, [2, 3])
위 코드를 보시면 logits의 차원에 따라 차원 축소를 해줍니다.
- 그리고 common_layers.log_prob_from_logits()를 활용해 log probabilities를 계산합니다.
- 이렇게 log_probs 를 계산하고 나면 common_layers.index_last_dim_with_indixes()를 활용해 타겟에 맞는 로그 확률을 가져옵니다.

- common_layers.index_last_dim_with_indices(log_probs, targets)라는 함수는 log_probs 텐서에서 targets 텐서의 값들을 인덱스로 사용하여 해당하는 값을 가져옵니다.
이 부분은 예시와 함께 설명해보도록 하겠습니다.
# 가정
log_probs = [[-0.2, -0.4, -0.1, -0.3],
[-0.5, -0.2, -0.3, -0.0]]
targets = [[2], [1]]
위와 같은 조건에서 common_layers.index_last_dim_with_indices(log_probs, targets) 를 하면
첫 번째 시퀀스에서 인덱스 2에 해당하는 확률을, 두 번째 시퀀스에서 인덱스 1에 해당하는 확률을 가져옵니다.
결과는 아래와 같을 것입니다.
[-0.1, -0.2]
- 다음으로 tf.reduce_sum()을 사용해 scores를 합산합니다.
axis=1을 기준으로 합산하여 시퀀스 내의 각 시간 단계(time step)에 대한 로그 확률을 합산하여 전체 시퀀스의 로그 확률을 계산합니다.
axis=1을 기준으로 텐서를 합쳐서 차원을 축소하는 과정에 대해서 자세히 알아보도록 하겠습니다.
[2, 3, 4] 형태의 3D 텐서를 가정하고, axis=1을 기준으로 축소하는 과정을 자세히 살펴보겠습니다.
먼저 [2, 3, 4] 모양의 3D 텐서는 다음과 같은 형태를 갖게 됩니다.
Tensor T:
[
[ <-- 첫 번째 차원 (axis=0)의 첫 번째 요소
[a, b, c, d], <-- 두 번째 차원 (axis=1)의 첫 번째 요소
[e, f, g, h], <-- 두 번째 차원 (axis=1)의 두 번째 요소
[i, j, k, l] <-- 두 번째 차원 (axis=1)의 세 번째 요소
],
[ <-- 첫 번째 차원 (axis=0)의 두 번째 요소
[m, n, o, p], <-- 두 번째 차원 (axis=1)의 첫 번째 요소
[q, r, s, t], <-- 두 번째 차원 (axis=1)의 두 번째 요소
[u, v, w, x] <-- 두 번째 차원 (axis=1)의 세 번째 요소
]
]
만약 tf.reduce_sum을 axis=1 기준으로 적용하면, 두 번째 차원 (axis=1)의 모든 요소들이 합산됩니다.
Result:
[
[
(a+e+i), (b+f+j), (c+g+k), (d+h+l)
],
[
(m+q+u), (n+r+v), (o+s+w), (p+t+x)
]
]
@registry.register_model
class TransformerEncoder(t2t_model.T2TModel):
"""Transformer, encoder only."""
def body(self, features):
hparams = self._hparams
inputs = features["inputs"]
target_space = features["target_space_id"]
inputs = common_layers.flatten4d3d(inputs)
(encoder_input, encoder_self_attention_bias, _) = (
transformer_prepare_encoder(inputs, target_space, hparams))
encoder_input = tf.nn.dropout(encoder_input,
1.0 - hparams.layer_prepostprocess_dropout)
encoder_output = transformer_encoder(
encoder_input,
encoder_self_attention_bias,
hparams,
nonpadding=features_to_nonpadding(features, "inputs"))
encoder_output = tf.expand_dims(encoder_output, 2)
return encoder_output
위 코드는 TransformerEncoder 클래스에 관한 코드입니다.
이 코드는 Transformer 클래스와 달리 인코더만 구현되어 있는 특이한 트랜스포머 클래스인 것 같습니다.

(덜 중요한 것 같으니 일단 옆으로 치웁니다..)
def transformer_prepare_decoder(targets, hparams, features=None, pad=None):
"""Prepare one shard of the model for the decoder.
Args:
targets: a Tensor.
hparams: run hyperparameters
features: optionally pass the entire features dictionary as well. This is
needed now for "packed" datasets.
pad: vector to use for padding when shifting targets right
Returns:
decoder_input: a Tensor, bottom of decoder stack
decoder_self_attention_bias: a bias tensor for use in decoder self-attention
"""
if hparams.causal_decoder_self_attention:
# Causal attention.
if hparams.prepend_mode == "prepend_inputs_full_attention":
decoder_self_attention_bias = (
common_attention.attention_bias_prepend_inputs_full_attention(
common_attention.embedding_to_padding(targets)))
else:
decoder_self_attention_bias = (
common_attention.attention_bias_lower_triangle(
common_layers.shape_list(targets)[1]))
else:
# Full attention.
decoder_padding = common_attention.embedding_to_padding(targets)
decoder_self_attention_bias = (
common_attention.attention_bias_ignore_padding(decoder_padding))
if features and "targets_segmentation" in features:
# "Packed" dataset - keep the examples from seeing each other.
targets_segmentation = features["targets_segmentation"]
targets_position = features["targets_position"]
decoder_self_attention_bias += common_attention.attention_bias_same_segment(
targets_segmentation, targets_segmentation)
else:
targets_position = None
if hparams.proximity_bias:
decoder_self_attention_bias += common_attention.attention_bias_proximal(
common_layers.shape_list(targets)[1])
decoder_input = common_layers.shift_right_3d(targets, pad)
if hparams.pos == "timing":
if targets_position is not None:
decoder_input = common_attention.add_timing_signal_1d_given_position(
decoder_input, targets_position)
else:
decoder_input = common_attention.add_timing_signal_1d(decoder_input)
elif hparams.pos == "timing_from_features":
decoder_input = common_attention.add_timing_signals_from_features(
decoder_input, features, hparams.position_features)
elif hparams.pos == "emb":
decoder_input = common_attention.add_positional_embedding(
decoder_input, hparams.max_length, "targets_positional_embedding",
targets_position)
if hparams.activation_dtype == "bfloat16":
decoder_self_attention_bias = tf.cast(decoder_self_attention_bias,
tf.bfloat16)
return (decoder_input, decoder_self_attention_bias)
transformer_prepare_decoder는 디코더 부분을 준비하는 함수입니다.
이 함수는 hparams.causal_decoder_self_attention의 값에 따라
decoder_self_attention_bias 의 값을 할당해줍니다.
True 면 causal attention으로 현재 기준으로 뒷 부분을 마스킹하는 bias이고
False면 full attention 입니다.
나머지는 hparams.pos의 값에 따라 decoder_inputs에 positional 정보를 추가하는 과정입니다.

지난 포스팅에서 transformer_encoder()의 경우 transformer_layers 모듈에 있던 것을 그대로 차용했는데요,
디코더는 새로 정의해서 사용했습니다.
아무래도 cross-attention등 디코더 만의 독특한 구조 때문에 직접 정의해줘야 하는 부분이 많아서 그런 것 같네요!
def transformer_self_attention_layer(decoder_input,
decoder_self_attention_bias,
layer_idx,
hparams,
encoder_output=None,
encoder_decoder_attention_bias=None,
cache=None,
decode_loop_step=None,
save_weights_to=None,
make_image_summary=False,
layer_collection=None,
recurrent_memory_by_layer=None,
chunk_number=None):
"""A single transformer self-attention layer."""
x = decoder_input
layer = layer_idx
layer_name = "layer_%d" % layer
layer_cache = cache[layer_name] if cache is not None else None
attention_dropout_broadcast_dims = (
common_layers.comma_separated_string_to_integer_list(
getattr(hparams, "attention_dropout_broadcast_dims", "")))
if recurrent_memory_by_layer is not None:
recurrent_memory = recurrent_memory_by_layer[layer_name]
else:
recurrent_memory = None
if layer < hparams.get("num_area_layers", 0):
max_area_width = hparams.get("max_area_width", 1)
max_area_height = hparams.get("max_area_height", 1)
memory_height = hparams.get("max_area_height", 1)
else:
max_area_width = 1
max_area_height = 1
memory_height = 1
with tf.variable_scope(layer_name):
with tf.variable_scope("self_attention"):
y = common_attention.multihead_attention(
common_layers.layer_preprocess(
x, hparams, layer_collection=layer_collection),
None,
decoder_self_attention_bias,
hparams.attention_key_channels or hparams.hidden_size,
hparams.attention_value_channels or hparams.hidden_size,
hparams.hidden_size,
hparams.num_heads,
hparams.attention_dropout,
attention_type=hparams.self_attention_type,
max_relative_position=hparams.max_relative_position,
heads_share_relative_embedding=(
hparams.heads_share_relative_embedding),
add_relative_to_values=hparams.add_relative_to_values,
save_weights_to=save_weights_to,
cache=layer_cache,
make_image_summary=make_image_summary,
dropout_broadcast_dims=attention_dropout_broadcast_dims,
max_length=hparams.get("max_length"),
decode_loop_step=decode_loop_step,
vars_3d=hparams.get("attention_variables_3d"),
activation_dtype=hparams.get("activation_dtype", "float32"),
weight_dtype=hparams.get("weight_dtype", "float32"),
layer_collection=layer_collection,
recurrent_memory=recurrent_memory,
chunk_number=chunk_number,
hard_attention_k=hparams.get("hard_attention_k", 0),
gumbel_noise_weight=hparams.get("gumbel_noise_weight", 0.0),
max_area_width=max_area_width,
max_area_height=max_area_height,
memory_height=memory_height,
area_key_mode=hparams.get("area_key_mode", "none"),
area_value_mode=hparams.get("area_value_mode", "none"),
training=(hparams.get(
"mode",
tf_estimator.ModeKeys.TRAIN) == tf_estimator.ModeKeys.TRAIN))
x = common_layers.layer_postprocess(x, y, hparams)
if encoder_output is not None:
if not isinstance(encoder_output, (list,)):
encoder_output = [encoder_output]
with tf.variable_scope("encdec_attention"):
for enc_output in encoder_output:
y = common_attention.multihead_attention(
common_layers.layer_preprocess(
x, hparams, layer_collection=layer_collection),
enc_output,
encoder_decoder_attention_bias,
hparams.attention_key_channels or hparams.hidden_size,
hparams.attention_value_channels or hparams.hidden_size,
hparams.hidden_size,
hparams.num_heads,
hparams.attention_dropout,
max_relative_position=hparams.max_relative_position,
heads_share_relative_embedding=(
hparams.heads_share_relative_embedding),
add_relative_to_values=hparams.add_relative_to_values,
save_weights_to=save_weights_to,
cache=layer_cache,
make_image_summary=make_image_summary,
dropout_broadcast_dims=attention_dropout_broadcast_dims,
max_length=hparams.get("max_length"),
vars_3d=hparams.get("attention_variables_3d"),
activation_dtype=hparams.get("activation_dtype", "float32"),
weight_dtype=hparams.get("weight_dtype", "float32"),
layer_collection=layer_collection,
hard_attention_k=hparams.get("hard_attention_k", 0),
gumbel_noise_weight=hparams.get("gumbel_noise_weight", 0.0),
max_area_width=max_area_width,
max_area_height=max_area_height,
memory_height=memory_height,
area_key_mode=hparams.get("area_key_mode", "none"),
area_value_mode=hparams.get("area_value_mode", "none"),
training=(hparams.get(
"mode",
tf_estimator.ModeKeys.TRAIN) == tf_estimator.ModeKeys.TRAIN))
x = common_layers.layer_postprocess(x, y, hparams)
return x, layer_cache
다음은 transformer_self_attention_layer() 입니다.
이 함수는 Transformer 모델 내의 한 개의 self-attention 레이어를 정의합니다.
함수의 이름에서 볼 수 있듯이, 주된 기능은 self-attention을 수행하는 것입니다.
첫 째, 들어오는 인자를 봤을 때 인코더에서는 사용하지 않고 디코더에서만 사용할 수 있다는 점.
둘 째, 디코더에 맞게끔 셀프 어텐션만 구현한 것이 아니라 인코더-디코더 어텐션도 구현되어 있다는 점입니다.
코드를 보면 먼저 x에 decoder_input을 할당합니다.
이후, common_attention.multihead_attention()를 적용하여 셀프어텐션 값을 y에 할당합니다.
다음으로 이렇게 구한 셀프어텐션 값 y를 활용해 _postprocess를 합니다.
x = common_layers.layer_postprocess(x, y, hparams)
이 과정은 아마도 residual connection과 layer normalization일 것입니다.
다음으로 encoder_output is not None 이라면 x의 값을 이용해
encoder-decoder attention 값을 계산해 y에 할당합니다.
이렇게 구한 인코더-디코더 어텐션 값에
다시 한번 postprocess를 해주면 디코더 내의 attention layer가 완성되는 것입니다.
def transformer_decoder_layer(decoder_input,
decoder_self_attention_bias,
layer_idx,
hparams,
encoder_output=None,
encoder_decoder_attention_bias=None,
cache=None,
decode_loop_step=None,
nonpadding=None,
save_weights_to=None,
make_image_summary=False,
losses=None,
layer_collection=None,
recurrent_memory_by_layer=None,
chunk_number=None):
"""A single transformer decoder layer."""
x, layer_cache = transformer_self_attention_layer(
decoder_input=decoder_input,
decoder_self_attention_bias=decoder_self_attention_bias,
layer_idx=layer_idx,
hparams=hparams,
encoder_output=encoder_output,
encoder_decoder_attention_bias=encoder_decoder_attention_bias,
cache=cache,
decode_loop_step=decode_loop_step,
save_weights_to=save_weights_to,
make_image_summary=make_image_summary,
layer_collection=layer_collection,
recurrent_memory_by_layer=recurrent_memory_by_layer,
chunk_number=chunk_number)
layer = layer_idx
layer_name = "layer_%d" % layer
with tf.variable_scope(layer_name):
with tf.variable_scope("ffn"):
y = transformer_ffn_layer(
common_layers.layer_preprocess(
x, hparams, layer_collection=layer_collection),
hparams,
conv_padding="LEFT",
nonpadding_mask=nonpadding,
losses=losses,
cache=layer_cache,
decode_loop_step=decode_loop_step,
layer_collection=layer_collection)
x = common_layers.layer_postprocess(x, y, hparams)
return x
transformer_decoder_layer() 함수는 앞에서 정의한 transformer_self_attention_layer() 를 활용합니다.
x 값에 self_attention_layer 값을 먼저 할당합니다.
그 후, Feed-Forward Neural Network 레이어를 처리해줍니다.
여기서는 transformer_ffn_layer = transformer_layers.transformer_ffn_layer 로 정의한 ffn을 활용합니다.
이후 postprocess 처리를 해줍니다.
def transformer_decoder(decoder_input,
encoder_output,
decoder_self_attention_bias,
encoder_decoder_attention_bias,
hparams,
cache=None,
decode_loop_step=None,
name="decoder",
nonpadding=None,
save_weights_to=None,
make_image_summary=True,
losses=None,
layer_collection=None,
recurrent_memory_by_layer=None,
chunk_number=None):
"""A stack of transformer layers.
Args:
decoder_input: a Tensor
encoder_output: a Tensor
decoder_self_attention_bias: bias Tensor for self-attention (see
common_attention.attention_bias())
encoder_decoder_attention_bias: bias Tensor for encoder-decoder attention
(see common_attention.attention_bias())
hparams: hyperparameters for model
cache: dict, containing tensors which are the results of previous
attentions, used for fast decoding.
decode_loop_step: An integer, step number of the decoding loop. Only used
for inference on TPU.
name: a string
nonpadding: optional Tensor with shape [batch_size, encoder_length]
indicating what positions are not padding. This is used to mask out
padding in convolutional layers. We generally only need this mask for
"packed" datasets, because for ordinary datasets, no padding is ever
followed by nonpadding.
save_weights_to: an optional dictionary to capture attention weights for
visualization; the weights tensor will be appended there under a string
key created from the variable scope (including name).
make_image_summary: Whether to make an attention image summary.
losses: optional list onto which to append extra training losses
layer_collection: A tensorflow_kfac.LayerCollection. Only used by the KFAC
optimizer. Default is None.
recurrent_memory_by_layer: Optional dict, mapping layer names to instances
of transformer_memory.RecurrentMemory. Default is None.
chunk_number: an optional integer Tensor with shape [batch] used to operate
the recurrent_memory.
Returns:
y: a Tensors
"""
x = decoder_input
mlperf_log.transformer_print(
key=mlperf_log.MODEL_HP_NUM_HIDDEN_LAYERS,
value=hparams.num_decoder_layers or hparams.num_hidden_layers,
hparams=hparams)
mlperf_log.transformer_print(
key=mlperf_log.MODEL_HP_ATTENTION_DROPOUT,
value=hparams.attention_dropout,
hparams=hparams)
mlperf_log.transformer_print(
key=mlperf_log.MODEL_HP_ATTENTION_DENSE,
value={
"use_bias": "false",
"num_heads": hparams.num_heads,
"hidden_size": hparams.hidden_size
},
hparams=hparams)
with tf.variable_scope(name):
for layer_idx in range(hparams.num_decoder_layers or
hparams.num_hidden_layers):
x = transformer_decoder_layer(
x,
decoder_self_attention_bias,
layer_idx,
hparams,
encoder_decoder_attention_bias=encoder_decoder_attention_bias,
encoder_output=encoder_output,
cache=cache,
decode_loop_step=decode_loop_step,
nonpadding=nonpadding,
save_weights_to=save_weights_to,
make_image_summary=make_image_summary,
losses=losses,
layer_collection=layer_collection,
recurrent_memory_by_layer=recurrent_memory_by_layer,
chunk_number=chunk_number
)
# if normalization is done in layer_preprocess, then it should also be done
# on the output, since the output can grow very large, being the sum of
# a whole stack of unnormalized layer outputs.
mlperf_log.transformer_print(
key=mlperf_log.MODEL_HP_NORM,
value={"hidden_size": hparams.hidden_size})
return common_layers.layer_preprocess(
x, hparams, layer_collection=layer_collection)
transformer_decoder 함수는 결국
hparams.num_decoder_layers or hparams.num_hidden_layers 만큼 반복해서 decoder_layer() 함수를 반복합니다.

@registry.register_hparams
def transformer_base_v1():
"""Set of hyperparameters."""
hparams = common_hparams.basic_params1()
hparams.norm_type = "layer"
hparams.hidden_size = 512
hparams.batch_size = 4096
hparams.max_length = 256
hparams.clip_grad_norm = 0. # i.e. no gradient clipping
hparams.optimizer_adam_epsilon = 1e-9
hparams.learning_rate_schedule = "legacy"
hparams.learning_rate_decay_scheme = "noam"
hparams.learning_rate = 0.1
hparams.learning_rate_warmup_steps = 4000
hparams.initializer_gain = 1.0
hparams.num_hidden_layers = 6
hparams.initializer = "uniform_unit_scaling"
hparams.weight_decay = 0.0
hparams.optimizer_adam_beta1 = 0.9
hparams.optimizer_adam_beta2 = 0.98
hparams.num_sampled_classes = 0
hparams.label_smoothing = 0.1
hparams.shared_embedding_and_softmax_weights = True
hparams.symbol_modality_num_shards = 16
# Add new ones like this.
hparams.add_hparam("filter_size", 2048)
# Layer-related flags. If zero, these fall back on hparams.num_hidden_layers.
hparams.add_hparam("num_encoder_layers", 0)
hparams.add_hparam("num_decoder_layers", 0)
# Attention-related flags.
hparams.add_hparam("num_heads", 8)
hparams.add_hparam("attention_key_channels", 0)
hparams.add_hparam("attention_value_channels", 0)
hparams.add_hparam("ffn_layer", "dense_relu_dense")
hparams.add_hparam("parameter_attention_key_channels", 0)
hparams.add_hparam("parameter_attention_value_channels", 0)
# All hyperparameters ending in "dropout" are automatically set to 0.0
# when not in training mode.
hparams.add_hparam("attention_dropout", 0.0)
hparams.add_hparam("attention_dropout_broadcast_dims", "")
hparams.add_hparam("relu_dropout", 0.0)
hparams.add_hparam("relu_dropout_broadcast_dims", "")
hparams.add_hparam("pos", "timing") # timing, none
hparams.add_hparam("position_features", "")
hparams.add_hparam("nbr_decoder_problems", 1)
hparams.add_hparam("proximity_bias", False)
hparams.add_hparam("causal_decoder_self_attention", True)
hparams.add_hparam("use_pad_remover", True)
hparams.add_hparam("self_attention_type", "dot_product")
hparams.add_hparam("conv_first_kernel", 3)
hparams.add_hparam("attention_variables_3d", False)
hparams.add_hparam("use_target_space_embedding", True)
# These parameters are only used when ffn_layer=="local_moe_tpu"
hparams.add_hparam("moe_overhead_train", 1.0)
hparams.add_hparam("moe_overhead_eval", 2.0)
hparams.moe_num_experts = 16
hparams.moe_loss_coef = 1e-3
# If specified, use this value instead of problem name in metrics.py.
# This is useful for programs that can automatically compare experiments side
# by side based on the same metric names.
hparams.add_hparam("overload_eval_metric_name", "")
# For making a transformer encoder unidirectional by using masked
# attention.
hparams.add_hparam("unidirectional_encoder", False)
# For hard attention.
hparams.add_hparam("hard_attention_k", 0)
hparams.add_hparam("gumbel_noise_weight", 0.0)
return hparams
이 외에는 대부분 하이퍼 파라미터를 설정해주는 함수들인 것 같습니다.
common_hparams.basic_params1() 을 활용해서 기본 하이퍼 파라미터를 설정합니다.
그 외에 어텐션에만 존재하는 하이퍼 파라미터를 추가해줍니다.
마지막으로 transformer.py 의 전체 구조에 대한 구조도를 보여드리고 마무리하도록 하겠습니다.
Transformer 클래스의 body() 함수는 크게 encode(), prepare_decode(), decode() 함수로 이루어져 있습니다.
encode() 함수의 내부는 transformer_layers.py 에서 구현이 되어 있습니다.
prepare_decode(), decode() 는 아래와 같은 함수를 콜합니다.
다음 포스팅에서 전체적으로 간단한 구조를 정리한 뒤에,
다양한 모듈에 대한 설명을 이어나가겠습니다.
감사합니다.
블레이즈 테크노트
'머신러닝(Machine Learning)' 카테고리의 다른 글
NLP 트랜스포머 코드 스터디 리뷰 transformer_layers.py (0) | 2023.08.23 |
---|---|
NLP 트랜스포머 코드 스터디 리뷰 (3) transformer.py (0) | 2023.08.21 |
NLP 트랜스포머 코드 스터디 리뷰 (1) transformer.py (0) | 2023.08.18 |
NLP 트랜스포머 모델 데이터셋 wmt14 다운로드하기 (0) | 2023.08.05 |
NLP 트랜스포머 네 번째, 멀티 헤드 어텐션 Multi-Head Attention 알아보기 (0) | 2023.08.01 |