41、注意力与记忆增强网络:原理、应用与案例分析

注意力与记忆增强网络:原理、应用与案例分析

1 循环网络、控制器与训练

在神经网络中,神经栈作为循环网络逐渐扩展,其控制器的操作如下所示。整个架构(以虚线标记)是一个循环网络,输入为:
- 前一个循环状态 $H_{t - 1}$
- 当前输入 $i_t$

输出为:
- 下一个循环状态 $H_t$
- $o_t$

前一个循环状态 $H_{t - 1}$ 由三部分组成:
- 来自 RNN 的前一个状态向量 $h_{t - 1}$
- 前一个栈读取值 $r_t$
- 前一个状态下栈的状态 $(V_{t - 1}, s_t)$

在实现时,除了随机初始化的 $h_0$ 外,所有向量初始都设为 0。

当前输入 $i_t$ 与栈的前一个读取值 $r_{t - 1}$ 连接到控制器,控制器有自己的前一个状态 $h_{t - 1}$,生成下一个状态 $h_t$ 和输出 $o’_t$。输出 $o’_t$ 产生推送信号标量 $d_t$、弹出信号标量 $u_t$ 和值向量 $v_t$,这些作为输入信号传递给神经栈,同时产生整个网络的输出信号 $o_t$。相关方程如下:
- $d_t = sigmoid(W_d o’_t + b_d)$
- $u_t = sigmoid(W_u o’_t + b_u)$
- $v_t = sigmoid(W_v o’_t + b_v)$
- $o_t = sigmoid(W_o o’_t + b_o)$

这个结构可以很容易地适应神经队列,只需将弹出信号改为从列表底部读取,而不是顶部,相关方程如下:
- $s_t[i] = \begin{cases} max(0, s_{t - 1}[i] - max(0, u_t - \sum_{j = 1}^{i - 1} s_{t - 1}[j])) & \text{if } 1 \leq i < t \ d_t & \text{if } i = t \end{cases}$
- $r_t = \sum_{i = 1}^{t} min(s_t[i], max(0, 1 - \sum_{j = 1}^{i - 1} s_t[j] \cdot V_t[i]))$

神经双端队列(Neural DeQue)的工作方式与神经栈类似,但它能够处理列表顶部和底部的推送、弹出和值输入信号。

2 循环实体网络

Henaff 等人设计了一种高度并行的架构——循环实体网络(EntNet),它具有长动态内存,在许多自然语言理解(NLU)任务中表现出色。其核心思想是使用一组内存单元块,每个单元可以存储句子中一个实体的信息,这样与名称、地点等对应的许多实体在单元中都有信息内容。

2.1 输入编码器

以问答系统为例,假设有一个训练集 ${(x_i, y_i) {i = 1}^{n}}$,其中 $x_i$ 是输入句子,$q$ 是问题,$y_i$ 是单个单词答案。输入编码层将单词序列转换为固定长度的向量。可以使用词袋(BOW)表示和 RNN 的最终状态来实现。对于给定时间 $t$ 的输入,使用一组向量 ${f_1, …, f_k}$ 和单词的输入嵌入 ${e_1, …, e_k}$,得到如下简单表示:
- $s_t = \sum
{i} f_i \circ e_i$

其中 $\circ$ 是哈达玛积(逐元素相乘)。同一组向量 ${f_1, …, f_k}$ 用于所有时间步。嵌入矩阵 $E \in R^{|V| \times d}$ 使用 $E(w) = e \in R^d$ 转换句子中的每个单词,其中 $d$ 是嵌入的维度。与其他参数一样,向量 ${f_1, …, f_k}$ 与其他参数一起从训练数据中学习。

2.2 动态内存

输入编码后的句子流入内存单元块,整个网络是一种门控循环单元(GRU),这些块中的隐藏状态连接在一起构成网络的总隐藏状态。总共有 5 - 20 个块 $h_1, …, h_m$,每个块 $h_j$ 有 20 - 100 个单元。每个块 $j$ 有一个隐藏状态 $h_j \in R^d$ 和一个键 $w_j \in R^d$。

块的作用是捕获实体的信息。通过将键向量的权重与感兴趣实体的嵌入相关联,模型可以学习文本中出现的实体信息。一个通用的第 $j$ 块(权重为 $w_j$,隐藏状态为 $h_j$)的计算如下:
- $g^t_j \leftarrow sigmoid(s^T_t h^{t - 1}_j + s^T_t w^{t - 1}_j)$(门控)
- $\tilde{h}^t_j \leftarrow \varphi(P h^{t - 1}_j + Q w^{t - 1}_j + R s_t)$(候选内存)
- $h^t_j \leftarrow h^{t - 1}_j + g_j \circ \tilde{h}^t_j$(新内存)
- $h^t_j \leftarrow \frac{h^t_j}{|h^t_j|}$(重置内存)

其中 $g_j$ 是决定内存更新程度的门控,$\varphi$ 是激活函数(如 ReLU),$h^t_j$ 是结合了旧时间戳和当前信息的新内存,最后一步的归一化有助于遗忘先前的信息。矩阵 $P \in R^{d \times d}$、$Q \in R^{d \times d}$ 和 $R \in R^{d \times d}$ 在所有块中共享。

2.3 输出模块与训练

输出模块在接收到问题 $q$ 时,会在所有隐藏状态上创建一个概率分布,相关方程如下:
- $p_j = softmax(q^T h_j)$
- $u = \sum_{j} p_j h_j$
- $y = R \varphi(q + H u)$

矩阵 $R \in R^{|V| \times d}$ 和 $H \in R^{d \times d}$ 与其他参数一起训练。函数 $\varphi$ 增加非线性,可以是像 ReLU 这样的激活函数。整个网络使用反向传播进行训练。实体可以作为预处理的一部分提取,键向量可以专门与故事中存在的实体的嵌入相关联,例如 bAbI 示例中的 {Mary, Sandra, John, bathroom, bedroom, kitchen, garden, football}。

3 记忆增强网络在文本和语音中的应用

大多数记忆网络已成功应用于复杂的 NLU 任务,如问答和语义角色标注。具体应用如下:
|应用者|应用方式|
| ---- | ---- |
|Sukhbaatar 等人|在语言建模任务中,通过增加内存跳跃次数,应用端到端记忆网络,超越了传统的 RNN|
|Kumar 等人|在问答框架中,将大多数 NLP 任务从语法任务转换为语义任务,并成功应用动态内存网络|
|Grefenstette 等人|在机器翻译中使用的反转转换语法(ITG)等转换任务中,使用神经栈、队列和双端队列等记忆网络,取得了显著的性能提升|

4 案例研究:基于注意力的神经机器翻译

4.1 数据集

在这个案例研究中,我们比较不同注意力机制在英语到法语翻译任务中的表现。使用的数据集由 Tatoeba 网站的翻译对组成。数据集的基本信息如下:
|数据集类型|大小|
| ---- | ---- |
|训练集|107885|
|验证集|13486|
|测试集|13486|
|英语词汇量|4755|
|法语词汇量|6450|

4.2 软件工具和库

  • 深度学习框架:PyTorch
  • 注意力机制实现:AllenNLP
  • 分词工具:spaCy
  • 数据加载器:torchtext

代码在 PyTorch 教程的基础上进行了扩展,增加了额外的功能和比较。

4.3 模型训练

我们比较了五种不同的注意力机制,训练 100 个周期。对于每种注意力机制,选择在验证数据上表现最佳的模型在测试数据上运行。训练的模型是 4 层双向 GRU 编码器和一个单向 GRU 解码器。编码器和解码器的隐藏大小均为 512,编码和解码嵌入的大小为 256。模型使用交叉熵损失和随机梯度下降(SGD)进行训练,批量大小为 512。编码器的初始学习率为 0.01,解码器为 0.05,两者都应用了动量为 0.9 的动量项。当验证损失在 5 个周期内没有改善时,使用学习率调度器降低学习率。为了正则化模型,在编码器和解码器中都添加了概率为 0.1 的 Dropout,并将梯度的范数裁剪为 10。

以下是网络不同组件的定义代码:

import torch
import torch.nn as nn
import random

class Encoder(nn.Module):
    def __init__(self, input_dim, emb_dim, enc_hid_dim, dec_hid_dim, dropout, num_layers = 1, bidirectional = False):
        super().__init__()
        self.input_dim = input_dim
        self.emb_dim = emb_dim
        self.enc_hid_dim = enc_hid_dim
        self.dec_hid_dim = dec_hid_dim
        self.num_layers = num_layers
        self.bidirectional = bidirectional
        self.embedding = nn.Embedding(input_dim, emb_dim)
        self.rnn = nn.GRU(emb_dim, enc_hid_dim, num_layers = num_layers, bidirectional = bidirectional)
        self.dropout = nn.Dropout(dropout)
        if bidirectional:
            self.fc = nn.Linear(enc_hid_dim * 2, dec_hid_dim)

    def forward(self, src):
        embedded = self.dropout(self.embedding(src))
        outputs, hidden = self.rnn(embedded)
        if self.bidirectional:
            hidden = torch.tanh(self.fc(torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim = 1)))
        if not self.bidirectional and self.num_layers > 1:
            hidden = hidden[-1, :, :]
        return outputs, hidden

class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, enc_hid_dim, dec_hid_dim, dropout, attention, bidirectional_input = False):
        super().__init__()
        self.emb_dim = emb_dim
        self.enc_hid_dim = enc_hid_dim
        self.dec_hid_dim = dec_hid_dim
        self.output_dim = output_dim
        self.dropout = dropout
        self.attention = attention
        self.bidirectional_input = bidirectional_input
        self.embedding = nn.Embedding(output_dim, emb_dim)
        if bidirectional_input:
            self.rnn = nn.GRU((enc_hid_dim * 2) + emb_dim, dec_hid_dim)
            self.out = nn.Linear((enc_hid_dim * 2) + dec_hid_dim + emb_dim, output_dim)
        else:
            self.rnn = nn.GRU((enc_hid_dim) + emb_dim, dec_hid_dim)
            self.out = nn.Linear((enc_hid_dim) + dec_hid_dim + emb_dim, output_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, input, hidden, encoder_outputs):
        input = input.unsqueeze(0)
        embedded = self.dropout(self.embedding(input))
        hidden = hidden.squeeze(0) if len(hidden.size()) > 2 else hidden
        if hidden.size(-1) != encoder_outputs.size(-1):
            attn = self.attention(hidden.repeat(1, 2), encoder_outputs.permute(1, 0, 2))
        else:
            attn = self.attention(hidden, encoder_outputs.permute(1, 0, 2))
        a = attn.unsqueeze(1)
        encoder_outputs = encoder_outputs.permute(1, 0, 2)
        weighted = torch.bmm(a, encoder_outputs)
        weighted = weighted.permute(1, 0, 2)
        rnn_input = torch.cat((embedded, weighted), dim = 2)
        output, hidden = self.rnn(rnn_input, hidden.unsqueeze(0))
        embedded = embedded.squeeze(0)
        output = output.squeeze(0)
        weighted = weighted.squeeze(0)
        output = self.out(torch.cat((output, weighted, embedded), dim = 1))
        return output, hidden.squeeze(0), attn

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder, device):
        super().__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.device = device

    def forward(self, src, trg, teacher_forcing_ratio = 0.5):
        batch_size = src.shape[1]
        max_len = trg.shape[0]
        trg_vocab_size = self.decoder.output_dim
        outputs = torch.zeros(max_len, batch_size, trg_vocab_size).to(self.device)
        encoder_outputs, hidden = self.encoder(src)
        hidden = hidden.squeeze(1)
        output = trg[0, :]
        for t in range(1, max_len):
            output, hidden, attn = self.decoder(output, hidden, encoder_outputs)
            outputs[t] = output
            teacher_force = random.random() < teacher_forcing_ratio
            top1 = output.max(1)[1]
            output = (trg[t] if teacher_force else top1)
        return outputs

我们使用 AllenNLP 中的点积、余弦和双线性注意力实现。这些函数接受解码器的隐藏状态和编码器的输出,并返回注意力得分。示例代码如下:

from allennlp.modules.attention import LinearAttention, CosineAttention, BilinearAttention, DotProductAttention

attn = DotProductAttention()
enc = Encoder(INPUT_DIM, ENC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, ENC_DROPOUT, num_layers = ENC_NUM_LAYERS, bidirectional = ENC_BIDIRECTIONAL)
dec = Decoder(OUTPUT_DIM, DEC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, DEC_DROPOUT, attn, bidirectional_input = ENC_BIDIRECTIONAL)
model = Seq2Seq(enc, dec, device).to(device)

在训练过程中,我们绘制了每个注意力模型的损失和困惑度(PPL)训练图。表现最好的三种方法是 Bahdanau、点积和双线性模型。余弦和线性注意力难以收敛,特别是线性注意力的注意力机制与输入序列完全不相关。在一些解码注意力输出的示例中,大多数情况下注意力仍与输入对齐,但预测大多不正确,在接近最大训练序列长度的时间步附近通常具有高熵。

以下是整个训练流程的 mermaid 流程图:

graph LR
    A[数据准备] --> B[模型定义]
    B --> C[模型训练]
    C --> D[验证集评估]
    D --> E{验证损失是否改善}
    E -- 是 --> F[继续训练]
    E -- 否 --> G[降低学习率]
    G --> F
    F --> H[达到最大周期]
    H --> I[选择最佳模型]
    I --> J[测试集评估]

综上所述,记忆增强网络在自然语言处理任务中展现出了强大的能力,不同的架构和注意力机制在不同任务中有各自的优势和局限性。通过案例研究,我们可以更深入地了解如何应用这些技术来解决实际问题。

5 不同注意力机制的性能分析

5.1 训练结果对比

在训练过程中,我们对五种不同的注意力机制进行了比较,以下是它们在训练过程中的一些关键指标对比:
|注意力机制|收敛情况|预测准确性|高熵区域|
| ---- | ---- | ---- | ---- |
|Bahdanau|收敛较快,表现稳定|较高|在接近最大训练序列长度时,仍能保持相对低的熵|
|点积|收敛速度适中|较高|在接近最大训练序列长度时,熵有所增加|
|双线性|收敛情况较好|较高|在接近最大训练序列长度时,熵增加明显|
|余弦|难以收敛|较低|整个训练过程中熵都较高|
|线性|难以收敛,与输入序列不相关|很低|整个训练过程中熵极高|

从这些对比中可以看出,Bahdanau、点积和双线性注意力机制在收敛速度和预测准确性方面表现较好,而余弦和线性注意力机制存在较大的问题。

5.2 解码注意力输出分析

我们通过观察不同文件的解码注意力输出,进一步分析了注意力机制的工作情况。以下是一些具体的分析步骤:
1. 选择不同长度的输入 :选取长度为 10 的输入(这是模型在训练过程中见过的最大长度)。
2. 观察注意力对齐情况 :大多数情况下,注意力仍能与输入对齐,但预测大多不正确。
3. 分析高熵区域 :在接近最大训练序列长度的时间步附近,预测通常具有高熵,这意味着模型在处理长序列时存在困难。

下面是一个简单的 mermaid 流程图,展示了解码注意力输出的分析流程:

graph LR
    A[选择输入文件] --> B[选取长度为 10 的输入]
    B --> C[观察注意力对齐情况]
    C --> D{注意力是否对齐}
    D -- 是 --> E[分析预测准确性]
    D -- 否 --> F[记录未对齐情况]
    E --> G{预测是否正确}
    G -- 是 --> H[记录正确预测]
    G -- 否 --> I[分析高熵区域]

6 总结与建议

6.1 总结

  • 记忆增强网络的优势 :记忆增强网络在自然语言处理任务中具有强大的能力,能够处理复杂的 NLU 任务,如问答和语义角色标注。不同的架构,如神经栈、神经队列、神经双端队列和循环实体网络,都有各自的特点和适用场景。
  • 注意力机制的性能差异 :在基于注意力的神经机器翻译任务中,不同的注意力机制表现差异较大。Bahdanau、点积和双线性注意力机制在收敛速度和预测准确性方面表现较好,而余弦和线性注意力机制存在收敛困难和预测不准确的问题。
  • 模型训练的关键因素 :模型训练过程中,学习率、动量、Dropout 和梯度裁剪等因素对模型的性能有重要影响。合理设置这些参数可以提高模型的收敛速度和泛化能力。

6.2 建议

  • 选择合适的注意力机制 :在实际应用中,根据任务的特点和需求,选择合适的注意力机制。对于大多数自然语言处理任务,Bahdanau、点积和双线性注意力机制是较好的选择。
  • 优化模型训练参数 :在模型训练过程中,通过调整学习率、动量、Dropout 和梯度裁剪等参数,优化模型的性能。可以使用学习率调度器根据验证损失动态调整学习率。
  • 处理长序列问题 :针对模型在处理长序列时存在的困难,可以尝试使用更长的训练序列、增加内存单元或采用分层注意力机制等方法。

6.3 未来展望

随着自然语言处理技术的不断发展,注意力与记忆增强网络有望在更多领域得到应用。未来的研究可以集中在以下几个方面:
- 改进注意力机制 :设计更加高效和灵活的注意力机制,以提高模型在处理复杂任务时的性能。
- 融合多种模型架构 :将不同的模型架构进行融合,充分发挥它们的优势,以解决更复杂的自然语言处理问题。
- 处理多模态数据 :将注意力与记忆增强网络应用于多模态数据处理,如文本、图像和语音的联合处理。

总之,注意力与记忆增强网络为自然语言处理带来了新的思路和方法。通过深入研究和不断实践,我们可以更好地利用这些技术解决实际问题,推动自然语言处理技术的发展。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值