LSTM模型详解

LSTM “权值共享”:

一个LSTM单元在每个时间步 t 的计算都遵循相同的公式,并使用同一套参数 W_f, W_i, W_o, W_c, U_f, U_i, U_o, U_c 和偏置 b_f, b_i, b_o, b_c

这些参数在时间维度上是“共享”的。无论你处理的是句子的第一个单词还是最后一个单词,计算遗忘门、输入门、输出门和候选细胞状态时,使用的都是这同一套参数。

共享的体现:
对于一串序列 [x_1, x_2, x_3, ..., x_T],LSTM单元像一个小型循环机器,在 t=1 时用这套参数计算,在 t=2, t=3, ..., t=T 时,反复使用同一套参数进行计算。

为什么需要权值共享?

  1. 减少参数量:这是最关键的原因。如果不共享权重,每个时间步都有自己独特的参数,那么模型参数会随着序列长度的增加而线性增长,导致模型无法训练(参数量巨大,极易过拟合)。共享权重使得参数量仅由LSTM单元的隐藏层大小决定,与序列长度无关。

  2. 泛化能力:共享权重迫使模型学习到一种适用于所有时间步的通用规则。例如,在自然语言处理中,无论一个动词出现在句首还是句末,模型都应该用同样的“规则”去处理它。这极大地增强了模型的泛化能力。

  3. 处理变长序列:因为参数固定,所以同一个LSTM模型可以处理任意长度的序列输入,这在处理像句子、音频等不等长的数据时至关重要。

重要区别:需要注意的是,LSTM的权值共享是跨时间步的共享。这与卷积神经网络(CNN)中跨空间位置的权值共享(同一个卷积核扫描整张图片)在思想上一脉相承,但应用维度不同。

 

单层LSTM vs. 多层LSTM

单层LSTM (Single-Layer LSTM)

  • 结构:只有一个LSTM层。每个时间步的输入是当前时间步的原始数据(或嵌入向量)和上一个时间步的隐藏状态,输出是该时间步的隐藏状态。

  • 计算流程:

    • h_t^1, C_t^1 = LSTM1(x_t, h_{t-1}^1, C_{t-1}^1)

    • 上标 1 表示第一层。

  • 特点:

    • 模型简单,参数量少,训练速度快,不易过拟合。

    • 学习能力有限,通常只能捕捉到相对浅层或简单的时序依赖关系。

    • 适用于简单任务或数据量较少的情况。

多层LSTM (Multi-Layer LSTM / Stacked LSTM)

  • 结构:将多个LSTM层堆叠起来。下一层的输入是上一层的输出。即,第 l 层在时间步 t 的输入是第 l-1 层在时间步 t 的隐藏状态 h_t^{l-1}

  • 计算流程(以两层为例):

    • 第一层: h_t^1, C_t^1 = LSTM1(x_t, h_{t-1}^1, C_{t-1}^1)

    • 第二层: h_t^2, C_t^2 = LSTM2(h_t^1, h_{t-1}^2, C_{t-1}^2) (注意:h_t^1 作为第二层的输入)

    • 最终输出通常取自最顶层LSTM的隐藏状态 h_t^L(L是总层数)

  • 特点:

    • 模型复杂,参数量大,学习能力强。

    • 能够构建层次化的特征表示:

      • 底层LSTM:捕捉底层的、局部的时序模式(例如,单词的形态、相邻单词之间的关系)。

      • 高层LSTM:基于底层提供的特征,捕捉更高级的、全局的、抽象的时序模式(例如,句子语义、长距离依赖关系)。

    • 缺点:需要更多数据训练,训练速度慢,更容易过拟合,且可能更难以优化(如梯度消失/爆炸问题,虽然LSTM本身缓解了这一点,但在非常深的网络中仍可能存在)。

  • 应用:广泛应用于复杂的序列建模任务,如机器翻译、文本生成、语音识别等,这些任务通常需要理解不同层次的语义信息。

 

LSTM输入和输出

在每个时间步 t,LSTM 单元接收三个输入:

  1. 当前输入 x_t

  2. 上一个时间步的隐藏状态 h_{t-1}

  3. 上一个时间步的细胞状态 C_{t-1}

经过内部计算(遗忘门、输入门、输出门等),它会产生两个输出:

  1. 当前隐藏状态 h_t

  2. 当前细胞状态 C_t

这两个输出的用途完全不同:

  1. 隐藏状态 h_t

    • 作为输出:h_t 通常被视为 LSTM 在当前时间步的输出向量。在做序列标注(如词性标注)或每个时间步都需要预测(如机器翻译逐词生成)的任务时,我们会用 h_t 来接一个分类器(如全连接层+Softmax)来进行预测。

    • 作为下一时间步的输入:h_t 会传递给下一个时间步 t+1,为其提供“之前的记忆”信息。

    • 作为多层LSTM的输入:在多层LSTM中,当前层的 h_t 会作为下一层在相同时间步 t 的输入。

  2. 细胞状态 C_t

    • 纯粹的内部记忆:C_t 几乎从不直接作为模型对外预测的输出。它的唯一使命是在LSTM单元内部沿着时间轴传递信息,确保长期依赖得以保留。它被传递给下一个时间步 t+1,作为其“长期记忆”的输入。

    • 不参与层间传递:在多层LSTM中,每一层都有自己的细胞状态。第1层的 C_t 只会传递给第1层的 t+1 时刻,而不会传递给第2层。

对于一个 num_layers * num_directions 层的 LSTM,输入一个维度为 (seq_len, batch, input_size) 的张量 X

  • 输出 h (隐藏状态) 的维度: (num_layers * num_directions, batch, hidden_size)

  • 输出 c (细胞状态) 的维度: (num_layers * num_directions, batch, hidden_size)

 

torch.nn.LSTM 的输出

torch.nn.LSTM 的输出是一个包含两个元素的元组:output 和 (h_n, c_n)。 

output, (h_n, c_n) = lstm(input, (h_0, c_0))

1. output (序列输出)

  • 形状: (sequence_length, batch, num_directions * hidden_size)
    或者 (batch, sequence_length, num_directions * hidden_size) (如果 batch_first=True

  • 内容: 这是所有时间步(完整序列)的隐藏状态(hidden states)。

    • 它包含了从第一个时间步到最后一个时间步,每个时间步对应的隐藏状态 h_t

    • 如果你设置 bidirectional=True(双向LSTM),output 的最后一个维度是 2 * hidden_size。通常,前 hidden_size 个元素是前向LSTM的输出,后 hidden_size 个元素是后向LSTM的输出。

    • 这是最常用的输出,因为你得到了整个序列的编码信息,适用于序列标注、作为注意力机制的输入等任务。

示例: 对于一个长度为 5 的序列 (seq_len=5),output 是一个包含 5 个张量的序列,每个张量对应一个时间步的隐藏状态。


2. h_n (最终的隐藏状态)

  • 形状: (num_layers * num_directions, batch, hidden_size)

  • 内容: 这是最后一个时间步的隐藏状态。

    • 对于多层LSTM,h_n 包含了每一层在最后一个时间步的隐藏状态。

    • 如果LSTM是双向的 (bidirectional=True),num_directions 为 2。第一行是所有层的前向最终隐藏状态,第二行是所有层的后向最终隐藏状态。

    • 它代表了模型处理完整个输入序列后的“短期记忆”或“总结”。

示例: 对于一个 2 层的LSTM (num_layers=2),h_n 的形状是 [2, batch_size, hidden_size]h_n[0] 是第一层最后一个时间步的隐藏状态,h_n[1] 是第二层最后一个时间步的隐藏状态。


3. c_n (最终的细胞状态)

    • 形状: (num_layers * num_directions, batch, hidden_size)

    • 内容: 这是最后一个时间步的细胞状态(Cell State)。

      • 其结构和索引方式与 h_n 完全一样,但它包含的是细胞状态而不是隐藏状态。

      • 它代表了模型处理完整个输入序列后的“长期记忆”。

      • 通常只在需要将状态传递给下一个序列(如用在编码器-解码器架构中)时才会使用,大部分简单的分类任务中不太常用。

如果分析序列的每一个部分(例如,做词性标注、命名实体识别):使用 output

如果为整个序列做一个预测(例如,句子情感分类):通常使用 h_n 的最后一层(h_n[-1]),因为它包含了所有之前时间步的信息。有时也会使用 output 的最后一个时间步,或者对 output 的所有时间步做池化(如平均池化)。

如果在构建编码器-解码器模型(如机器翻译):将 (h_n, c_n) 作为解码器的初始状态。

 

双向LSTM

双向LSTM(Bi-Directional LSTM,BiLSTM)是一种强大的序列建模架构,它的核心作用是让模型能够同时利用序列的过去和未来上下文信息来进行当前时间步的预测。

核心思想

传统单向LSTM只能从左到右处理序列,当前时间步的预测只能基于之前的信息。而双向LSTM同时运行两个LSTM:

  1. 前向LSTM (Forward LSTM):按正常顺序处理序列(从 t=1 到 t=T

  2. 后向LSTM (Backward LSTM):按逆序处理序列(从 t=T 到 t=1

然后将两个方向的隐藏状态在每个时间步进行组合(通常是拼接),形成最终的输出表示。

时间序列:    [   词1,     词2,     词3,     词4    ]
            │       │       │       │
前向LSTM:   → h1_f → h2_f → h3_f → h4_f
后向LSTM:   h1_b ← h2_b ← h3_b ← h4_b ←
            │       │       │       │
组合输出:    [h1_f⊕h1_b, h2_f⊕h2_b, h3_f⊕h3_b, h4_f⊕h4_b]
import torch
import torch.nn as nn

# 创建双向LSTM
bidirectional_lstm = nn.LSTM(
    input_size=100,    # 输入维度
    hidden_size=128,   # 隐藏层维度
    num_layers=1,      # 层数
    bidirectional=True, # 设置为双向
    batch_first=True
)

# 输入数据: (batch_size, seq_len, input_size)
input_data = torch.randn(16, 10, 100)  # 16个样本,序列长度10,维度100

# 前向传播
output, (h_n, c_n) = bidirectional_lstm(input_data)

print(f"Output shape: {output.shape}")  # torch.Size([16, 10, 256])
# 注意:输出维度是 2 * hidden_size = 256

适用场景

  • 序列标注任务(NER、POS、分词等)

  • 需要完整上下文理解的任务

  • 编码器-解码器架构中的编码器


nn.LSTM模块参数

input_size :输入的维度

hidden_size:h的维度

num_layers:堆叠LSTM的层数,默认值为1

bias:偏置 ,默认值:True

batch_first: 如果是True,则input为(batch, seq, input_size)。默认值为:False(seq_len, batch, input_size)

bidirectional :是否双向传播,默认值为False

 

输入

(input_size, hideen_size)

以训练句子为例子,假如每个词是100维的向量,每个句子含有24个单词,一次训练10个句子。那么batch_size=10,seq=24,input_size=100。(seq指的是句子的长度,input_size作为一个的输入) ,所以在设置LSTM网络的过程中input_size=100。由于seq的长度是24,那么这个LSTM结构会循环24次最后输出预设的结果。如下图所示。

预设的hidden_size,这个hideen_size主要是下面LSTM公式中的各个W和b的维度设置,以g_{t}为例子,假设hideen_size为16,则W_{ig}为16*100,x_{t}为100*1,W_{hg}为16*16,h_{t-1}为16*1。

 

输出

output:(seq_len, batch, num_directions * hidden_size

h_n:(num_layers * num_directions, batch, hidden_size)

c_n :(num_layers * num_directions, batch, hidden_size
注:num_directions 表示单向、双向

单向

import torch.nn as nn
import torch
x = torch.rand(10,24,100)
lstm = nn.LSTM(100,16,num_layers=2)
output,(h,c) = lstm(x)
print(output.size())
print(h.size())
print(c.size())
 
output:
torch.Size([24, 10, 16])
torch.Size([2, 10, 16])
torch.Size([2, 10, 16])

双向

import torch.nn as nn
import torch
x = torch.rand(10,24,100)
lstm = nn.LSTM(100,16,bidirectional=True)
output,(h,c) = lstm(x)
print(output.size())
print(h.size())
print(c.size())
 
output:
torch.Size([24, 10, 32])
torch.Size([2, 10, 16])
torch.Size([2, 10, 16])

使用h0、c0

import torch.nn as nn
import torch
x = torch.rand(24,10,100) #seq,batch,input_size
h0 = torch.rand(1,10,16)# num_layers*num_directions, batch, hidden_size
c0 = torch.rand(1,10,16)
lstm = nn.LSTM(100,16)
output,(h,c) = lstm(x,(h0,c0))

 

posted @ 2023-08-28 14:01  wangssd  阅读(2750)  评论(0)    收藏  举报