论文:FEED-FORWARD NETWORKS WITH ATTENTION CAN SOLVE SOME LONG-TERM MEMORY PROBLEMS

发布时间 2023-11-29 10:20:26作者: 辛宣

题目:FEED-FORWARD NETWORKS WITH ATTENTION CAN SOLVE SOME LONG-TERM MEMORY PROBLEMS” (Raffel 和 Ellis, 2016, p. 1)

“带有注意力的前馈网络可以解决一些长期记忆问题” (Raffel 和 Ellis, 2016, p. 1) (pdf)

这篇论文提出了一种适用于前馈神经网络的简化注意力模型,并展示了这个模型可以解决序列长度比已发布的最佳结果更长和变化范围更广的合成“加法”和“乘法”长期记忆问题

这篇论文提出了一种适用于前馈神经网络的简化注意力模型,并展示了这个模型可以解决序列长度比已发布的最佳结果更长和变化范围更广的合成“加法”和“乘法”长期记忆问题【19†source】。

原理

image

注意力机制允许模型的不同时间点状态之间有更直接的依赖关系。在这篇论文中,注意力基础模型在每个时间步骤产生一个隐藏状态 ( h_t ),然后计算一个作为状态序列加权平均的“上下文”向量 ( c_t ),权重 ( \alpha{tj} ) 在每个时间步骤 t 为每个状态 ( h_j ) 计算。然后,这些上下文向量被用来计算一个新的状态序列 ( s ),其中 ( s_t ) 取决于 ( s ) 和在 ( t-1 ) 时刻的模型输出。这些权重是通过一个学习函数 ( a ) 来计算的,该函数被认为是计算给定 ( h_j ) 和前一个状态 ( s_{t-1} ) 的 ( h_j ) 的标量重要性值【20†source】。

论文中还提出了一种简化的前馈注意力机制。在这种机制中,注意力被看作是通过计算状态序列的自适应加权平均来产生输入序列的固定长度嵌入 ( c )。使用这种简化形式的注意力,模型可以处理变长序列,即使 ( h_t ) 的计算是前馈的,即 ( h_t = f(x_t) )。使用前馈函数 ( f ) 还可以实现大幅度的效率提升,因为计算可以完全并行化【21†source】。

限制

然而,论文也指出了模型的一个明显限制,即在时间顺序重要的任务上会失败,因为随时间计算的平均值会丢失顺序信息。尽管如此,作者提出,在一些实际任务中,处理非常长的序列比时间顺序的重要性要小得多。例如,在文本文档分类中,单词的顺序对于许多任务来说并不重要。论文的实验明确展示了包含注意力机制可以让模型在计算其输出时引用序列中的特定点。它们也为Bahdanau等人在2014年提出的观点提供了另一个论据,即注意力帮助模型处理非常长和长度变化极大的序列。作者对提出的前馈模型在需要序列时间整合的额外真实世界问题中的效益持乐观态度,并且表示需要进一步的研究。为了方便未来的工作,所有在实验中使用的代码都可以在线获取【22†source】。
代码

点击查看代码
import torch
import torch.nn as nn

class LSTMModel(nn.Module):
    """Single task LSTM model with attention mechanism."""
    
    def __init__(self, cfg, lstmmodel_cfg):
        """
        Initialize the LSTM model with attention mechanism.
        
        Parameters:
        - cfg: A dictionary containing configuration such as dropout rate.
        - lstmmodel_cfg: A dictionary containing configuration for LSTM such as input_size, hidden_size, and out_size.
        """
        super(LSTMModel, self).__init__()
        self.lstm = nn.LSTM(
            input_size=lstmmodel_cfg["input_size"], 
            hidden_size=lstmmodel_cfg["hidden_size"], 
            batch_first=True
        )
        self.drop = nn.Dropout(p=cfg["dropout_rate"])
        self.dense = nn.Linear(lstmmodel_cfg["hidden_size"], lstmmodel_cfg["out_size"])
        self.attention_vector = nn.Parameter(torch.rand(lstmmodel_cfg["hidden_size"], 1))

    def attention(self, lstm_output):
        """
        Compute attention weights and apply to LSTM outputs.

        Parameters:
        - lstm_output: The output of the LSTM layer.

        Returns:
        - The context vector, attention weights.
        """
        attention_weights = torch.tanh(lstm_output) @ self.attention_vector
        attention_weights = torch.softmax(attention_weights.squeeze(2), dim=1)
        context_vector = torch.sum(lstm_output * attention_weights.unsqueeze(-1), dim=1)
        return context_vector, attention_weights

    def forward(self, inputs):
        """
        Forward pass for the LSTM model with attention mechanism.
        
        Parameters:
        - inputs: The input tensor to the LSTM.

        Returns:
        - The output of the model after applying LSTM, attention, dropout, and dense layer.
        """
        # Pass input through LSTM layer
        lstm_out, (hidden, cell) = self.lstm(inputs.float())
        
        # Compute attention
        context_vector, attention_weights = self.attention(lstm_out)
        
        # Apply dropout to the context vector
        context_vector = self.drop(context_vector)
        
        # Pass the context vector through the dense layer to get final output
        output = self.dense(context_vector)
        
        return output

# Example configuration
cfg = {
    "dropout_rate": 0.5
}

lstmmodel_cfg = {
    "input_size": 10,  # 10 features
    "hidden_size": 128, # size of LSTM hidden states
    "out_size": 1  # output size
}

# Instantiate the model
model = LSTMModel(cfg, lstmmodel_cfg)

# Print the model
print(model)

# Example input
inputs = torch.randn(64, 7, 10)  # 64 batches, 7 days, 10 features

# Forward pass
outputs = model(inputs)
print(outputs.shape)  # Expected shape: (64, 1) since we are predicting one output per batch



attention_weights = torch.tanh(lstm_output) @ self.attention_vector这里面的@ self.attention_vector是啥意思
attention_weights = torch.tanh(lstm_output) @ self.attention_vector这里面的@ self.attention_vector是啥意思
在PyTorch中,符号 `@` 表示矩阵乘法。这是Python 3.5+ 引入的操作符,用于替代 `numpy` 和 `torch` 中的 `matmul` 函数。

在您提供的代码段:

```python
attention_weights = torch.tanh(lstm_output) @ self.attention_vector

lstm_output 通常是一个三维张量,其形状为 (batch_size, sequence_length, hidden_size)self.attention_vector 是一个二维参数向量,其形状为 (hidden_size, 1)。在这里,torch.tanh(lstm_output)lstm_output 的每个元素应用了双曲正切函数,这是一种激活函数,可以将输入值压缩到 ([-1, 1]) 的范围内。

@ 操作符执行矩阵乘法。在这个上下文中,它将 lstm_output 的每个时间步骤的输出与 attention_vector 相乘,结果是一个形状为 (batch_size, sequence_length, 1) 的张量,它表示每个时间步骤对于计算上下文向量 c 的重要性。这个权重随后会通过 softmax 函数标准化,以确定最终的注意力分布。

所以简而言之,这行代码计算了每个时间步的隐藏状态的加权和,其中权重由 attention_vector 决定,其结果是注意力权重,表示模型应该在序列的哪些部分集中注意力。

</details>


<details>
<summary>self.attention_vector = nn.Parameter(torch.rand(lstmmodel_cfg["hidden_size"], 1)),为啥有rand?论文里有?

</summary>

在您提供的代码段中:

self.attention_vector = nn.Parameter(torch.rand(lstmmodel_cfg["hidden_size"], 1))

这行代码创建了一个随机初始化的参数向量,它将被用作注意力机制中的权重。在PyTorch中,nn.Parameter是一种特殊类型的张量,它会自动被认为是模型的参数,因此在训练过程中会被优化。

使用torch.rand来初始化参数是一种常见的做法,它从一个均匀分布中随机生成数值,其值在0到1之间。这是为了打破对称性并允许梯度下降算法在训练过程中有效地工作。随机初始化可以帮助避免所有权重开始时具有相同的值,这可能导致优化过程陷入局部最小值。

关于论文内容,根据您引用的内容,论文中提出了一种简化的注意力机制,它可以用于生成整个序列的单个向量 ( c ),并且提到了权重 ( \alpha ) 是通过可学习的函数 ( a ) 来计算的【20†source】【21†source】。虽然论文中没有明确提到使用torch.rand初始化,但在实际应用中,参数的随机初始化是标准做法,特别是在没有先验信息指导初始化时。论文提出的模型框架是理论上的,而代码实现时的细节如参数初始化通常会在具体的实验设置中详细说明。

</details>

------------