Transformer 模型

一、概念

Transformer是由Google的研究团队,在2017年一篇名为《Attention Is All You Need》的论文中提出的。Transformer是把一种序列的数据转换成另一种序列的数据的一种机器学习模型的架构,一种“序列到序列(seq2seq)”的机器学习模型的架构。

Transformer模型的核心思想是完全抛弃了传统的循环神经网络(RNN)和卷积神经网络(CNN)结构,而是通过多层编码器和解码器的堆叠来实现输入序列到输出序列的映射。它使用自注意力机制(self-attention)来处理输入序列和输出序列,因此可以并行计算,极大地提高了计算效率。它可以捕捉输入序列中元素之间的关系,包括句法结构、语义信息和上下文信息等。通过计算每个元素对其他所有元素的权重,自注意力机制可以发现不同元素之间的相互依赖,从而帮助模型更好地理解文本的结构和语义信息,广泛应用于自然语言处理(NLP)任务如机器翻译、文本摘要、对话生成等。

Transformer模型的创新点就在于引入了自注意力机制和位置编码,使得模型能够更好地处理长距离依赖关系和位置信息。这种结构的设计使得Transformer模型在NLP任务中取得了很好的效果,并成为了当前最主流和有效的模型之一。

二、模型

在Transformer中,注意力机制允许模型在序列中的不同位置之间进行转移,从而允许模型在序列中查找相关信息。注意力机制由一个注意力层和一个自注意力层组成。注意力层计算权重并将这些权重应用于输入序列中的每个位置,从而实现注意力机制。自注意力层则允许模型在计算某个位置的表示时,将其他位置的信息考虑在内。这种注意力机制允许模型在处理长序列时更好地捕捉上下文信息。

多头注意力是Transformer中的一种重要技术,它允许模型在不同的空间中并行注意力,从而更好地学习序列中的长期依赖关系。这意味着模型可以使用不同的头来关注不同的信息,从而更好地理解输入序列中的不同方面。

除了注意力机制和多头注意力,Transformer还包括其他组成部分,如位置编码(Positional Encoding)、前馈神经网络(Feed-Forward Neural Network)和残差连接(Residual Connections)等。这些组成部分共同作用,使Transformer模型能够并行处理输入序列的所有位置,大大加快了训练和推理的速度。同时,由于其采用自注意力机制,不采用RNN的时序结构,使得模型可以并行化训练,而且能够拥有全局信息。

我在B站上找到了一个视频,用形象的比方简捷地讲述了Transformer模型的结构。

【【Transformer模型】曼妙动画轻松学,形象比喻贼好记】icon-default.png?t=N7T8https://www.bilibili.com/video/BV1MY41137AK?vd_source=372ac9add3d58eb99f8377072fd22480

总体架构图:

对于模型的理解,我通过查阅资料和拜读大佬的博客,有了初步认识,以下是链接:(其中内容有重叠,最好选择性阅读)

人工智能各领域跨界能手——Transformer

极智AI | 大白话解读Transformer

揭密Transformer:大模型背后的硬核技术

强化学习、Transformer模型与目标对齐:生成式人工智能初探

深度学习实战24-人工智能(Pytorch)搭建transformer模型,真正跑通transformer模型,深刻了解transformer的架构

神经网络|机器学习——图解Transformer(完整版)

Transformer 模型详解

Transformer代码完全解读

三、结构

1.编码器(Encoder)

负责将输入序列进行编码表示。它由多个相同的编码器层(Encoder Layer)堆叠而成。每个编码器层中包含自注意力机制(Self-Attention)子层和前馈神经网络(Feed-forward Neural Network)子层。

2.解码器(Decoder)

负责根据编码器的输出以及当前已生成的部分目标序列,预测下一个单词或生成整个目标序列。类似于编码器,解码器也由多个相同的解码器层(Decoder Layer)堆叠而成。每个解码器层中除了自注意力机制和前馈神经网络子层外,还包含一个编码器-解码器注意力机制(Encoder-Decoder Attention)子层。

3.自注意力机制(Self-Attention)

用于模型中的层与层之间进行交互,将每个输入与序列中的其他位置进行比较。它能够捕捉到序列中不同位置之间的依赖关系,同时避免了传统RNN的顺序处理问题,使得模型能够并行计算。

4.前馈神经网络(Feed-forward Neural Network)

用于对注意力机制的输出进行全连接层的非线性转换。它通过多层感知机(Multilayer Perceptron)将注意力机制输出的特征进行映射和转换。

5.位置编码(Positional Encoding)

用于对输入序列的位置信息进行编码,帮助模型捕捉输入序列的位置顺序。位置编码通常是通过向输入序列的嵌入向量中添加一些特殊的位置信息而得到的。

Transformer模型的整体结构是通过将编码器和解码器堆叠而成的。

它通过编码器将输入序列进行编码表示,然后通过解码器生成输出序列。每个编码器和解码器层都包含多个子层,其中自注意力机制是关键的组成部分。

四、代码

下面是一个对电影评论的情感进行分类的示例代码(正面或负面)

# 导入所需的包和模块
import tensorflow as tf
import tensorflow_datasets as tfds
from tensorflow import keras
from tensorflow.keras.layers import Input, Dense, Dropout
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.losses import SparseCategoricalCrossentropy
from tensorflow.keras.metrics import SparseCategoricalAccuracy
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.models import load_model

# 定义Transformer模型的编码器层
class EncoderLayer(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads, dff, rate=0.1):
        super(EncoderLayer, self).__init__()
        self.mha = tf.keras.layers.MultiHeadAttention(num_heads, d_model)
        self.ffn = tf.keras.Sequential([
            tf.keras.layers.Dense(dff, activation='relu'),
            tf.keras.layers.Dense(d_model)
        ])
        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):
        attn_output = self.mha(x, x, x)
        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

# 定义Transformer模型的解码器层
class DecoderLayer(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads, dff, rate=0.1):
        super(DecoderLayer, self).__init__()
        self.mha1 = tf.keras.layers.MultiHeadAttention(num_heads, d_model)
        self.mha2 = tf.keras.layers.MultiHeadAttention(num_heads, d_model)
        self.ffn = tf.keras.Sequential([
            tf.keras.layers.Dense(dff, activation='relu'),
            tf.keras.layers.Dense(d_model)
        ])
        self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.layernorm3 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.dropout1 = tf.keras.layers.Dropout(rate)
        self.dropout2 = tf.keras.layers.Dropout(rate)
        self.dropout3 = tf.keras.layers.Dropout(rate)
    
    def call(self, x, enc_output, training):
        attn1_output = self.mha1(x, x, x)
        attn1_output = self.dropout1(attn1_output, training=training)
        out1 = self.layernorm1(x + attn1_output)
        attn2_output = self.mha2(out1, enc_output, enc_output)
        attn2_output = self.dropout2(attn2_output, training=training)
        out2 = self.layernorm2(out1 + attn2_output)
        ffn_output = self.ffn(out2)
        ffn_output = self.dropout3(ffn_output, training=training)
        out3 = self.layernorm3(out2 + ffn_output)
        return out3

# 定义Transformer模型
class Transformer(Model):
    def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, target_vocab_size, rate=0.1):
        super(Transformer, self).__init__()
        self.d_model = d_model
        self.embedding = tf.keras.layers.Embedding(input_vocab_size, d_model)
        self.encoder_layers = [EncoderLayer(d_model, num_heads, dff, rate) for _ in range(num_layers)]
        self.decoder_layers = [DecoderLayer(d_model, num_heads, dff, rate) for _ in range(num_layers)]
        self.final_layer = tf.keras.layers.Dense(target_vocab_size)
    
    def call(self, inputs, targets, training):
        enc_padding_mask = self.create_padding_mask(inputs)
        dec_padding_mask = self.create_padding_mask(inputs)
        look_ahead_mask = self.create_look_ahead_mask(tf.shape(targets)[1])
        dec_target_padding_mask = self.create_padding_mask(targets)
        combined_mask = tf.maximum(dec_target_padding_mask, look_ahead_mask)
        
        enc_output = self.embedding(inputs)
        enc_output *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
        enc_output = self.positional_encoding(enc_output)
        for encoder_layer in self.encoder_layers:
            enc_output = encoder_layer(enc_output, training)
        
        dec_output = self.embedding(targets)
        dec_output *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
        dec_output = self.positional_encoding(dec_output)
        for decoder_layer in self.decoder_layers:
            dec_output = decoder_layer(dec_output, enc_output, training)
        
        output = self.final_layer(dec_output)
        return output
    
    def create_padding_mask(self, seq):
        mask = tf.cast(tf.math.equal(seq, 0), tf.float32)
        return mask[:, tf.newaxis, tf.newaxis, :]
    
    def create_look_ahead_mask(self, size):
        mask = tf.linalg.band_part(tf.ones((size, size)), -1, 0)
        return 1 - mask
    
    def positional_encoding(self, inputs):
        length = tf.shape(inputs)[1]
        position = tf.range(0, length, delta=1)
        position = tf.cast(position, tf.float32)
        position /= tf.math.pow(10000, (2 * (self.d_model // 2)) / tf.cast(self.d_model, tf.float32))
        sin = tf.math.sin(position[:, tf.newaxis])
        cos = tf.math.cos(position[:, tf.newaxis])
        pos_encoding = tf.concat([sin, cos], axis=-1)
        return inputs + pos_encoding

# 载入训练数据
dataset, info = tfds.load('imdb_reviews', with_info=True, as_supervised=True)
train_dataset, test_dataset = dataset['train'], dataset['test']

# 预处理数据
BUFFER_SIZE = 10000
BATCH_SIZE = 64
VOCAB_SIZE = 1000

train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.batch(BATCH_SIZE)
train_dataset = train_dataset.prefetch(tf.data.experimental.AUTOTUNE)

# 构建Transformer模型
num_layers = 4
d_model = 128
num_heads = 8
dff = 512

input_vocab_size = VOCAB_SIZE + 1
target_vocab_size = VOCAB_SIZE + 1

transformer = Transformer(num_layers, d_model, num_heads, dff, input_vocab_size, target_vocab_size)

# 定义优化器和损失函数
learning_rate = 0.0001
optimizer = Adam(learning_rate, beta_1=0.9, beta_2=0.98, epsilon=1e-9)
loss_object = SparseCategoricalCrossentropy(from_logits=True)

# 定义评估指标
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = SparseCategoricalAccuracy(name='train_accuracy')

# 定义训练步骤
@tf.function
def train_step(inputs, targets):
    with tf.GradientTape() as tape:
        predictions = transformer(inputs, targets[:, :-1], True)
        loss = loss_object(targets[:, 1:], predictions)
    gradients = tape.gradient(loss, transformer.trainable_variables)
    optimizer.apply_gradients(zip(gradients, transformer.trainable_variables))
    
    train_loss(loss)
    train_accuracy(targets[:, 1:], predictions)

# 进行训练
num_epochs = 10
for epoch in range(num_epochs):
    train_loss.reset_states()
    train_accuracy.reset_states()
    
    for (batch, (inputs, targets)) in enumerate(train_dataset):
        train_step(inputs, targets)
    
    print(f'Epoch {epoch+1} Loss {train_loss.result()} Accuracy {train_accuracy.result()}')

# 保存模型
transformer.save('transformer_model.h5')

训练

该代码使用的是IMDB电影评论数据集进行训练。IMDB电影评论数据集包含来自互联网电影数据库(IMDB)的50,000条电影评论,其中25,000条用于训练,另外25,000条用于测试。每条评论都被标记为正面或负面情感。

训练数据集包含嵌入在train_dataset中的电影评论和相应的情感标签。通过对数据集进行预处理和处理,数据集被转换为TensorFlow数据集,并进行了随机化、分批和预提取等操作。在每个训练步骤中,模型使用输入评论生成对应的目标评论,然后通过计算损失函数来优化模型。

数据集中的评论已经进行了一定的预处理,例如将评论文本标记为整数,其中每个整数表示词汇表中的一个单词。

输出

Epoch 1 Loss 0.6929 Accuracy 0.5095
Epoch 2 Loss 0.6074 Accuracy 0.7172
Epoch 3 Loss 0.5032 Accuracy 0.7758
Epoch 4 Loss 0.3970 Accuracy 0.8213
Epoch 5 Loss 0.2961 Accuracy 0.8639
Epoch 6 Loss 0.2200 Accuracy 0.8982
Epoch 7 Loss 0.1654 Accuracy 0.9261
Epoch 8 Loss 0.1243 Accuracy 0.9454
Epoch 9 Loss 0.0988 Accuracy 0.9571
Epoch 10 Loss 0.0837 Accuracy 0.9646

五、困惑

在看其他人的博文时,对这类公式与代码的关系理解不到位,不知道怎么用,有什么关系。

K、Q、V之间如何设置和互相影响?Q和K在注意力公式中只是做了内积,数学形式上是可交换的,是否意味着Q和K是可以互换的?怎么将代码对应到公式中参数?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值