本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.
传统的神经网络(Neural Network)模型主要学习的是数据点(向量)到数据点(向量)变换,而循环神经网络(Recurrent Neural Network)则学习的是数据点序列(Sequence)到数据点序列的变换,近年来比较常用的模型有门控循环单元(Gate Recurrent Unit)、长短期记忆(Long-Short Term Memory)以及Transformer模型。其中Transformer模型可以理解为循环神经网络模型中的“变形金刚”,在许多应用场景(例如机器翻译、自然语言处理)中都取得了很好的效果, 成为了目前研究领域的新主流。但不管当前研究人员的观点如何以及是否偏颇,我们作为普通的吃瓜群众而言,对于优秀的东西都是要不断学习的。本文会以电影评论分类的代码为例子,对其进行简要的探讨与总结。
Transformer模型的核心思想
Transformer 模型最开始应用于机器翻译的应用场景,它试图避免循环神经网络递归运算,从而使得并行计算成为可能,即极大的缩短的训练和测试的时间,以便应用于对时间要求较高的自动化系统。同时,它可以捕获序列内部长距离的依赖关系,从而避免了输入序列过长而造成的性能损失。Transformer模型的核心思想是自注意力机制(Self-Attention),即能注意输入序列的不同位置以计算该序列的特征表达的能力。Transformer 模型由多个自注意力层(self-attentionlayers)堆叠而成,下图显示了按比缩放的点积注意力(Scaled Dot-Product Attention)子模块和多头注意力(Multi-Head Attention)层的设计。
其中Mask操作就是将所得矩阵上三角元素置零的操作,它是一个可选的模块,在某些应用中可以提升性能。在定义这些基本模块后,便可以搭建一个网络进行序列到序列的变换了,下图是原始论文中给出的一个最简单的结构,输出为一个概率分布向量。
其中,在输入层有一个输入编码和位置编码的概念:输入编码将输入序列转化为一个定长的向量表示,位置编码则会将序列中各个数据的位置信息也进行编码,最后得到的编码为这两种编码的之和。该编码方式增加了输入序列编码的区分度,因为Transformer模型将序列整体作为输入,而不是像传统的循环神经网络那样依次处理序列中的数据点,因而对序列中数据位置信息编码是必要的。 位置编码 的公式和代码可以在对应的链接里找到,由于其通用性有待商榷,故不放在这里作为参考,而大家也可以根据自己的应用灵活设计编码方式。
Transformer模型应用实例
下面给出了一个使用Transformer模型进行影评分类的代码示例,深度学习框架为基于Tensorflow的Keras,输入序列为影评的文字(英文),输出为态度积极或消极的概率分布向量。
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers # 定义Transformer层 class TransformerBlock(layers.Layer): def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1): super(TransformerBlock, self).__init__() self.att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim) self.ffn = keras.Sequential([layers.Dense(ff_dim, activation="relu"), layers.Dense(embed_dim),]) self.layernorm1 = layers.LayerNormalization(epsilon=1e-6) self.layernorm2 = layers.LayerNormalization(epsilon=1e-6) self.dropout1 = layers.Dropout(rate) self.dropout2 = layers.Dropout(rate) def call(self, inputs, training): attn_output = self.att(inputs, inputs) attn_output = self.dropout1(attn_output, training=training) out1 = self.layernorm1(inputs + attn_output) ffn_output = self.ffn(out1) ffn_output = self.dropout2(ffn_output, training=training) return self.layernorm2(out1 + ffn_output) # 定义单词与位置编码层 class TokenAndPositionEmbedding(layers.Layer): def __init__(self, maxlen, vocab_size, embed_dim): super(TokenAndPositionEmbedding, self).__init__() self.token_emb = layers.Embedding(input_dim=vocab_size, output_dim=embed_dim) self.pos_emb = layers.Embedding(input_dim=maxlen, output_dim=embed_dim) def call(self, x): maxlen = tf.shape(x)[-1] positions = tf.range(start=0, limit=maxlen, delta=1) positions = self.pos_emb(positions) x = self.token_emb(x) return x + positions # 只考虑频次最高的20000个单词作为整个词典 vocab_size = 20000 # 在imdb数据集中,对于每个电影仅考虑其评论的前200个单词 maxlen = 200 (x_train, y_train), (x_val, y_val) = keras.datasets.imdb.load_data(num_words=vocab_size) # 将每个训练、测试样本序列补充为定长 x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen) x_val = keras.preprocessing.sequence.pad_sequences(x_val, maxlen=maxlen) # 定义每个样本序列的编码长度 embed_dim = 32 # 定义注意力头(Attention Head)的个数 num_heads = 2 # 定义Transformer层中前向传播网络里隐藏单元的个数 ff_dim = 32 # 定义整个模型的输入、输出和连接顺序 model = keras.Sequential([ TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim), TransformerBlock(embed_dim, num_heads, ff_dim), layers.GlobalAveragePooling1D(), layers.Dropout(0.1), layers.Dense(20, activation="relu"), layers.Dropout(0.1), layers.Dense(2, activation="softmax") ]) # 编译及训练模型 model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) history = model.fit(x_train, y_train, batch_size=32, epochs=2, validation_data=(x_val, y_val))
运行代码后输出的结果为:
Epoch 1/2 782/782 [==============================] - 100s 126ms/step - loss: 0.5223 - accuracy: 0.7051 - val_loss: 0.3765 - val_accuracy: 0.8346 Epoch 2/2 782/782 [==============================] - 96s 123ms/step - loss: 0.1986 - accuracy: 0.9245 - val_loss: 0.3288 - val_accuracy: 0.8728
Transformer模型的优点与缺点
一个 transformer 模型用自注意力层而非循环神经网络或卷积神经网络来处理变长的输入,这种通用架构有一系列的优势:
它不对数据间的时间或空间关系做出任何假设。
层输出可以并行计算,而非像循环神经网络这样的序列计算。
远距离项可以影响彼此的输出,而无需经过许多循环神经网络步骤或卷积层。
它能学习长距离的依赖。在许多序列任务中,这是一项挑战。
该架构的缺点是:
对于时间序列,一个单位时间的输出是依据整个历史记录计算的,而非仅从当前的输入和隐含状态计算得到,这可能效率较低。
如果输入序列确实有时间或空间的依赖关系(例如文本)则必须加入一些位置编码。
Be First to Comment