44、使用循环神经网络对序列数据进行建模

使用循环神经网络对序列数据进行建模

1. 序列数据介绍

在机器学习中,序列数据是一种常见且独特的数据类型。与其他类型的数据不同,序列数据中的元素按特定顺序出现,且彼此之间并非相互独立。

1.1 建模序列数据 - 顺序很重要

典型的监督学习机器学习算法通常假设输入数据是独立同分布(IID)的,即训练示例相互独立且具有相同的底层分布。在这种假设下,训练示例的输入顺序对模型训练并无影响。例如,鸢尾花数据集,每朵花的测量数据都是独立的,一朵花的测量结果不会影响其他花的测量结果。

然而,当处理序列数据时,这种独立性假设不再成立。以预测某只股票的市场价值为例,每个训练示例代表某一天该股票的市场价值。若要预测未来三天的股票市场价值,按日期排序考虑先前的股票价格以得出趋势是合理的,而随机使用这些训练示例则无法有效获取趋势信息。

1.2 序列数据与时间序列数据

时间序列数据是序列数据的一种特殊类型,其中每个示例都与时间维度相关联。在时间序列数据中,样本是在连续的时间戳上采集的,因此时间维度决定了数据点之间的顺序。例如,股票价格和语音记录都属于时间序列数据。

但并非所有序列数据都具有时间维度,如文本数据或 DNA 序列,这些示例虽然有序,但不属于时间序列数据。不过,循环神经网络(RNN)可用于处理时间序列数据和非时间序列的序列数据。

1.3 表示序列

在机器学习模型中,为了利用序列数据的顺序信息,我们通常将序列表示为〈𝒙(1), 𝒙(2), …, 𝒙(T)〉,其中上标索引表示实例的顺序,序列的长度为 T。

标准的神经网络模型,如多层感知机(MLP)和用于图像数据的卷积神经网络(CNN),假设训练示例相互独立,不包含顺序信息,即这些模型没有对先前训练示例的记忆。而 RNN 则专门设计用于建模序列数据,能够记住过去的信息并相应地处理新事件,这在处理序列数据时具有明显优势。

1.4 序列建模的不同类别

序列建模有许多有趣的应用,如语言翻译、图像字幕生成和文本生成等。根据输入和输出数据的关系,序列建模任务可分为以下几类:
- 多对一 :输入数据是序列,但输出是固定大小的向量或标量,而非序列。例如,在情感分析中,输入是基于文本的(如电影评论),输出是一个类别标签(如表示评论者是否喜欢该电影的标签)。
- 一对多 :输入数据是标准格式,不是序列,但输出是序列。图像字幕生成就是这种类型的例子,输入是一张图像,输出是总结该图像内容的英文短语。
- 多对多 :输入和输出数组都是序列。此类又可根据输入和输出是否同步进一步细分。同步的多对多建模任务如视频分类,视频中的每一帧都被标记;延迟的多对多建模任务如将一种语言翻译成另一种语言,机器必须先读取和处理整个英文句子,才能生成其德语翻译。

2. 用于建模序列的 RNN

在使用 TensorFlow 实现 RNN 之前,我们先来了解 RNN 的主要概念。

2.1 理解 RNN 循环机制

RNN 的架构与标准前馈神经网络有所不同。在标准前馈网络中,信息从输入层流向隐藏层,再从隐藏层流向输出层。而在 RNN 中,隐藏层的输入不仅来自当前时间步的输入层,还来自上一个时间步的隐藏层。

这种相邻时间步之间的信息流动使网络能够记住过去的事件,这种信息流动通常以循环的形式表示,这也是 RNN 名称的由来。RNN 可以有多个隐藏层,通常将只有一个隐藏层的 RNN 称为单层 RNN,但要注意它与没有隐藏层的单层神经网络(如 Adaline 或逻辑回归)不同。

RNN 的输出类型可以是序列〈𝒐(0), 𝒐(1), …, 𝒐(T)〉,也可以只是最后一个输出(即 𝒐(T))。在 TensorFlow Keras API 中,可以通过设置 return_sequences 参数为 True False 来指定循环层返回序列输出还是仅使用最后一个输出。

在 RNN 中,每个隐藏单元在第一个时间步(t = 0)时初始化为零或小随机值,在 t > 0 的时间步,隐藏单元的输入来自当前时间的数据点 𝒙(t) 和上一个时间步的隐藏单元值 𝒉(t - 1)。对于多层 RNN,各层的信息流动如下:
- 第一层 :隐藏层表示为 𝒉1(t),其输入来自数据点 𝒙(t) 和上一个时间步同一层的隐藏值 𝒉1(t - 1)。
- 第二层 :隐藏层 𝒉2(t) 的输入来自当前时间步下一层的输出 𝒐1(t) 和上一个时间步自身的隐藏值 𝒉2(t - 1)。

除最后一层外,每个循环层都必须接收序列作为输入,因此除最后一层外,所有循环层都必须返回序列作为输出(即 return_sequences = True ),最后一个循环层的行为取决于具体问题。

2.2 计算 RNN 中的激活值

为了计算 RNN 中隐藏层和输出层的实际激活值,我们以单层 RNN 为例(多层 RNN 的原理相同)。在 RNN 的表示中,每个有向边(框之间的连接)都与一个权重矩阵相关联,这些权重不依赖于时间 t,因此在时间轴上是共享的。单层 RNN 中的不同权重矩阵如下:
- 𝑾𝑥ℎ :输入 𝒙(t) 与隐藏层 h 之间的权重矩阵。
- 𝑾ℎℎ :与循环边相关联的权重矩阵。
- 𝑾ℎ𝑜 :隐藏层与输出层之间的权重矩阵。

在某些实现中,权重矩阵 𝑾𝑥ℎ 和 𝑾ℎℎ 会连接成一个组合矩阵 𝑾ℎ = [𝑾𝑥ℎ; 𝑾ℎℎ]。

隐藏层的净输入 𝒛ℎ(预激活)通过线性组合计算,即计算权重矩阵与相应向量的乘积之和,并加上偏置单元:
[ 𝒛ℎ(t) = 𝑾𝑥ℎ𝒙(t) + 𝑾ℎℎ𝒉(t - 1) + 𝒃ℎ ]
然后,时间步 t 时隐藏单元的激活值计算如下:
[ 𝒉(t) = 𝜙ℎ(𝒛ℎ(t)) = 𝜙ℎ(𝑾𝑥ℎ𝒙(t) + 𝑾ℎℎ𝒉(t - 1) + 𝒃ℎ) ]
其中,𝒃ℎ 是隐藏单元的偏置向量,𝜙ℎ(∙) 是隐藏层的激活函数。

如果使用连接后的权重矩阵 𝑾ℎ = [𝑾𝑥ℎ; 𝑾ℎℎ],计算隐藏单元的公式将变为:
[ 𝒉(t) = 𝜙ℎ([𝑾𝑥ℎ; 𝑾ℎℎ][
\begin{matrix}
𝒙(t) \
𝒉(t - 1)
\end{matrix}
] + 𝒃ℎ) ]
计算出当前时间步隐藏单元的激活值后,输出单元的激活值计算如下:
[ 𝒐(t) = 𝜙𝑜(𝑾ℎ𝑜𝒉(t) + 𝒃𝑜) ]

2.3 隐藏循环与输出循环

到目前为止,我们看到的 RNN 中,隐藏层具有循环特性。但还有一种替代模型,其循环连接来自输出层。在这种情况下,上一个时间步输出层的净激活值 𝒐𝑡 - 1 可以通过以下两种方式添加:
- 输出到隐藏循环 :添加到当前时间步的隐藏层 𝒉𝑡。
- 输出到输出循环 :添加到当前时间步的输出层 𝒐𝑡。

2.4 使用时间反向传播(BPTT)训练 RNN

RNN 的学习算法于 1990 年被提出。其基本思想是,总体损失 L 是从 t = 1 到 t = T 所有时间步的损失函数之和:
[ 𝐿 = \sum_{t = 1}^{T} 𝐿(t) ]
由于时间 t 的损失依赖于所有先前时间步(1 到 t)的隐藏单元,因此梯度的计算如下:
[ \frac{\partial 𝐿(t)}{\partial 𝑾ℎℎ} = \frac{\partial 𝐿(t)}{\partial 𝒐(t)} \times \frac{\partial 𝒐(t)}{\partial 𝒉(t)} \times (\sum_{k = 1}^{t} \frac{\partial 𝒉(t)}{\partial 𝒉(k)} \times \frac{\partial 𝒉(k)}{\partial 𝑾ℎℎ}) ]
其中,(\frac{\partial 𝒉(t)}{\partial 𝒉(k)}) 计算为相邻时间步的乘积:
[ \frac{\partial 𝒉(t)}{\partial 𝒉(k)} = \prod_{i = k + 1}^{t} \frac{\partial 𝒉(i)}{\partial 𝒉(i - 1)} ]

为了更好地理解 RNN 的前向传播过程,我们使用 TensorFlow Keras API 中的 SimpleRNN 层进行示例。以下是具体代码:

import tensorflow as tf
tf.random.set_seed(1)
rnn_layer = tf.keras.layers.SimpleRNN(
    units=2, use_bias=True,
    return_sequences=True)
rnn_layer.build(input_shape=(None, None, 5))
w_xh, w_oo, b_h = rnn_layer.weights
print('W_xh shape:', w_xh.shape)
print('W_oo shape:', w_oo.shape)
print('b_h  shape:', b_h.shape)

x_seq = tf.convert_to_tensor(
    [[1.0]*5, [2.0]*5, [3.0]*5],
    dtype=tf.float32)
# 输出 of SimpleRNN:
output = rnn_layer(tf.reshape(x_seq, shape=(1, 3, 5)))
# 手动计算输出:
out_man = []
for t in range(len(x_seq)):
    xt = tf.reshape(x_seq[t], (1, 5))
    print('Time step {} =>'.format(t))
    print('   Input           :', xt.numpy())

    ht = tf.matmul(xt, w_xh) + b_h
    print('   Hidden          :', ht.numpy())

    if t>0:
        prev_o = out_man[t-1]
    else:
        prev_o = tf.zeros(shape=(ht.shape))
    ot = ht + tf.matmul(prev_o, w_oo)
    ot = tf.math.tanh(ot)
    out_man.append(ot)
    print('   Output (manual) :', ot.numpy())
    print('   SimpleRNN output:', output[0][t].numpy())
    print()

在手动前向计算中,我们使用了双曲正切(tanh)激活函数,这也是 SimpleRNN 的默认激活函数。从打印结果可以看出,手动前向计算的输出与 SimpleRNN 层在每个时间步的输出完全匹配。

2.5 学习长距离交互的挑战

BPTT 算法在训练 RNN 时会引入一些新的挑战,主要是所谓的梯度消失和梯度爆炸问题。这是由于在计算损失函数的梯度时,存在乘法因子 (\frac{\partial 𝒉(t)}{\partial 𝒉(k)}),它包含 t - k 次乘法运算。如果循环边的权重 |w| < 1,当 t - k 很大时,这个因子会变得非常小;如果 |w| > 1,当 t - k 很大时,这个因子会变得非常大。

在实践中,有以下几种解决方案:
- 梯度裁剪 :为梯度指定一个截断或阈值,将超过该值的梯度值赋为该截断值。
- 截断时间反向传播(TBPTT) :限制每次前向传播后信号可以反向传播的时间步数。例如,即使序列有 100 个元素或步骤,我们可能只反向传播最近的 20 个时间步。
- 长短期记忆网络(LSTM) :由 Sepp Hochreiter 和 Jürgen Schmidhuber 于 1997 年设计,通过使用记忆单元,在处理长距离依赖时,在解决梯度消失和梯度爆炸问题上更为成功。

虽然梯度裁剪和 TBPTT 可以解决梯度爆炸问题,但截断会限制梯度有效回流和正确更新权重的步数。而 LSTM 在处理长距离依赖时表现更优,下面我们将详细讨论 LSTM。

3. 长短期记忆网络(LSTM)

LSTM 是为了解决 RNN 在处理长距离依赖时遇到的梯度消失和梯度爆炸问题而设计的。它通过引入记忆单元和门控机制,能够更好地捕捉序列中的长期信息。

3.1 LSTM 的结构

LSTM 单元主要由输入门($i_t$)、遗忘门($f_t$)、输出门($o_t$)和记忆单元($C_t$)组成。以下是各部分的作用:
- 遗忘门 :决定上一时刻的记忆单元 $C_{t - 1}$ 中有多少信息需要被遗忘。其计算公式为:
[ f_t = \sigma(W_f[h_{t - 1}, x_t] + b_f) ]
其中,$\sigma$ 是 sigmoid 函数,$W_f$ 是遗忘门的权重矩阵,$b_f$ 是偏置向量,$h_{t - 1}$ 是上一时刻的隐藏状态,$x_t$ 是当前时刻的输入。
- 输入门 :决定当前输入 $x_t$ 中有多少信息需要被添加到记忆单元中。计算公式为:
[ i_t = \sigma(W_i[h_{t - 1}, x_t] + b_i) ]
同时,会计算一个候选记忆单元 $\tilde{C} t$:
[ \tilde{C}_t = \tanh(W_C[h
{t - 1}, x_t] + b_C) ]
- 更新记忆单元 :根据遗忘门和输入门的输出,更新当前时刻的记忆单元 $C_t$:
[ C_t = f_t \odot C_{t - 1} + i_t \odot \tilde{C} t ]
其中,$\odot$ 表示逐元素相乘。
- 输出门 :决定当前记忆单元 $C_t$ 中有多少信息需要被输出到当前时刻的隐藏状态 $h_t$。计算公式为:
[ o_t = \sigma(W_o[h
{t - 1}, x_t] + b_o) ]
当前时刻的隐藏状态 $h_t$ 计算如下:
[ h_t = o_t \odot \tanh(C_t) ]

下面是 LSTM 单元的工作流程 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([输入 x_t 和 h_{t - 1}]):::startend --> B(计算遗忘门 f_t):::process
    A --> C(计算输入门 i_t):::process
    A --> D(计算候选记忆单元 \tilde{C}_t):::process
    B --> E(更新记忆单元 C_t):::process
    C --> E
    D --> E
    A --> F(计算输出门 o_t):::process
    E --> G(计算隐藏状态 h_t):::process
    F --> G
    G --> H([输出 h_t 和 C_t]):::startend
3.2 LSTM 的优势

LSTM 通过门控机制,能够选择性地遗忘和保留信息,从而有效地处理长距离依赖。与普通 RNN 相比,LSTM 在处理长序列时,能够更好地避免梯度消失和梯度爆炸问题,使得模型能够学习到更长期的依赖关系。

4. 截断时间反向传播(TBPTT)

TBPTT 是一种用于训练 RNN 的方法,旨在解决梯度消失和梯度爆炸问题。

4.1 TBPTT 的原理

TBPTT 的基本思想是限制每次前向传播后信号可以反向传播的时间步数。在标准的 BPTT 中,梯度会沿着整个序列进行反向传播,这可能导致梯度在长序列中变得不稳定。而 TBPTT 只允许梯度在有限的时间步内反向传播。

例如,对于一个长度为 $T$ 的序列,我们可以设置一个截断长度 $k$,在每次前向传播后,只对最近的 $k$ 个时间步进行反向传播。这样可以减少梯度在长序列中累积的影响,从而缓解梯度消失和梯度爆炸问题。

4.2 TBPTT 的实现步骤

以下是 TBPTT 的实现步骤列表:
1. 进行前向传播:从序列的起始位置开始,依次计算每个时间步的隐藏状态和输出。
2. 选择截断点:在序列中选择一个截断点,确定反向传播的时间步数。
3. 进行反向传播:从截断点开始,反向计算梯度,更新模型的参数。
4. 重复步骤 1 - 3:直到处理完整个序列。

5. 在 TensorFlow 中实现多层 RNN 进行序列建模

在 TensorFlow 中,我们可以方便地实现多层 RNN 进行序列建模。以下是一个简单的示例代码:

import tensorflow as tf

# 定义输入数据的形状
input_shape = (None, None, 5)  # 批量大小、序列长度、特征维度

# 创建多层 RNN 模型
model = tf.keras.Sequential([
    tf.keras.layers.SimpleRNN(units=10, return_sequences=True, input_shape=input_shape),
    tf.keras.layers.SimpleRNN(units=20, return_sequences=True),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

# 编译模型
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# 打印模型结构
model.summary()

在这个示例中,我们创建了一个包含两个 SimpleRNN 层和一个全连接层的多层 RNN 模型。第一个 SimpleRNN 层的 return_sequences 参数设置为 True ,表示该层会返回整个序列的输出,以便传递给下一个 SimpleRNN 层。最后一个全连接层使用 sigmoid 激活函数,用于二分类任务。

6. 项目实践
6.1 项目一:IMDb 电影评论数据集的 RNN 情感分析

在这个项目中,我们将使用 RNN 对 IMDb 电影评论数据集进行情感分析。以下是大致的步骤:
1. 数据预处理 :加载 IMDb 数据集,对文本进行分词、编码等处理。
2. 模型构建 :构建一个 RNN 模型,例如使用 SimpleRNN LSTM 层。
3. 模型训练 :使用训练数据对模型进行训练。
4. 模型评估 :使用测试数据评估模型的性能。

以下是一个简化的代码示例:

import tensorflow as tf
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences

# 加载 IMDb 数据集
vocab_size = 10000
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=vocab_size)

# 填充序列
max_length = 200
x_train = pad_sequences(x_train, maxlen=max_length)
x_test = pad_sequences(x_test, maxlen=max_length)

# 构建模型
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(input_dim=vocab_size, output_dim=100),
    tf.keras.layers.SimpleRNN(units=32),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

# 编译模型
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# 训练模型
model.fit(x_train, y_train, epochs=5, batch_size=64, validation_data=(x_test, y_test))
6.2 项目二:使用儒勒·凡尔纳《神秘岛》文本数据的 LSTM 字符级语言建模

在这个项目中,我们将使用 LSTM 进行字符级语言建模。以下是大致的步骤:
1. 数据预处理 :加载《神秘岛》的文本数据,将字符转换为数字编码。
2. 构建数据集 :将文本数据划分为输入序列和目标序列。
3. 模型构建 :构建一个 LSTM 模型。
4. 模型训练 :使用训练数据对模型进行训练。
5. 文本生成 :使用训练好的模型生成新的文本。

以下是一个简化的代码示例:

import tensorflow as tf
import numpy as np

# 加载文本数据
text = open('the_mysterious_island.txt', 'rb').read().decode(encoding='utf-8')

# 创建字符到索引的映射
vocab = sorted(set(text))
char2idx = {u: i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)

# 将文本转换为数字编码
text_as_int = np.array([char2idx[c] for c in text])

# 创建训练数据集
seq_length = 100
examples_per_epoch = len(text) // (seq_length + 1)
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
sequences = char_dataset.batch(seq_length + 1, drop_remainder=True)

def split_input_target(chunk):
    input_text = chunk[:-1]
    target_text = chunk[1:]
    return input_text, target_text

dataset = sequences.map(split_input_target)

# 构建模型
vocab_size = len(vocab)
embedding_dim = 256
rnn_units = 1024

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim),
    tf.keras.layers.LSTM(rnn_units, return_sequences=True),
    tf.keras.layers.Dense(vocab_size)
])

# 编译模型
model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True))

# 训练模型
BATCH_SIZE = 64
BUFFER_SIZE = 10000
dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
model.fit(dataset, epochs=10)

# 文本生成
def generate_text(model, start_string):
    num_generate = 1000
    input_eval = [char2idx[s] for s in start_string]
    input_eval = tf.expand_dims(input_eval, 0)
    text_generated = []
    temperature = 1.0

    model.reset_states()
    for i in range(num_generate):
        predictions = model(input_eval)
        predictions = tf.squeeze(predictions, 0)
        predictions = predictions / temperature
        predicted_id = tf.random.categorical(predictions, num_samples=1)[-1, 0].numpy()
        input_eval = tf.expand_dims([predicted_id], 0)
        text_generated.append(idx2char[predicted_id])

    return (start_string + ''.join(text_generated))

print(generate_text(model, start_string=u"Captain Nemo"))
7. 使用梯度裁剪避免梯度爆炸

梯度裁剪是一种简单而有效的方法,用于避免梯度爆炸问题。

7.1 梯度裁剪的原理

梯度裁剪的基本思想是为梯度指定一个截断或阈值,当梯度的范数超过该阈值时,将梯度缩放到该阈值范围内。这样可以防止梯度在训练过程中变得过大,从而保证模型的稳定性。

7.2 梯度裁剪的实现

在 TensorFlow 中,可以通过在优化器中设置 clipvalue clipnorm 参数来实现梯度裁剪。以下是一个示例代码:

import tensorflow as tf

# 构建模型
model = tf.keras.Sequential([
    tf.keras.layers.Dense(64, activation='relu', input_shape=(10,)),
    tf.keras.layers.Dense(1)
])

# 定义优化器并设置梯度裁剪
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001, clipvalue=0.5)

# 编译模型
model.compile(optimizer=optimizer, loss='mse')

# 训练模型
x_train = tf.random.normal((100, 10))
y_train = tf.random.normal((100, 1))
model.fit(x_train, y_train, epochs=10)

在这个示例中,我们使用 Adam 优化器,并设置 clipvalue=0.5 ,表示当梯度的绝对值超过 0.5 时,将其裁剪为 0.5。

8. 引入 Transformer 模型并理解自注意力机制

Transformer 模型是一种基于自注意力机制的深度学习模型,在自然语言处理领域取得了巨大的成功。

8.1 自注意力机制的原理

自注意力机制允许模型在处理序列时,关注序列中的不同位置,从而捕捉序列中的长距离依赖关系。具体来说,自注意力机制通过计算输入序列中每个位置与其他位置的相关性,为每个位置分配不同的权重,然后根据这些权重对输入序列进行加权求和,得到每个位置的表示。

以下是自注意力机制的计算步骤表格:
|步骤|描述|
|----|----|
|1|将输入序列 $X$ 分别乘以三个权重矩阵 $W_Q$、$W_K$ 和 $W_V$,得到查询矩阵 $Q$、键矩阵 $K$ 和值矩阵 $V$。|
|2|计算注意力分数:$scores = QK^T / \sqrt{d_k}$,其中 $d_k$ 是查询和键的维度。|
|3|对注意力分数进行 softmax 操作,得到注意力权重:$attention_weights = softmax(scores)$。|
|4|根据注意力权重对值矩阵进行加权求和,得到输出:$output = attention_weights \cdot V$。|

8.2 Transformer 模型的结构

Transformer 模型主要由编码器和解码器组成。编码器负责对输入序列进行编码,解码器负责根据编码器的输出生成目标序列。

编码器和解码器都由多个相同的层堆叠而成,每个层包含多头自注意力机制和前馈神经网络。多头自注意力机制允许模型在不同的表示子空间中关注输入序列,从而提高模型的表达能力。

Transformer 模型的结构 mermaid 流程图如下:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([输入序列]):::startend --> B(编码器):::process
    B --> C(解码器):::process
    C --> D([输出序列]):::startend
    B1(多头自注意力机制):::process --> B2(前馈神经网络):::process
    C1(多头自注意力机制):::process --> C2(编码器 - 解码器注意力机制):::process
    C2 --> C3(前馈神经网络):::process
    subgraph 编码器层
    B1
    B2
    end
    subgraph 解码器层
    C1
    C2
    C3
    end

通过引入 Transformer 模型和自注意力机制,我们可以更有效地处理序列数据,尤其是在处理长序列时,能够取得更好的性能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值