??self-attetion是BERT中的最為核心的內容之一,雖然TensorFlow版的BERT中的self-attention的原理和論文中是一致的,但是實作代碼卻有所出入,為了幫助新手快速理解這部分內容,所以通過該篇博客逐行解釋具體代碼,
文章目錄
- 1. 函式引數
- 2. 維度變換程序
- 2.1 單個注意力頭
- 2.2 多個注意力頭
- 3. 代碼決議
- 3.1 transpose_for_scores函式
- 3.2 reshape_to_matrix函式
- 3.3 attention_layer函式
- 3.4 dropout函式
1. 函式引數
def attention_layer(from_tensor,
to_tensor,
attention_mask=None,
num_attention_heads=1,
size_per_head=512,
query_act=None,
key_act=None,
value_act=None,
attention_probs_dropout_prob=0.0,
initializer_range=0.02,
do_return_2d_tensor=False,
batch_size=None,
from_seq_length=None,
to_seq_length=None)
-
引數from_tensor指的是輸入張量,如果不考慮對TPU的優化的話,它的輸入維度為[batch_size, seq_length, hidden_size],
-
引數from_tensor指的是輸出張量,如果是self-attetion,則它的維度和輸入是一致的,即[batch_size, seq_length, hidden_size],當然,谷歌設計了一個更為通用的attention layer,所以也可以設定成和輸入維度不一致,
-
引數attention_mask指的是部分位置的元素進行mask,具體來說該張量中每個元素的取值為1或者0,其中當取值為0時,則進行mask,它的維度為[batch_size, seq_length, seq_length],那它是什么時候進行使用呢?比如為了防止資訊泄露,將后面位置的元素進行mask,
-
引數num_attention_heads,在Transformer中包括的是多頭的注意力,該引數就是頭的個數,
-
引數size_per_head,指的是每個注意力頭對應的維度,
-
引數query_act、key_act和value_act指的是Q、K、V對應的激活函式,一般是不使用的,
-
引數initializer_range指的是權重初始化的范圍,在BERT中用的是0.02,
-
引數do_return_2d_tensor的型別為布爾型,當為True時,輸出維度為[batch_size * from_seq_length, num_attention_heads * size_per_head],當為False時,輸出維度為[batch_size, from_seq_length, num_attention_heads* size_per_head],
-
引數batch_size、from_seq_length和to_seq_length是當input為矩陣(維度為2)時,則需要進行對應輸入,
"""Performs multi-headed attention from `from_tensor` to `to_tensor`.
This is an implementation of multi-headed attention based on "Attention
is all you Need". If `from_tensor` and `to_tensor` are the same, then
this is self-attention. Each timestep in `from_tensor` attends to the
corresponding sequence in `to_tensor`, and returns a fixed-with vector.
This function first projects `from_tensor` into a "query" tensor and
`to_tensor` into "key" and "value" tensors. These are (effectively) a list
of tensors of length `num_attention_heads`, where each tensor is of shape
[batch_size, seq_length, size_per_head].
Then, the query and key tensors are dot-producted and scaled. These are
softmaxed to obtain attention probabilities. The value tensors are then
interpolated by these probabilities, then concatenated back to a single
tensor and returned.
In practice, the multi-headed attention are done with transposes and
reshapes rather than actual separate tensors.
Args:
from_tensor: float Tensor of shape [batch_size, from_seq_length,
from_width].
to_tensor: float Tensor of shape [batch_size, to_seq_length, to_width].
attention_mask: (optional) int32 Tensor of shape [batch_size,
from_seq_length, to_seq_length]. The values should be 1 or 0. The
attention scores will effectively be set to -infinity for any positions in
the mask that are 0, and will be unchanged for positions that are 1.
num_attention_heads: int. Number of attention heads.
size_per_head: int. Size of each attention head.
query_act: (optional) Activation function for the query transform.
key_act: (optional) Activation function for the key transform.
value_act: (optional) Activation function for the value transform.
attention_probs_dropout_prob: (optional) float. Dropout probability of the
attention probabilities.
initializer_range: float. Range of the weight initializer.
do_return_2d_tensor: bool. If True, the output will be of shape [batch_size
* from_seq_length, num_attention_heads * size_per_head]. If False, the
output will be of shape [batch_size, from_seq_length, num_attention_heads
* size_per_head].
batch_size: (Optional) int. If the input is 2D, this might be the batch size
of the 3D version of the `from_tensor` and `to_tensor`.
from_seq_length: (Optional) If the input is 2D, this might be the seq length
of the 3D version of the `from_tensor`.
to_seq_length: (Optional) If the input is 2D, this might be the seq length
of the 3D version of the `to_tensor`.
Returns:
float Tensor of shape [batch_size, from_seq_length,
num_attention_heads * size_per_head]. (If `do_return_2d_tensor` is
true, this will be of shape [batch_size * from_seq_length,
num_attention_heads * size_per_head]).
Raises:
ValueError: Any of the arguments or tensor shapes are invalid.
"""
2. 維度變換程序
??在深度學習中,把握每個張量或者矩陣在變換前后的維度是至關重要的,但是不少初學者卻忽略了這一點,所以特此加入該部分內容,
??在開始之前,我們先把self-attention的公式列一下:
A
t
t
e
n
t
i
o
n
(
Q
,
K
,
V
)
=
s
o
f
t
m
a
x
(
Q
K
T
d
k
)
V
Attention(Q, K, V)=softmax(\frac{QK^T}{\sqrt{d_k}}) V
Attention(Q,K,V)=softmax(dk?
?QKT?)V
2.1 單個注意力頭
??為了簡單起見,先假設注意力頭是單個,然后搞清楚后再進行擴展就更容易理解多個注意力頭的維度變換程序,令B=batch_size,S=seq_length,H=hidden_size,
??首先,Q、K、V的維度均為[B, S, H],那么 Q K T QK^T QKT的維度為[B, S,S],則 s o f t m a x ( Q K T d k ) V softmax(\frac{QK^T}{\sqrt{d_k}}) V softmax(dk? ?QKT?)V的維度為[B, S, H],
2.2 多個注意力頭
??多個注意力頭相比于單個注意力頭而言,Q、K、V的維度均多了一維,即[B, S, N, H],在矩陣相乘之前,會將維度轉換為[B, N, S, H],K轉置后維度為[B, N, H, S],所以矩陣相乘后的維度為[B, N, S, S],則 s o f t m a x ( Q K T d k ) V softmax(\frac{QK^T}{\sqrt{d_k}}) V softmax(dk? ?QKT?)V的維度為[B, N, S, H],最后再還原成[B, S, N, H],
3. 代碼決議
3.1 transpose_for_scores函式
def transpose_for_scores(input_tensor, batch_size, num_attention_heads,
seq_length, width):
output_tensor = tf.reshape(
input_tensor, [batch_size, seq_length, num_attention_heads, width])
output_tensor = tf.transpose(output_tensor, [0, 2, 1, 3])
return output_tensor
??transpose_for_scores函式的作用就是把維度從[B, S, N, H]維度轉換為[B, N, S, H],
3.2 reshape_to_matrix函式
def reshape_to_matrix(input_tensor):
"""Reshapes a >= rank 2 tensor to a rank 2 tensor (i.e., a matrix)."""
ndims = input_tensor.shape.ndims
if ndims < 2:
raise ValueError("Input tensor must have at least rank 2. Shape = %s" %
(input_tensor.shape))
if ndims == 2:
return input_tensor
width = input_tensor.shape[-1]
output_tensor = tf.reshape(input_tensor, [-1, width])
return output_tensor
??reshape_to_matrix,就是把三維張量轉換為二維矩陣,變換規則為前兩維壓縮成一維,最后一維不變,之所以要進行上述操作,主要是在TPU上進行矩陣和張量的來回切換,會很影響整個運算效率,
??舉個例子來說,假設input_tensor的維度是[8, 128, 768], output_tensor是[8*128, 768]=[1024, 768] ,
3.3 attention_layer函式
def attention_layer(from_tensor,
to_tensor,
attention_mask=None,
num_attention_heads=1,
size_per_head=512,
query_act=None,
key_act=None,
value_act=None,
attention_probs_dropout_prob=0.0,
initializer_range=0.02,
do_return_2d_tensor=False,
batch_size=None,
from_seq_length=None,
to_seq_length=None):
from_shape = get_shape_list(from_tensor, expected_rank=[2, 3])
to_shape = get_shape_list(to_tensor, expected_rank=[2, 3])
if len(from_shape) != len(to_shape):
raise ValueError(
"The rank of `from_tensor` must match the rank of `to_tensor`.")
if len(from_shape) == 3:
batch_size = from_shape[0]
from_seq_length = from_shape[1]
to_seq_length = to_shape[1]
elif len(from_shape) == 2:
if (batch_size is None or from_seq_length is None or to_seq_length is None):
raise ValueError(
"When passing in rank 2 tensors to attention_layer, the values "
"for `batch_size`, `from_seq_length`, and `to_seq_length` "
"must all be specified.")
# Scalar dimensions referenced here:
# B = batch size (number of sequences)
# F = `from_tensor` sequence length
# T = `to_tensor` sequence length
# N = `num_attention_heads`
# H = `size_per_head`
from_tensor_2d = reshape_to_matrix(from_tensor)
to_tensor_2d = reshape_to_matrix(to_tensor)
# `query_layer` = [B*F, N*H]
query_layer = tf.layers.dense(
from_tensor_2d,
num_attention_heads * size_per_head,
activation=query_act,
name="query",
kernel_initializer=create_initializer(initializer_range))
# `key_layer` = [B*T, N*H]
key_layer = tf.layers.dense(
to_tensor_2d,
num_attention_heads * size_per_head,
activation=key_act,
name="key",
kernel_initializer=create_initializer(initializer_range))
# `value_layer` = [B*T, N*H]
value_layer = tf.layers.dense(
to_tensor_2d,
num_attention_heads * size_per_head,
activation=value_act,
name="value",
kernel_initializer=create_initializer(initializer_range))
# `query_layer` = [B, N, F, H]
query_layer = transpose_for_scores(query_layer, batch_size,
num_attention_heads, from_seq_length,
size_per_head)
# `key_layer` = [B, N, T, H]
key_layer = transpose_for_scores(key_layer, batch_size, num_attention_heads,
to_seq_length, size_per_head)
# Take the dot product between "query" and "key" to get the raw
# attention scores.
# `attention_scores` = [B, N, F, T]
attention_scores = tf.matmul(query_layer, key_layer, transpose_b=True)
attention_scores = tf.multiply(attention_scores,
1.0 / math.sqrt(float(size_per_head)))
if attention_mask is not None:
# `attention_mask` = [B, 1, F, T]
attention_mask = tf.expand_dims(attention_mask, axis=[1])
# Since attention_mask is 1.0 for positions we want to attend and 0.0 for
# masked positions, this operation will create a tensor which is 0.0 for
# positions we want to attend and -10000.0 for masked positions.
adder = (1.0 - tf.cast(attention_mask, tf.float32)) * -10000.0
# Since we are adding it to the raw scores before the softmax, this is
# effectively the same as removing these entirely.
attention_scores += adder
# Normalize the attention scores to probabilities.
# `attention_probs` = [B, N, F, T]
attention_probs = tf.nn.softmax(attention_scores)
# This is actually dropping out entire tokens to attend to, which might
# seem a bit unusual, but is taken from the original Transformer paper.
attention_probs = dropout(attention_probs, attention_probs_dropout_prob)
# `value_layer` = [B, T, N, H]
value_layer = tf.reshape(
value_layer,
[batch_size, to_seq_length, num_attention_heads, size_per_head])
# `value_layer` = [B, N, T, H]
value_layer = tf.transpose(value_layer, [0, 2, 1, 3])
# `context_layer` = [B, N, F, H]
context_layer = tf.matmul(attention_probs, value_layer)
# `context_layer` = [B, F, N, H]
context_layer = tf.transpose(context_layer, [0, 2, 1, 3])
if do_return_2d_tensor:
# `context_layer` = [B*F, N*H]
context_layer = tf.reshape(
context_layer,
[batch_size * from_seq_length, num_attention_heads * size_per_head])
else:
# `context_layer` = [B, F, N*H]
context_layer = tf.reshape(
context_layer,
[batch_size, from_seq_length, num_attention_heads * size_per_head])
return context_layer
??從最開始的from_shape = get_shape_list(from_tensor, expected_rank=[2, 3])到raise ValueError是進行維度檢查(值得學習和借鑒),
??下面兩行代碼是將from_tensor和to_tensor的維度均轉換為[B*S, H],具體可參考3.2的內容,
from_tensor_2d = reshape_to_matrix(from_tensor)
to_tensor_2d = reshape_to_matrix(to_tensor)
??query_layer是將維度從[BS, H]轉換成了[BS, N*H],key_layer和value_layer與之相似,
query_layer = tf.layers.dense(
from_tensor_2d,
num_attention_heads * size_per_head,
activation=query_act,
name="query",
kernel_initializer=create_initializer(initializer_range))
??下列代碼即為Attention(Q, K, V)中的 Q K T d k \frac{QK^T}{\sqrt{d_k}} dk? ?QKT?的對應代碼,這部分內容可參考上文中的維度轉換程序:
query_layer = transpose_for_scores(query_layer, batch_size,num_attention_heads,
from_seq_length, size_per_head)
# `key_layer` = [B, N, T, H]
key_layer = transpose_for_scores(key_layer, batch_size,
num_attention_heads, to_seq_length, size_per_head)
# Take the dot product between "query" and "key" to get the raw
# attention scores.
# `attention_scores` = [B, N, F, T]
attention_scores = tf.matmul(query_layer, key_layer, transpose_b=True)
attention_scores = tf.multiply(attention_scores, 1.0 / math.sqrt(float(size_per_head)))
??需要注意的是,在部分場景中需要對某些位置的元素進行mask,即為下列代碼:
if attention_mask is not None:
# `attention_mask` = [B, 1, F, T]
attention_mask = tf.expand_dims(attention_mask, axis=[1])
# Since attention_mask is 1.0 for positions we want to attend and 0.0 for
# masked positions, this operation will create a tensor which is 0.0 for
# positions we want to attend and -10000.0 for masked positions.
adder = (1.0 - tf.cast(attention_mask, tf.float32)) * -10000.0
# Since we are adding it to the raw scores before the softmax, this is
# effectively the same as removing these entirely.
attention_scores += adder
??dropout函式可見3.4,其他操作依然可以參照上文中的維度變換,回傳結果有兩種,一種是回傳矩陣,一種是回傳張量,
# Normalize the attention scores to probabilities.
# `attention_probs` = [B, N, F, T]
attention_probs = tf.nn.softmax(attention_scores)
# This is actually dropping out entire tokens to attend to, which might
# seem a bit unusual, but is taken from the original Transformer paper.
attention_probs = dropout(attention_probs, attention_probs_dropout_prob)
# `value_layer` = [B, T, N, H]
value_layer = tf.reshape(
value_layer,
[batch_size, to_seq_length, num_attention_heads, size_per_head])
# `value_layer` = [B, N, T, H]
value_layer = tf.transpose(value_layer, [0, 2, 1, 3])
# `context_layer` = [B, N, F, H]
context_layer = tf.matmul(attention_probs, value_layer)
# `context_layer` = [B, F, N, H]
context_layer = tf.transpose(context_layer, [0, 2, 1, 3])
if do_return_2d_tensor:
# `context_layer` = [B*F, N*H]
context_layer = tf.reshape(
context_layer,
[batch_size * from_seq_length, num_attention_heads * size_per_head])
else:
# `context_layer` = [B, F, N*H]
context_layer = tf.reshape(
context_layer,
[batch_size, from_seq_length, num_attention_heads * size_per_head])
return context_layer
3.4 dropout函式
def dropout(input_tensor, dropout_prob):
"""Perform dropout.
Args:
input_tensor: float Tensor.
dropout_prob: Python float. The probability of dropping out a value (NOT of
*keeping* a dimension as in `tf.nn.dropout`).
Returns:
A version of `input_tensor` with dropout applied.
"""
if dropout_prob is None or dropout_prob == 0.0:
return input_tensor
output = tf.nn.dropout(input_tensor, 1.0 - dropout_prob)
return output
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/297757.html
標籤:AI
