[PytorchLearning] Notas introductorias de PNL manuscritas Mecanismo interno del codificador de transformador

Notas introductorias de PNL Manuscrito Transformador Codificador Mecanismo interno

Este artículo interpreta principalmente el código fuente y los principios de la generación de incrustaciones de Word en Transformer Encoder, el mecanismo de incrustación de posición y el mecanismo de máscara en autoatención.

Código fuente y notas en el artículo (haga clic en una barra de estrella QAQ): haga clic aquí para ver el código fuente
Dirección del documento: haga clic aquí para ver el documento
inserte la descripción de la imagen aquí

4.1 Con respecto a la incrustación de palabras, tome el modelado de secuencias como ejemplo

4.1.1 Definir parámetros importantes en la incrustación de palabras

# batch_size大小
batch_size=2

# 定义句子序列的最大长度
max_src_len=5
max_tgt_len=5

# 定义序列最大长度
model_dim=16

# 定义单词序列最大索引
max_src_num=8
max_tgt_num=8

4.1.2 Construya la secuencia de vocabulario de la incrustación de palabras de entrada

import torch
import torch.nn.functional as F

# 构造源句子与目标句子的张量
src_len=torch.Tensor([2,4]).to(torch.int32)# tensor([2, 4], dtype=torch.int32)
tgt_len=torch.Tensor([4,3]).to(torch.int32)# tensor([4, 3], dtype=torch.int32)

La operación anterior construye el tensor de la oración fuente y la oración objetivo. La oración fuente contiene 2 oraciones, la longitud de la primera oración es 2 y la longitud de la segunda oración es 4; la oración objetivo es la misma. La oración de origen y la oración de destino anteriores están representadas por un tensor cuya dimensión es la longitud máxima de la secuencia de oraciones (5), y se utilizan principalmente las operaciones pad, unsqueeze y cat . Este paso sigue siendo muy práctico en el proceso de construcción de la formación. datos.

# 1.直接构造两个tensor
src_seq=[torch.randint(1,max_src_num,(L,)) for L in src_len]
# [tensor([4, 3]), tensor([7, 3, 1, 1])]

# 2.利用F.pad将序列填充至最大长度5
src_seq=[F.pad(torch.randint(1,max_src_num,(L,)),(0,max_src_len-L) )for L in src_len]
# [tensor([4, 3, 0, 0, 0]), tensor([7, 3, 1, 1, 0])]

# 3.利用torch.unsequeeze将一维张量(5)变为二维张量(1,5)
src_seq=[torch.unsqueeze(F.pad(torch.randint(1,max_src_num,(L,)),(0,max_src_len-L)),0)\
                   for L in src_len]
# [tensor([[6, 5, 0, 0, 0]]), tensor([[3, 7, 1, 4, 0]])]

# 4.使用cat拼接
src_seq=torch.cat([torch.unsqueeze(F.pad(torch.randint(1,max_src_num,(L,)),\
                                         (0,max_src_len-L)),0) for L in src_len])
'''
tensor([[6, 5, 0, 0, 0],
        [3, 7, 1, 4, 0]])
'''

# 同理,使用pad、unsqueeze、cat构造tgt张量
tgt_seq=torch.cat([torch.unsqueeze(F.pad(torch.randint(1,max_tgt_num,(L,))\
                                         (0,max_tgt_len-L)),0) for L in tgt_len])
'''
tensor([[4, 2, 4, 3, 0],
        [4, 5, 2, 0, 0]])
'''

Las operaciones anteriores usan el índice de palabras para construir la oración de origen y la oración de destino, y hacen el relleno. El valor predeterminado de relleno es 0. Nota:

1.torch.randint(low,high,size) se utiliza para generar una secuencia de oraciones y generar un tensor con elementos del 1 al 8, donde el tipo de tamaño debe ser tupla, lo que indica el tamaño de la secuencia de salida final

2.F.pad(entrada,almohadilla) llena un tensor N-dimensional de entrada en la almohadilla. El tipo de almohadilla es tupla. El primer elemento de la tupla indica la dimensión del relleno, y la segunda dimensión indica la cantidad de relleno. , por lo que aquí se refiere a llenar cada tensor unidimensional con un valor de longitud 5

3.torch.unsqueeze(input,dim) devuelve un nuevo tensor con un tamaño de 1 y lo inserta en la posición especificada dim. El tensor devuelto comparte los mismos datos subyacentes que este tensor. El rango de dim está entre [-input.dim() - 1, input.dim() + 1)y los parámetros de dim negativos se convierten de acuerdo con las siguientes operaciones dim = dim + input.dim() + 1Por ejemplo, dim=0 y dim=-2 tienen el mismo efecto de relleno en este caso de uso.

4.1.3 Construcción incrustada

Ahora se ha determinado que tanto src como tgt son tensores bidimensionales, y la posición de dim=1 es una oración.La oración es larga o corta, pero usaremos la operación pad para alinearla. Lo siguiente convertirá cada palabra de la oración en un vector de palabra de longitud model_dim

# 前面已经定义model_dim=16,调用nn.Embedding构造词向量
src_embedding_table=nn.Embedding(max_src_num,model_dim)
tgt_embedding_table=nn.Embedding(max_tgt_num,model_dim)
src_embedding=src_embedding_table(src_seq)
tgt_embedding=tgt_embedding_table(tgt_seq)

print(src_embedding_table.weight)# 得到一个table,是src词表的权重
print(src_seq)# src词表的索引
print(src_embedding)# 按照索引取出的权重
print(src_embedding.shape)# shape中前两维不变,最后一维由原来的标量变为一维张量,所以维数变为三维

imprimir:

# print(src_embedding_table.weight)
tensor([[-0.8922,  1.6868, -0.6418,  0.3140, -0.8981, -0.2032, -1.1533,  1.4407,
          0.6462, -0.0218, -0.2189, -0.5544, -1.1963, -0.8797, -1.4896, -1.1375],
        [-0.6679,  1.3612,  0.0115, -0.7135, -0.7511, -0.2279,  0.9266,  0.6085,
         -0.0658, -0.7805,  0.1241, -0.5363, -0.0310,  0.1398, -0.2880, -0.3838],
        [-0.6014, -0.5428, -1.9882, -0.7380, -0.8123, -0.5486, -0.7666,  0.4053,
         -0.7813, -0.5849, -0.3628, -0.7975,  0.4671,  2.0936,  0.5843, -1.3917],
        [ 1.8526,  0.5546,  0.1360, -0.6861, -1.5588, -0.8645, -0.5102, -0.4818,
         -0.7090,  1.7046, -0.9654,  0.0745, -0.5227, -0.4729, -0.6181,  0.3763],
        [-0.5552, -0.8068, -1.2071, -1.9199,  1.1797,  0.7980,  0.0243,  0.5780,
         -1.0205,  0.3595,  0.1759, -1.7504,  0.1044,  0.1721,  1.3329,  2.4223],
        [ 0.0843, -0.6042, -0.8001, -1.7500,  1.7444,  0.5514,  0.3341, -0.3628,
          0.0701, -0.1078, -0.0630, -2.8175, -0.3428, -0.7154, -0.1690,  0.9915],
        [ 2.6575, -1.9004, -0.7635,  0.7862,  1.9882, -2.4753, -0.0353,  0.2691,
         -0.1716,  0.0885, -0.1151, -0.6685,  0.5251,  0.4102,  1.5151, -0.1743],
        [-0.4236,  1.5056,  1.6229, -1.1891, -0.3939,  0.0631,  1.0910,  0.4685,
         -0.1328, -0.6178,  0.0780,  1.4527, -0.5974, -0.9052,  0.1527, -0.5200]],
       requires_grad=True)
# print(src_seq)
tensor([[3, 4, 0, 0, 0],
        [2, 6, 7, 5, 0]])
# print(src_embedding)
tensor([[[-0.4144,  0.8002, -0.7077,  1.0783,  0.2542,  0.6446,  0.1157,
           0.3006,  1.3689, -1.8104, -0.4804,  1.5375,  0.2803,  0.3098,
          -0.4550,  0.2727],# 对应weight中的第4个权重向量
         [-1.0427,  2.1106,  0.4897, -1.3543, -1.2303,  0.4397,  0.9002,
          -0.2692,  0.4160,  0.6407, -0.2677, -1.3330,  0.9792, -0.8851,
           0.8809,  2.2589],# 对应weight中的第5个权重向量
         [ 1.4646, -1.2331,  0.7219, -0.1666, -0.0202, -1.0846, -0.6944,
           0.4036,  0.1553,  0.7446, -1.4565,  0.6299, -1.2328, -2.6654,
          -0.7258, -0.7802],
         [ 1.4646, -1.2331,  0.7219, -0.1666, -0.0202, -1.0846, -0.6944,
           0.4036,  0.1553,  0.7446, -1.4565,  0.6299, -1.2328, -2.6654,
          -0.7258, -0.7802],
         [ 1.4646, -1.2331,  0.7219, -0.1666, -0.0202, -1.0846, -0.6944,
           0.4036,  0.1553,  0.7446, -1.4565,  0.6299, -1.2328, -2.6654,
          -0.7258, -0.7802]],

        [[-1.4822, -0.1679, -1.3464,  1.0757,  1.2704, -0.9263,  0.2799,
          -0.9830, -0.6915, -0.5027,  1.4015, -0.8211,  2.2959,  0.5048,
          -0.6541,  1.9831],
         [-1.4967,  0.1112,  0.4351, -1.1601,  0.0701,  1.8887,  1.2096,
          -0.5478, -0.9204,  0.1664,  0.7460,  0.0595,  0.5841, -1.7000,
          -0.1230, -0.2716],
         [ 0.5547, -0.2306,  0.9880,  0.0605, -0.0773,  0.2532, -0.9352,
           1.9237, -0.8470,  0.0512,  0.1840,  2.5656,  0.9901, -0.3262,
          -0.8087,  1.1227],
         [-0.5701,  0.7893, -1.8570,  0.0964, -0.9695,  1.0017, -0.3000,
          -1.8581,  0.6612,  0.1198,  0.4757, -0.1629, -1.7126,  1.1889,
          -1.0428, -0.2390],
         [ 1.4646, -1.2331,  0.7219, -0.1666, -0.0202, -1.0846, -0.6944,
           0.4036,  0.1553,  0.7446, -1.4565,  0.6299, -1.2328, -2.6654,
          -0.7258, -0.7802]]], grad_fn=<EmbeddingBackward>)
# print(src_embedding.shape)
torch.Size([2, 5, 16])

En el proceso anterior, el peso de cada vector de palabra se obtiene a través de nn.Embedding y luego se obtiene directamente en src_embedding de acuerdo con el índice, como tensor([[3, 4, 0, 0, 0],]) corresponde a el cuarto en peso, pesos vectoriales de 5 palabras, la incrustación final se obtiene a través de las operaciones anteriores

4.1.4 Construir PosiciónIncrustación

¿Por qué es necesaria la codificación posicional?

  • Para cualquier idioma, la posición y el orden de las palabras en una oración son muy importantes. No solo son una parte integral de la estructura gramatical de una oración, sino también un concepto importante para expresar la semántica. Si una palabra se coloca en una posición diferente o en un orden diferente, el significado de la oración completa puede desviarse, por ejemplo:

    No me gusta la historia de la película, pero me gusta el elenco.
    Me gusta la historia de la película, pero no me gusta el elenco.

​ Las palabras utilizadas en las dos oraciones anteriores son exactamente iguales, pero el significado de la oración expresada es completamente opuesto, así que considere introducir información sobre el orden de las palabras para distinguir el significado de las dos oraciones.

  • El modelo Transformer abandona RNN y CNN como el modelo básico de aprendizaje de secuencias. Sabemos que la propia red neuronal cíclica es una estructura secuencial, que contiene inherentemente la información de posición de las palabras en la secuencia. Cuando se descarta la estructura de la red neuronal cíclica y se usa completamente Atención, la información del orden de las palabras se perderá y el modelo no tendrá forma de conocer la información de posición relativa y absoluta de cada palabra en la oración. Por lo tanto, es necesario agregar la señal de orden de palabras al vector de palabras para ayudar al modelo a aprender esta información.La codificación posicional es el método utilizado para resolver este problema.

incrustación de posición的公式如下:
PE ( pos , 2 i ) = sin ⁡ pos 1000 0 2 i / dmodel PE ( pos , 2 i + 1 ) = cos ⁡ pos 1000 0 2 i / dmodel PE_{(pos,2i)} =\sin\frac {pos}{10000^{2i/d_{modelo}}}\\ PE_{(pos,2i+1)}=\cos\frac {pos}{10000^{2i/d_{modelo} }}educación física( pos , 2 i ) _=pecado1000 02 i / dm o d e lpos _educación física( pos , 2 i + 1 ) _=porque1000 02 i / dm o d e lpos _
Entre ellos, pos posp os es el número de secuencia de la palabra que aparece en el vocabulario,iii es el número de dimensión. Primero podemos generar un tensor pe_embedding lleno con 0 de la misma dimensión y luego llenarlo con las reglas anteriores.

# 构造pos和i的matrix
pos_mat=torch.arange(max_src_len).reshape((-1,1))
i_mat=torch.pow(10000,torch.arange(0,model_dim,2).reshape((1,-1))/model_dim)

# 构造position_embedding_table
pe_embedding_table=torch.zeros(max_src_len,model_dim)
pe_embedding_table[:,0::2]=torch.sin(pos_mat/i_mat)
pe_embedding_table[:,1::2]=torch.cos(pos_mat/i_mat)

# 得到position table后再利用nn.embedding得到其权重
pe_embedding=nn.Embedding(max_position_len,model_dim)
pe_embedding.weight=nn.Parameter(pe_embedding_table,requires_grad=False)

# 构造位置张量从position embedding中取向量,形如[0,1,2,...,max(src_len)]
src_pos=torch.cat([torch.unsqueeze(torch.arange(max_src_len),0) for _ in src_len]).to(torch.long)
tgt_pos=torch.cat([torch.unsqueeze(torch.arange(max_tgt_len),0) for _ in tgt_len]).to(torch.long)

# 取出权重向量组成position embedding
src_pe_embedding=pe_embedding(src_pos)
tgt_pe_embedding=pe_embedding(tgt_pos)
print(src_pe_embedding.shape)# torch.Size([2, 5, 16])

# 此时,可以将embedding和position embedding相加得到word embedding
word_embedding=src_pe_embedding+src_embedding
print(word_embedding.shape)# torch.Size([2, 5, 16])

Se puede ver que el tamaño de la incrustación de posición construida es el mismo que el tamaño de la secuencia de vocabulario, por lo que puede sumar los dos directamente para obtener la incrustación de palabras

4.2 Demostrar la importancia de la escala en Softmax a través de un ejemplo

En el artículo sobre la atención, el autor utiliza la escala de atención de producto escalar para QKT QK^{T}Q KT está escalada, la fórmula es la siguiente
A atención ( Q , K , V ) = softmax ( QKT dk ) V Atención(Q,K,V)=softmax(\frac{QK^{T}}{\sqrt d_k} )VA t e n c i ó n ( Q ,K ,V )=entonces f t máx ( _d kQ KT) El objetivo principal de V
es fijar su varianza en 1 para evitar el desequilibrio de peso causado por una varianza excesiva (cuanto más grande, más grande, más pequeño, más pequeño, como se demuestra en el siguiente ejemplo).

score=torch.randn(5)# tensor([ 0.6052, -0.2023, -1.3294, -0.2546,  1.5445])
prob=F.softmax(score,0)# tensor([0.2187, 0.0976, 0.0316, 0.0926, 0.5595])

Se puede ver que cuando generamos aleatoriamente cinco valores que obedecen a la distribución normal, la diferencia entre los valores después de softmax no es grande

# score的缩放在softmax上并不是线性的,而是大的越大小的越小
alpha1,alpha2=0.1,10
prob1,prob2=F.softmax(score*alpha1,0),F.softmax(score*alpha2,-1)
print(prob1,prob2)
# tensor([0.2100, 0.1937, 0.1730, 0.1927, 0.2306])
# tensor([8.3344e-05, 2.5940e-08, 3.3038e-13, 1.5366e-08, 9.9992e-01])

De manera similar, también podemos usar la función jacobiana para observar la matriz jacobiana de sorce (equivalente al gradiente en el proceso de entrenamiento)

def softmax_func(score):
    return F.softmax(score)
jaco_mat1=torch.autograd.functional.jacobian(softmax_func,score*alpha1)
jaco_mat2=torch.autograd.functional.jacobian(softmax_func,score*alpha2)
tensor([[ 0.1659, -0.0407, -0.0363, -0.0405, -0.0484],
        [-0.0407,  0.1562, -0.0335, -0.0373, -0.0447],
        [-0.0363, -0.0335,  0.1431, -0.0333, -0.0399],
        [-0.0405, -0.0373, -0.0333,  0.1555, -0.0444],
        [-0.0484, -0.0447, -0.0399, -0.0444,  0.1774]])
tensor([[ 8.3337e-05, -2.1620e-12, -2.7535e-17, -1.2807e-12, -8.3337e-05],
        [-2.1620e-12,  2.5940e-08, -8.5701e-21, -3.9860e-16, -2.5938e-08],
        [-2.7535e-17, -8.5701e-21,  3.3038e-13, -5.0766e-21, -3.3035e-13],
        [-1.2807e-12, -3.9860e-16, -5.0766e-21,  1.5366e-08, -1.5365e-08],
        [-8.3337e-05, -2.5938e-08, -3.3035e-13, -1.5365e-08,  8.3440e-05]])

Se puede ver que cuando reducimos la puntuación, la distribución del peso de cada elemento es relativamente equilibrada y el gradiente es más fácil de optimizar; pero si se aumenta la puntuación, la distribución del peso de cada elemento es extremadamente desequilibrada y el gradiente desaparece. . Por lo tanto, la estandarización de los pesos de los elementos es aún más importante para el mecanismo de atención.

4.3 Construya la máscara de atención automática del codificador

La máscara de autoatención es diferente de la máscara en la etapa del Decodificador, porque la oración que ingresamos puede no tener un dmodel d_model preestablecidodmEl o del es tan largo y hay relleno en el medio. No hay necesidad de calcular la similitud entre estas partes rellenas y otras partes , así que enmascárelas directamente para ahorrar recursos informáticos.
Atención ( Q , K , V ) = softmax ( QKT dk ) V Atención(Q,K,V)=softmax(\frac{QK^{T}}{\sqrt d_k})VA t e n c i ó n ( Q ,K ,V )=entonces f t máx ( _d kQ KT) La parte de la máscara V
esQKT QK^TQ KEl resultado de T , debido aQKT QK^TQ KLo que obtiene T es una matriz cuadrada. Si su tamaño es HxH, entonces el tamaño de la matriz de máscara también debe ser HxH. Construyamos la matriz de máscara:

# mask_shape:[batch_size,max_src_len,max_src_len]
valid_encoder_pos=torch.unsqueeze(torch.cat([torch.unsqueeze(F.pad(torch.ones(L),(0,max_src_len-L)),0) for L in src_len]),2)
print(valid_encoder_pos)# [tensor([1., 1., 0., 0., 0.]), tensor([1., 1., 1., 1., 0.])]

El resultado anterior muestra que la posición efectiva de la primera oración son los primeros dos dígitos, y la posición efectiva de la segunda oración son los primeros cuatro dígitos Entonces, para la matriz de atención de la primera oración, la posición efectiva debe ser el índice 0 y 1 respectivamente en la fila, la posición en la columna, la segunda oración es la misma, calculemos e imprimamos para ver

valid_encoder_pos_mat=torch.bmm(valid_encoder_pos,valid_encoder_pos.transpose(1,2))
print(valid_encoder_pos_mat.shape)
print(src_len)
print(valid_encoder_pos_mat)

Imprimir resultado:

torch.Size([2, 5, 5])
tensor([2, 4], dtype=torch.int32)
tensor([[[1., 1., 0., 0., 0.],
         [1., 1., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.]],

        [[1., 1., 1., 1., 0.],
         [1., 1., 1., 1., 0.],
         [1., 1., 1., 1., 0.],
         [1., 1., 1., 1., 0.],
         [0., 0., 0., 0., 0.]]])

Bajo el mecanismo de autoatención, cada oración calcula la similitud consigo misma, y ​​cada oración obtendrá una matriz de similitud.Para evitar que las posiciones llenas participen en la operación, necesitamos usar la matriz de máscara anterior para enmascarar. Lo anterior es una matriz efectiva, 1 significa que la posición es válida y 0 significa que la posición está llena En la práctica, usamos la matriz inversa de la matriz anterior.

nvalid_encoder_pos_mat=1-valid_encoder_pos_mat
print(invalid_encoder_pos_mat)

La anterior es la matriz inválida utilizada en la máscara. En este momento, 0 significa válido y 1 significa relleno. Convirtiendo la matriz anterior a tipo bool obtendrá la máscara final

mask_encoder_self_attention=invalid_encoder_pos_mat.to(torch.bool)
print(mask_encoder_self_attention) 

La siguiente es la matriz de máscara, Verdadero significa que esta posición debe enmascararse, Falso significa que esta posición no puede enmascararse (posición válida)

tensor([[[False, False,  True,  True],
         [False, False,  True,  True],
         [ True,  True,  True,  True],
         [ True,  True,  True,  True]],

        [[False, False, False, False],
         [False, False, False, False],
         [False, False, False, False],
         [False, False, False, False]]])

Complete la matriz de máscara anterior en nuestra entrada:

score_demo=torch.randn(batch_size,max(src_len),max(src_len))
masked_score=score_demo.masked_fill(mask_encoder_self_attention,-np.inf)
print(score_demo)
print(masked_score)
print(p)

Puedes ver la salida final de softmax

# score_demo
tensor([[[ 1.9743e+00, -6.5912e-01,  2.4752e+00, -1.1645e+00,  3.0591e-01],
         [-1.5183e+00,  6.0389e-01,  2.6187e-02, -1.9337e+00,  7.4799e-01],
         [-5.0436e-01,  1.9787e+00,  1.0392e+00, -1.1386e-01, -1.1774e+00],
         [ 3.2022e-01,  2.5618e-01, -2.2645e-01,  5.5852e-02, -8.5724e-02],
         [-9.7226e-01, -2.2245e-01,  5.5039e-01,  1.5990e+00,  1.5704e+00]],

        [[-1.1615e+00,  8.0501e-01, -8.2313e-01,  1.2514e-03,  1.8805e+00],
         [-1.0990e+00, -3.3807e-01,  1.3614e+00, -4.5983e-01, -1.7561e-01],
         [ 1.9764e-02, -8.4881e-01,  7.7464e-01, -6.6284e-01,  4.3511e-01],
         [-1.8832e+00, -1.2018e+00, -7.7904e-02, -9.5873e-01,  2.5455e-01],
         [-1.6526e+00, -2.5848e-01, -1.0722e+00,  3.3856e-01,  1.1735e+00]]])
# masked_score
tensor([[[ 1.9743e+00, -6.5912e-01,        -inf,        -inf,        -inf],
         [-1.5183e+00,  6.0389e-01,        -inf,        -inf,        -inf],
         [       -inf,        -inf,        -inf,        -inf,        -inf],
         [       -inf,        -inf,        -inf,        -inf,        -inf],
         [       -inf,        -inf,        -inf,        -inf,        -inf]],

        [[-1.1615e+00,  8.0501e-01, -8.2313e-01,  1.2514e-03,        -inf],
         [-1.0990e+00, -3.3807e-01,  1.3614e+00, -4.5983e-01,        -inf],
         [ 1.9764e-02, -8.4881e-01,  7.7464e-01, -6.6284e-01,        -inf],
         [-1.8832e+00, -1.2018e+00, -7.7904e-02, -9.5873e-01,        -inf],
         [       -inf,        -inf,        -inf,        -inf,        -inf]]])
# p
tensor([[[0.9330, 0.0670, 0.0000, 0.0000, 0.0000],
         [0.1070, 0.8930, 0.0000, 0.0000, 0.0000],
         [   nan,    nan,    nan,    nan,    nan],
         [   nan,    nan,    nan,    nan,    nan],
         [   nan,    nan,    nan,    nan,    nan]],

        [[0.0784, 0.5606, 0.1100, 0.2509, 0.0000],
         [0.0597, 0.1278, 0.6993, 0.1132, 0.0000],
         [0.2468, 0.1035, 0.5250, 0.1247, 0.0000],
         [0.0864, 0.1707, 0.5252, 0.2177, 0.0000],
         [   nan,    nan,    nan,    nan,    nan]]])

Supongo que te gusta

Origin blog.csdn.net/weixin_43427721/article/details/127631147
Recomendado
Clasificación