Transformer:基于注意力机制的序列转换模型

Transformer:基于注意力机制的序列转换模型

最近,我研究了一篇非常有趣的论文——《Attention Is All You Need》,由Google Brain团队的Ashish Vaswani等人撰写。这篇论文提出了一种全新的神经网络架构——Transformer,它完全基于注意力机制,摒弃了传统的循环(RNN)和卷积(CNN)网络结构,用于序列转换任务,如机器翻译和英语成分句法分析等。Transformer在机器翻译任务上取得了当时最佳的性能,并且训练速度更快,训练成本更低。接下来,我将详细介绍Transformer架构及其核心机制。

论文链接:http://链接: https://pan.baidu.com/s/1OTl2xik7d6DNtJjOLqmu4A 提取码: zhyy

一、背景知识

在传统的序列转换模型中,循环神经网络(RNN)及其变体(如LSTM和GRU)一直是主流方法。这些模型通过在输入和输出序列的符号位置上进行计算,生成一系列隐藏状态。然而,这种固有的序列性质使得模型无法在训练时进行并行化处理,导致训练效率低下。为了解决这一问题,注意力机制被引入到序列转换模型中,允许模型在不考虑输入或输出序列中符号距离的情况下,对依赖关系进行建模。但以往的注意力机制大多与循环网络结合使用。而Transformer架构则完全依赖于注意力机制,彻底摒弃了循环和卷积操作。

二、Transformer架构

Transformer架构的核心是多头注意力机制(Multi-Head Attention)和位置编码(Positional Encoding)。它包含编码器(encoder)和解码器(decoder),编码器由多个相同的层堆叠而成,每层包含多头自注意力机制和逐位置的全连接前馈网络。解码器的结构类似,但额外包含一个针对编码器输出的多头注意力层,并且在自注意力层中加入了掩码以保持自回归性质。

(一)多头注意力机制

多头注意力机制是Transformer架构的核心。它通过将查询(queries)、键(keys)和值(values)投影到多个不同的表示子空间,并在这些子空间中并行执行注意力函数,允许模型从不同角度关注信息。具体来说,多头注意力机制将输入的查询、键和值分别通过不同的线性投影,得到多个“头”的查询、键和值。然后,每个“头”都独立地计算注意力权重,并将这些权重应用于对应的值,得到加权求和的结果。最后,将所有“头”的结果拼接起来,并通过一个线性投影层得到最终的输出。

(二)缩放点积注意力

缩放点积注意力(Scaled Dot-Product Attention)是多头注意力机制中使用的一种注意力函数。它计算查询和键的点积,除以键向量维度的平方根以防止梯度消失或爆炸,然后应用softmax函数获得权重。具体公式如下: Attention(Q,K,V)=softmax(dk​​QKT​)V 其中,Q、K和V分别是查询、键和值矩阵,dk​是键向量的维度。

(三)位置编码

由于Transformer架构中没有循环或卷积操作,因此需要一种方法来注入序列中标记的顺序信息。位置编码(Positional Encoding)就是为了解决这个问题而设计的。论文中使用正弦和余弦函数的不同频率来表示位置编码。具体来说,位置编码的每个维度对应一个正弦或余弦函数,其频率随着维度的变化而变化。这样,模型可以通过位置编码来感知序列中标记的顺序关系。

三、训练与实验

(一)训练数据与批处理

Transformer模型在WMT 2014英德和英法翻译数据集上进行了训练。英德数据集包含约450万句对,英法数据集包含约3600万句对。为了提高训练效率,论文采用了字节对编码(BPE)对句子进行编码,并根据句子长度对句对进行批处理。

(二)硬件与训练计划

Transformer模型在8个NVIDIA P100 GPU上进行训练。基础模型训练了10万步,大约需要12小时;大型模型训练了30万步,大约需要3.5天。通过使用多GPU并行训练,大大提高了模型的训练速度。

(三)优化器

论文中使用了Adam优化器,并根据训练步数动态调整学习率。具体的学习率调整公式如下:

其中,dmodel​是模型的维度,step_num是当前训练步数,warmup_steps是预热步数。这种学习率调整策略在训练初期快速提高学习率,然后逐渐降低学习率,有助于模型更快地收敛。

(四)正则化

为了防止模型过拟合,论文采用了残差dropout和标签平滑两种正则化方法。残差dropout在每个子层的输出上应用dropout操作,然后将其添加到子层的输入上,并进行归一化处理。标签平滑则通过将目标标签的概率分布进行平滑处理,使模型更加鲁棒。

四、实验结果

(一)机器翻译

Transformer模型在WMT 2014英德翻译任务上取得了28.4的BLEU分数,超越了所有先前的最佳模型,包括集成模型。在英法翻译任务上,取得了41.8的BLEU分数,成为新的单模型最佳成绩。这些结果表明,Transformer模型在机器翻译任务上具有显著的性能优势。

(二)英语成分句法分析

Transformer模型还在英语成分句法分析任务上进行了实验。在Penn Treebank数据集上,Transformer(4层)在仅使用WSJ数据集时取得了91.3的F1分数,在半监督设置下取得了92.7的F1分数,接近最佳性能。这表明Transformer模型不仅在机器翻译任务上表现出色,还具有良好的泛化能力,能够应用于其他自然语言处理任务。

五、总结

Transformer模型通过完全基于注意力机制的设计,显著提高了训练速度和翻译质量,成为当时机器翻译任务的新标杆。多头注意力机制和位置编码是Transformer架构的核心,它们使得模型能够更有效地处理序列数据。此外,Transformer模型在英语成分句法分析任务上的良好表现也证明了其在不同任务上的泛化能力。未来,我们可以将Transformer模型应用于更多的自然语言处理任务,如文本生成、问答系统等,探索其在不同领域的应用潜力。


如何复现Transformer模型:从零开始的实践指南

Google Brain团队在论文《Attention Is All You Need》中提出的一种革命性的序列转换模型。它完全基于注意力机制,摒弃了传统的循环和卷积网络结构,在机器翻译等任务上取得了当时最佳的性能。今天,我将分享如何从零开始复现这个模型,包括环境搭建、代码实现和训练过程。

一、环境搭建

复现Transformer模型需要一个合适的深度学习环境。我推荐使用以下工具和框架:

  • Python:建议使用Python 3.7或更高版本。

  • TensorFlow 2.x:Transformer模型的官方实现基于TensorFlow。你可以通过以下命令安装TensorFlow:

    bash复制

    pip install tensorflow
  • 其他依赖:安装一些常用的库,如numpymatplotlib等:

    bash复制

    pip install numpy matplotlib

二、数据准备

Transformer模型在机器翻译任务上表现优异,因此我们可以使用WMT 2014英德翻译数据集进行复现。数据集可以从以下链接下载:

  • WMT 2014英德数据集

下载数据后,需要对数据进行预处理,包括分词、构建词汇表和编码等。可以使用subword-nmt工具进行字节对编码(BPE):

bash复制

pip install subword-nmt

以下是一个简单的预处理脚本示例:

Python复制

import os
from subword_nmt.apply_bpe import BPE
from subword_nmt.learn_bpe import learn_bpe

# 学习BPE
learn_bpe(open("data/train.en", "r"), open("data/train.de", "r"), open("data/bpe.codes", "w"), num_symbols=32000)

# 应用BPE
bpe = BPE(open("data/bpe.codes", "r"))
with open("data/train.en.bpe", "w") as f_out:
    for line in open("data/train.en", "r"):
        f_out.write(bpe.process_line(line))

with open("data/train.de.bpe", "w") as f_out:
    for line in open("data/train.de", "r"):
        f_out.write(bpe.process_line(line))

三、模型实现

Transformer模型的实现可以参考论文中的架构描述。以下是基于TensorFlow 2.x的简化实现代码:

1. 位置编码

位置编码用于注入序列中标记的顺序信息:

Python复制

import tensorflow as tf
import numpy as np

def positional_encoding(position, d_model):
    angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                            np.arange(d_model)[np.newaxis, :],
                            d_model)
    sines = np.sin(angle_rads[:, 0::2])
    cosines = np.cos(angle_rads[:, 1::2])
    pos_encoding = np.concatenate([sines, cosines], axis=-1)
    return tf.cast(pos_encoding, dtype=tf.float32)

def get_angles(pos, i, d_model):
    angle_rates = 1 / np.power(10000, (2 * (i // 2)) / np.float32(d_model))
    return pos * angle_rates

2. 多头注意力机制

多头注意力机制是Transformer的核心:

Python复制

class MultiHeadAttention(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        self.num_heads = num_heads
        self.d_model = d_model
        assert d_model % self.num_heads == 0
        self.depth = d_model // self.num_heads
        self.wq = tf.keras.layers.Dense(d_model)
        self.wk = tf.keras.layers.Dense(d_model)
        self.wv = tf.keras.layers.Dense(d_model)
        self.dense = tf.keras.layers.Dense(d_model)

    def split_heads(self, x, batch_size):
        x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))
        return tf.transpose(x, perm=[0, 2, 1, 3])

    def call(self, v, k, q, mask):
        batch_size = tf.shape(q)[0]
        q = self.wq(q)
        k = self.wk(k)
        v = self.wv(v)
        q = self.split_heads(q, batch_size)
        k = self.split_heads(k, batch_size)
        v = self.split_heads(v, batch_size)
        scaled_attention = scaled_dot_product_attention(q, k, v, mask)
        scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])
        concat_attention = tf.reshape(scaled_attention, (batch_size, -1, self.d_model))
        output = self.dense(concat_attention)
        return output

def scaled_dot_product_attention(q, k, v, mask):
    matmul_qk = tf.matmul(q, k, transpose_b=True)
    dk = tf.cast(tf.shape(k)[-1], tf.float32)
    scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)
    if mask is not None:
        scaled_attention_logits += (tf.cast(mask, tf.float32) * -1e9)
    attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)
    output = tf.matmul(attention_weights, v)
    return output

3. 编码器和解码器

编码器和解码器的实现基于多头注意力机制和前馈网络:

Python复制

class EncoderLayer(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads, dff, rate=0.1):
        super(EncoderLayer, self).__init__()
        self.mha = MultiHeadAttention(d_model, num_heads)
        self.ffn = point_wise_feed_forward_network(d_model, dff)
        self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.dropout1 = tf.keras.layers.Dropout(rate)
        self.dropout2 = tf.keras.layers.Dropout(rate)

    def call(self, x, training, mask):
        attn_output = self.mha(x, x, x, mask)
        attn_output = self.dropout1(attn_output, training=training)
        out1 = self.layernorm1(x + attn_output)
        ffn_output = self.ffn(out1)
        ffn_output = self.dropout2(ffn_output, training=training)
        out2 = self.layernorm2(out1 + ffn_output)
        return out2

def point_wise_feed_forward_network(d_model, dff):
    return tf.keras.Sequential([
        tf.keras.layers.Dense(dff, activation='relu'),
        tf.keras.layers.Dense(d_model)
    ])

4. Transformer模型

将编码器和解码器组合成完整的Transformer模型:

Python复制

class Transformer(tf.keras.Model):
    def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, target_vocab_size, pe_input, pe_target, rate=0.1):
        super(Transformer, self).__init__()
        self.encoder = Encoder(num_layers, d_model, num_heads, dff, input_vocab_size, pe_input, rate)
        self.decoder = Decoder(num_layers, d_model, num_heads, dff, target_vocab_size, pe_target, rate)
        self.final_layer = tf.keras.layers.Dense(target_vocab_size)

    def call(self, inp, tar, training, enc_padding_mask, look_ahead_mask, dec_padding_mask):
        enc_output = self.encoder(inp, training, enc_padding_mask)
        dec_output, attention_weights = self.decoder(tar, enc_output, training, look_ahead_mask, dec_padding_mask)
        final_output = self.final_layer(dec_output)
        return final_output, attention_weights

四、训练过程

训练Transformer模型需要准备训练数据、定义优化器和损失函数,并进行训练循环。以下是一个简化的训练代码示例:

1. 准备训练数据

Python复制

import tensorflow_datasets as tfds

def load_dataset():
    dataset, metadata = tfds.load('wmt14_translate/de-en', with_info=True, as_supervised=True)
    train_dataset, val_dataset = dataset['train'], dataset['validation']
    tokenizer_en = tfds.features.text.SubwordTextEncoder.build_from_corpus(
        (en.numpy() for en, de in train_dataset), target_vocab_size=2**13)
    tokenizer_de = tfds.features.text.SubwordTextEncoder.build_from_corpus(
        (de.numpy() for en, de in train_dataset), target_vocab_size=2**13)
    return train_dataset, val_dataset, tokenizer_en, tokenizer_de

train_dataset, val_dataset, tokenizer_en, tokenizer_de = load_dataset()

2. 定义优化器和损失函数

Python复制

optimizer = tf.keras.optimizers.Adam(1e-4, beta_1=0.9, beta_2=0.98, epsilon=1e-9)
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')

def loss_function(real, pred):
    mask = tf.math.logical_not(tf.math.equal(real, 0))
    loss_ = loss_object(real, pred)
    mask = tf.cast(mask, dtype=loss_.dtype)
    loss_ *= mask
    return tf.reduce_sum(loss_) / tf.reduce_sum(mask)

3. 训练循环

Python复制

train_step_signature = [
    tf.TensorSpec(shape=(None, None), dtype=tf.int64),
    tf.TensorSpec(shape=(None, None), dtype=tf.int64),
]

@tf.function(input_signature=train_step_signature)
def train_step(inp, tar):
    tar_inp = tar[:, :-1]
    tar_real = tar[:, 1:]
    enc_padding_mask, combined_mask, dec_padding_mask = create_masks(inp, tar_inp)
    with tf.GradientTape() as tape:
        predictions, _ = transformer(inp, tar_inp, True, enc_padding_mask, combined_mask, dec_padding_mask)
        loss = loss_function(tar_real, predictions)
    gradients = tape.gradient(loss, transformer.trainable_variables)
    optimizer.apply_gradients(zip(gradients, transformer.trainable_variables))
    return loss

for epoch in range(10):
    total_loss = 0
    for (batch, (inp, tar)) in enumerate(train_dataset):
        batch_loss = train_step(inp, tar)
        total_loss += batch_loss
        if batch % 50 == 0:
            print(f'Epoch {epoch + 1} Batch {batch} Loss {batch_loss.numpy():.4f}')
    print(f'Epoch {epoch + 1} Loss {total_loss / len(train_dataset):.4f}')

五、总结

通过上述步骤,我们可以从零开始复现Transformer模型。虽然代码进行了简化,但涵盖了Transformer模型的核心部分。在实际应用中,你可能需要进一步优化代码,例如增加模型保存和加载功能、调整超参数等。

你可能感兴趣的:(论文精读,transformer,深度学习,人工智能)