rnn

GitHub
944 309 困难 1 次阅读 3周前BSD-3-Clause语言模型开发框架
AI 解读 由 AI 自动生成,仅供参考

rnn 是一个专为 Torch7 深度学习框架设计的循环神经网络(RNN)扩展库。它旨在解决传统神经网络难以有效处理序列数据(如文本、时间序列)的痛点,通过提供模块化的组件,让开发者能够轻松构建复杂的时序模型。

该工具主要面向使用 Lua/Torch 进行算法开发的科研人员与工程师。借助 rnn,用户可以快速搭建包括标准 RNN、长短期记忆网络(LSTM)、门控循环单元(GRU)以及双向网络(BiRNN)在内的多种主流架构。其独特的技术亮点在于提供了高度优化的序列处理模块(如 SeqLSTM、SeqGRU),能够一次性处理整个序列而非逐步迭代,显著提升了训练效率。此外,它还内置了注意力机制、范数稳定化准则以及针对填充数据的掩码处理功能,有效解决了长序列训练中的梯度不稳定和计算冗余问题。虽然官方已提示该项目归档并推荐迁移至新版 torch/rnn,但其设计思路与丰富的功能模块依然是理解和使用 Torch 生态中序列建模的重要参考。

使用场景

某自然语言处理团队正在基于 Torch7 框架开发一个实时金融新闻情感分析系统,需要处理带有时间依赖性的文本序列数据。

没有 rnn 时

  • 开发者必须手动编写复杂的循环逻辑来模拟时间步传播,代码冗长且极易出错,难以维护。
  • 想要尝试更先进的 LSTM 或 GRU 单元以提升准确率时,需从零推导数学公式并实现反向传播,研发周期长达数周。
  • 处理变长句子填充(Padding)时,缺乏原生支持,导致大量无效计算浪费 GPU 资源,推理延迟居高不下。
  • 构建双向网络捕捉上下文信息时,需自行拼接正向和反向传递结果,调试困难且容易引发维度不匹配错误。

使用 rnn 后

  • 直接调用 SequencerLSTM 模块即可自动处理序列时间步,将核心模型代码缩减至几十行,逻辑清晰直观。
  • 通过切换 FastLSTMSeqGRU 等预置模块,几分钟内即可完成模型架构升级,显著提升了情感分类的准确度。
  • 利用 MaskZeroLookupTableMaskZero 原生支持零值掩码,自动忽略填充部分的计算,推理速度提升约 40%。
  • 借助 BiSequencer 一键构建双向循环网络,轻松捕捉前后文语境,无需关心底层数据流向的细节实现。

rnn 通过提供模块化、高性能的循环神经网络组件,让开发者从繁琐的底层实现中解放出来,专注于算法优化与业务落地。

运行环境要求

操作系统
  • Linux
  • macOS
GPU
  • 可选
  • 若使用 CUDA 加速,需 NVIDIA GPU 及对应的 cutorch/cunn/cunnx 支持(具体型号和显存未说明,示例中提到在 NVIDIA Titan X 上运行)
内存

未说明

依赖
notes1. 该仓库已弃用,官方建议迁移至 https://github.com/torch/rnn。 2. 这是一个基于 Lua 和 Torch7 框架的库,不使用 Python。 3. 安装需使用 luarocks 包管理器。 4. 部分高性能示例(如噪声对比估计)明确要求安装使用 Lua 而非 LuaJIT 版本的 Torch。 5. 若遇到错误,建议更新上述依赖库。
python不适用 (该项目基于 Lua/Torch7,非 Python)
torch
nn
dpnn
torchx
cutorch (如需 CUDA)
cunn (如需 CUDA)
cunnx (如需 CUDA)
rnn hero image

快速开始

rnn: 循环神经网络

注意:此仓库已被弃用,推荐使用 https://github.com/torch/rnn。

这是一个扩展了 Torch 的 nn 模块的循环神经网络库。你可以用它来构建 RNN、LSTM、GRU、双向 RNN、双向 LSTM 等等。

该库包含以下对象的文档:

将连续的 forward 调用视为序列中不同时间步的模块:

通过装饰后的 AbstractRecurrent 实例对整个序列进行 forward 的模块:

  • AbstractSequencer:由 Sequencer、Repeater、RecurrentAttention 等继承的抽象类;
  • Sequencer:将封装的模块应用于输入序列中的所有元素(张量或表);
  • SeqLSTMnn.Sequencer(nn.FastLSTM) 的极速版本,其中 inputoutput 均为张量;
  • SeqLSTMP:带有投影层的 SeqLSTM
  • SeqGRUnn.Sequencer(nn.GRU) 的极速版本,其中 inputoutput 均为张量;
  • SeqBRNN:基于 SeqLSTM 的双向 RNN;
  • BiSequencer:用于实现双向 RNN 和 LSTM;
  • BiSequencerLM:用于实现语言模型的双向 RNN 和 LSTM;
  • Repeater:将相同的输入重复应用于一个 AbstractRecurrent 实例;
  • RecurrentAttention:一种针对 REINFORCE 模块的通用注意力模型;

其他模块和损失函数:

  • MaskZero:对于输入中与零张量对应的行,将被装饰模块的 outputgradOutput 行置零;
  • TrimZero:行为与 MaskZero 相同,但在输入包含大量零掩码行时效率更高;
  • LookupTableMaskZero:扩展 nn.LookupTable 以支持用于填充的零索引。零索引会被当作零张量传递;
  • MaskZeroCriterion:对于输入中与零张量对应的行,将被装饰损失函数的 gradInputerr 行置零;
  • SeqReverseSequence:在特定维度上反转输入序列;

用于处理序列型输入和目标的损失函数:

  • SequencerCriterion:依次将同一损失函数应用于输入和目标序列(张量或表)。
  • RepeaterCriterion:对同一个目标重复应用同一损失函数于一个序列。

安装此仓库的方法如下:

git clone git@github.com:Element-Research/rnn.git
cd rnn
luarocks make rocks/rnn-scm-1.rockspec

请注意,现在执行 luarocks install rnn 将会安装 https://github.com/torch/rnn。

示例

以下是使用本包的一些训练脚本示例:

外部资源

引用

如果您在工作中使用了 rnn,我们非常感谢您能引用以下论文:

Léonard, Nicholas, Sagar Waghmare, Yang Wang, and Jin-Hwa Kim. rnn: Recurrent Library for Torch. arXiv 预印本 arXiv:1511.07889 (2015)。

任何对该库做出重大贡献的人都将被添加为论文的作者。 重大贡献者 是指向该库添加至少 300 行代码的人。

故障排除

大多数问题可以通过更新各种依赖项来解决:

luarocks install torch
luarocks install nn
luarocks install dpnn
luarocks install torchx

如果您使用 CUDA:

luarocks install cutorch
luarocks install cunn
luarocks install cunnx

别忘了更新这个包:

git clone git@github.com:Element-Research/rnn.git
cd rnn
luarocks make rocks/rnn-scm-1.rockspec

如果这样仍然无法解决问题,请在 GitHub 上提交一个问题。

AbstractRecurrent

RecurrentLSTMGRU 继承的抽象类。 构造函数接受一个参数:

rnn = nn.AbstractRecurrent([rho])

参数 rho 是反向传播通过时间(BPTT)的最大步数。 子类可以将其设置为一个很大的数字,比如 99999(默认值),如果他们希望对整个序列进行反向传播,无论其长度如何。设置较小的 rho 值在处理长序列时很有用,但我们只希望对最后 rho 步进行反向传播,这意味着不需要存储序列的其余部分(因此没有额外的成本)。

[recurrentModule] getStepModule(step)

返回第 step 个时间步的模块。这由子类内部使用,以获取内部 recurrentModule 的副本。这些副本共享 parametersgradParameters,但各自拥有自己的 outputgradInput 以及任何其他中间状态。

setOutputStep(step)

这是为 Recursor 在反向传播时保留的内部方法。它将对象的 output 属性设置为指向第 step 个时间步的输出。 引入此方法是为了修复一个非常烦人的错误。

maskZero(nInputDim)

MaskZero 装饰内部 recurrentModule。当 input 中相应行是零张量时,recurrentModuleoutput 张量(或其表)中的每一行(即样本)都会被置零。

nInputDim 参数必须指定 input 中第一张张量的非批次维度数量。对于 input 表格,第一张张量是在深度优先搜索中遇到的第一张。

调用此方法可以使同一批次中不同长度的序列用零向量填充。

当某个样本时间步被掩蔽(即 input 是一行零)时,隐藏状态实际上会在下一个非掩蔽时间步被重置(即遗忘)。换句话说,可以用掩蔽元素将不相关的序列分开。

trimZero(nInputDim)

TrimZero 装饰内部 recurrentModule

[output] updateOutput(input)

向前传播当前步骤的输入。前几步的输出或中间状态会被递归地使用。这对调用者来说是透明的,因为之前的输出和中间状态会被记住。此方法还会将 step 属性加 1。

updateGradInput(input, gradOutput)

backward 类似,此方法应按照与传播序列时的 forward 调用相反的顺序调用。例如:

rnn = nn.LSTM(10, 10) -- AbstractRecurrent 实例
local outputs = {}
for i=1,nStep do -- 向前传播序列
   outputs[i] = rnn:forward(inputs[i])
end

for i=nStep,1,-1 do -- 按照相反顺序向后传播序列
   gradInputs[i] = rnn:backward(inputs[i], gradOutputs[i])
end

rnn:forget()

相反的顺序实现了通过时间的反向传播(BPTT)。

accGradParameters(input, gradOutput, scale)

updateGradInput 类似,但用于累积相对于参数的梯度。

recycle(offset)

此方法与 forget 密切相关。当当前时间步大于 rho 时,它很有用,此时会开始回收最旧的 recurrentModule sharedClones,以便它们可以用于存储下一步。此 offset 用于像 nn.Recurrent 这样的模块,它们在第一步使用不同的模块。默认偏移量为 0。

forget(offset)

此方法将所有状态重置到序列缓冲区的起始位置, 即忘记当前序列。它还会将 step 属性重置为 1。 强烈建议在每次参数更新后调用 forget 方法。 否则,前一个状态会被用来激活下一个状态,这通常会导致不稳定。 这是因为前一个状态是基于已经改变的参数计算得出的。 此外,在每个新序列开始时调用 forget 也是一个良好的实践。

maxBPTTstep(rho)

此方法设置进行时间反向传播(BPTT)的最大时间步数。例如,如果你将此值设置为 rho = 3 个时间步, 然后进行 4 步前向传播,再进行反向传播,那么只有最后 3 步会被用于反向传播。如果你的 AbstractRecurrent 实例被 Sequencer 包装, Sequencer 会自动处理这一过程。否则,将此值设置为较大的数(如 9999999)对于大多数情况都是合适的。

backwardOnline()

此方法已于 2016 年 1 月 6 日弃用。 自那以后,AbstractRecurrent 实例默认使用 backwardOnline 行为。 详情请参阅 updateGradInput

training()

在训练模式下,网络会记住过去 rho(时间步数)内的所有状态。 这是进行 BPTT 所必需的。

evaluate()

在评估过程中,由于无需稍后执行 BPTT, 因此仅需记住上一步的状态即可。这样在内存使用上非常高效, 使得可以对潜在无限长度的序列进行评估。

循环神经网络

参考文献:

这是一个用于实现循环神经网络(RNN)的复合模块,不包括输出层。

nn.Recurrent(start, input, feedback, [transfer, rho, merge]) 构造函数接受 6 个参数:

  • start:输出的大小(不包括批次维度),或者是在传播的第一步中插入在 input 模块和 transfer 模块之间的模块。当 start 是一个大小(数字或 torch.LongTensor)时,这个 start 模块会被初始化为 nn.Add(start)(见参考文献 A)。
  • input:处理输入张量(或表)的模块。其输出必须与 start 的大小相同(或者在有 start 模块的情况下为其输出大小相同),并且与 feedback 模块的输出大小一致。
  • feedback:将前一步的输出张量(或表)反馈至 merge 模块的模块。
  • merge:一个表模块,用于在通过 transfer 模块之前合并 inputfeedback 模块的输出。
  • transfer:一个非线性模块,用于处理 merge 模块的输出,或者在第一步中处理 start 模块的输出。
  • rho:可回溯的最大反向传播步数。限制了存储在内存中的先前步骤数量。由于梯度消失效应,参考文献 A 和 B 建议将 rho 设置为 5(或更低)。默认值为 99999。

RNN 用于处理一系列输入。 序列中的每一步都应通过各自的 forward(和 backward)来传播, 一次处理一个 input(和 gradOutput)。 每次调用 forward 都会记录中间状态(input 和许多 Module.outputs), 并将 step 属性加 1。 backward 方法必须按照与 forward 调用相反的顺序调用, 以进行时间反向传播(BPTT)。这种逆序是必要的, 以便为每次 forward 调用返回对应的 gradInput

只有在调用 forget 方法时,step 属性才会重置为 1。 此时,模块便准备好处理下一个序列(或其批次)。 需要注意的是,序列越长,存储所有 outputgradInput 状态所需的内存就越多(每个时间步都需要一个状态)。

为了将此模块用于批次处理,我们建议在一个批次中使用多个相同大小的序列, 并在每 rho 步调用 updateParameters,在序列结束时调用 forget

请注意,调用 evaluate 方法会关闭长期记忆; RNN 将只记住上一步的输出。这使得 RNN 可以处理长序列而无需额外分配内存。

有关如何使用此模块的简单示例,请参阅 simple-recurrent-network.lua 训练脚本。

使用 Sequencer 进行装饰

请注意,任何 AbstractRecurrent 实例都可以用 Sequencer 进行装饰, 这样只需一次 forward/backward 调用就可以处理整个序列(一个表)。 实际上,这也是推荐的做法,因为它允许堆叠 RNN,并使 rnn 符合模块接口, 即每次调用 forward 后都可以立即调用 backward, 因为模型的每个输入都是一个完整的序列,即一个由张量组成的表, 其中每个张量代表一个时间步。

seq = nn.Sequencer(module)

simple-sequencer-network.lua 训练脚本 与上述 simple-recurrent-network.lua 脚本等效, 唯一的区别在于它用 Sequencer 装饰了 rnn,该 Sequencer 接受一个包含 inputsgradOutputs 的表(该批次的序列)。 这使得 Sequencer 能够自动处理序列的循环。

只有当你打算将其用于实时预测时,才需要考虑不使用 SequencerAbstractRecurrent 模块。 实际上,即使使用被 Sequencer 装饰的 AbstractRecurrent 实例, 也可以通过调用 Sequencer:remember() 并将每个时间步的输入表示为 {input} 来进行实时预测。 其他装饰器也可以使用,比如 RepeaterRecurrentAttentionSequencer 只是最常见的选择。

LSTM

参考文献:

这是一个标准的长短期记忆模块的实现。我们以参考文献A中的LSTM作为本模块的设计蓝图,因为它最为简洁。同时,这也是参考文献C中描述的标准LSTM。

nn.LSTM(inputSize, outputSize, [rho])构造函数接受三个参数:

  • inputSize:指定输入大小的数字;
  • outputSize:指定输出大小的数字;
  • rho:反向传播时回溯的最大步数。限制了保存在内存中的先前步骤数量。默认值为9999。

LSTM

实际实现对应于以下算法:

i[t] = σ(W[x->i]x[t] + W[h->i]h[t−1] + W[c->i]c[t−1] + b[1->i])      (1)
f[t] = σ(W[x->f]x[t] + W[h->f]h[t−1] + W[c->f]c[t−1] + b[1->f])      (2)
z[t] = tanh(W[x->c]x[t] + W[h->c]h[t−1] + b[1->c])                   (3)
c[t] = f[t]c[t−1] + i[t]z[t]                                         (4)
o[t] = σ(W[x->o]x[t] + W[h->o]h[t−1] + W[c->o]c[t] + b[1->o])        (5)
h[t] = o[t]tanh(c[t])                                                (6)

其中W[s->q]表示从sq的权重矩阵,t表示时间步,b[1->q]是进入q的偏置项,σ()为Sigmoid函数,x[t]为输入,i[t]为输入门(公式1),f[t]为遗忘门(公式2),z[t]为传入细胞的状态(我们称之为隐藏状态)(公式3),c[t]为细胞状态(公式4),o[t]为输出门(公式5),而h[t]则是该模块的输出(公式6)。另外需要注意的是,从细胞到门向量的权重矩阵都是对角矩阵W[c->s],其中s可以是ifo

正如你所见,与RNN不同,这个实现并不足够通用,无法在构造时接受任意组件模块的定义。然而,可以通过继承的方式轻松地对LSTM模块进行定制,只需重写不同的工厂方法:

  • buildGate:构建用于实现输入、遗忘和输出门的通用门;
  • buildInputGate:构建输入门(公式1)。目前调用buildGate
  • buildForgetGate:构建遗忘门(公式2)。目前调用buildGate
  • buildHidden:构建隐藏状态(公式3);
  • buildCell:构建细胞状态(公式4);
  • buildOutputGate:构建输出门(公式5)。目前调用buildGate
  • buildModel:构建内部使用的实际LSTM模型(公式6)。

请注意,我们建议将LSTMSequencer结合使用(详情请参阅这里)。

FastLSTM

龙短期记忆网络的一个更快版本。基本上,输入门、遗忘门、输出门以及隐藏状态是在一次计算中同时完成的。

需要注意的是,FastLSTM不使用细胞与门之间的窥视连接。其算法与LSTM相比有所变化:

i[t] = σ(W[x->i]x[t] + W[h->i]h[t−1] + b[1->i])                      (1)
f[t] = σ(W[x->f]x[t] + W[h->f]h[t−1] + b[1->f])                      (2)
z[t] = tanh(W[x->c]x[t] + W[h->c]h[t−1] + b[1->c])                   (3)
c[t] = f[t]c[t−1] + i[t]z[t]                                         (4)
o[t] = σ(W[x->o]x[t] + W[h->o]h[t−1] + b[1->o])                      (5)
h[t] = o[t]tanh(c[t])                                                (6)

即省略了公式1中的W[c->i]c[t−1]、公式2中的W[c->f]c[t−1]以及公式5中的W[c->o]c[t]这些项。

usenngraph

这是FastLSTM类的一个静态属性,默认值为false。将usenngraph设置为true会强制所有新实例化的FastLSTM对象使用nngraphnn.gModule来构建内部的recurrentModule,该模块会在每个时间步被克隆。

循环批量归一化

此扩展使FastLSTM类能够在训练过程中通过将输入到隐藏层以及隐藏层之间的变换中心化为零,从而加快收敛速度。它减少了时间步之间的内部协变量偏移。这是Cooijmans等人提出的循环批量归一化的一种实现。每个LSTM单元的隐藏层到隐藏层的转换按照以下方式归一化:

i[t] = σ(BN(W[x->i]x[t]) + BN(W[h->i]h[t−1]) + b[1->i])                      (1)
f[t] = σ(BN(W[x->f]x[t]) + BN(W[h->f]h[t−1]) + b[1->f])                      (2)
z[t] = tanh(BN(W[x->c]x[t]) + BN(W[h->c]h[t−1]) + b[1->c])                   (3)
c[t] = f[t]c[t−1] + i[t]z[t]                                                 (4)
o[t] = σ(BN(W[x->o]x[t]) + BN(W[h->o]h[t−1]) + b[1->o])                      (5)
h[t] = o[t]tanh(c[t])                                                        (6)

其中批量归一化变换为:

  BN(h; gamma, beta) = beta + gamma *      hd - E(hd)
                                      ------------------
                                       sqrt(E(σ(hd) + eps))                       

其中hd是要归一化的(预)激活向量,gammabeta是模型参数,分别决定归一化后激活的均值和标准差。eps是一个正则化超参数,用于保持除法运算的数值稳定性,而E(hd)E(σ(hd))分别是小批量中激活向量的均值和方差的估计值。作者建议将gamma初始化为一个小值,并发现0.1是既不会导致梯度消失又能有效工作的值。beta作为平移参数,默认值为null

要在训练中启用批量归一化,请执行以下操作:

nn.FastLSTM.bn = true
lstm = nn.FastLSTM(inputsize, outputsize, [rho, eps, momentum, affine]

其中momentum与上述公式中的gamma相同(默认值为0.1),eps如前所述,而affine是一个布尔值,用于决定是否关闭可学习的仿射变换(false)或开启(true,默认值)。

GRU

参考文献:

这是一个门控循环单元模块的实现。

nn.GRU(inputSize, outputSize [,rho [,p [, mono]]]) 构造函数与 nn.LSTM 类似,接受 3 个参数,或对于 dropout 接受 4 个参数:

  • inputSize:指定输入大小的数字;
  • outputSize:指定输出大小的数字;
  • rho:反向传播时回溯的最大步数。限制了保存在内存中的先前步骤数量。默认值为 9999;
  • p:GRU 内部连接的 dropout 概率。
  • mono:GRU 内部 dropout 的单调采样。仅在 TrimZero + BGRU(p>0) 的情况下需要。

GRU

实际实现对应于以下算法:

z[t] = σ(W[x->z]x[t] + W[s->z]s[t−1] + b[1->z])            (1)
r[t] = σ(W[x->r]x[t] + W[s->r]s[t−1] + b[1->r])            (2)
h[t] = tanh(W[x->h]x[t] + W[hr->c](s[t−1]r[t]) + b[1->h])  (3)
s[t] = (1-z[t])h[t] + z[t]s[t-1]                           (4)

其中 W[s->q] 是从 sq 的权重矩阵,t 表示时间步,b[1->q] 是指向 q 的偏置,σ() 是 Sigmoid 函数,x[t] 是输入,s[t] 是模块的输出(等式 4)。请注意,与 LSTM 不同,GRU 没有细胞。

GRU 使用 recurrent-language-model.lua 脚本在 PennTreeBank 数据集上进行了基准测试。它略胜于 FastLSTM,然而由于 LSTM 的参数比 GRU 多, 如果数据集大于 PennTreeBank,性能结果可能会发生变化。不要急于判断哪一种更好(参见参考文献 C 和 D)。

                内存   示例/秒
    FastLSTM      176M        16.5K 
    GRU            92M        15.8K

内存dp.Experiment 保存文件的大小来衡量。示例/秒 以 1 个 epoch 的训练速度来衡量,因此可能受到磁盘 IO 的影响。

GRU-BENCHMARK

RNN 的 dropout(参见参考文献 E 和 F)也使用 recurrent-language-model.lua 脚本在 PennTreeBank 数据集上进行了基准测试。详细信息可在脚本中找到。在基准测试中,GRULookupTable 之后使用 dropout,而 BGRU,即贝叶斯 GRU,则在内部连接上使用 dropout(如参考文献 F 所述),而不是在 LookupTable 之后使用。

正如 Yarin Gal(参考文献 F)所提到的,建议首次尝试时可以使用 p = 0.25

GRU-BENCHMARK

SAdd

为了实现 GRU,添加了一个简单的模块,该模块无法仅使用 nn 模块构建。

module = nn.SAdd(addend, negate)

对输入数据应用单个标量加法,即 y_i = x_i + b,然后如果 negate 为真,则将所有分量取反。这用于实现 GRUs[t] = (1-z[t])h[t] + z[t]s[t-1](见上述等式 4)。

nn.SAdd(-1, true)

在这里,如果输入数据是 z[t],那么输出就会变成 -(z[t]-1)=1-z[t]。请注意,nn.Mul() 是一个可学习参数的标量乘法。

MuFuRu

参考文献:

这是一个多功能循环单元模块的实现。

nn.MuFuRu(inputSize, outputSize [,ops [,rho]]) 构造函数接受 2 个必填参数,以及可选参数:

  • inputSize:指定输入维度的数字;
  • outputSize:指定输出维度的数字;
  • ops:字符串表,表示应使用哪些组合操作。该表可以是 {'keep', 'replace', 'mul', 'diff', 'forget', 'sqrt_diff', 'max', 'min'} 的任意子集。默认情况下,所有组合操作都启用。
  • rho:反向传播时回溯的最大步数。限制了保存在内存中的先前步骤数量。默认值为 9999;

多功能循环单元通过允许学习任意组合算子的权重,对 GRU 进行了泛化。与 GRU 类似,重置门根据当前输入和前一隐藏状态计算,并用于计算新的特征向量:

r[t] = σ(W[x->r]x[t] + W[s->r]s[t−1] + b[1->r])            (1)
v[t] = tanh(W[x->v]x[t] + W[sr->v](s[t−1]r[t]) + b[1->v])  (2)

其中 W[a->b] 表示从激活 ab 的权重矩阵,t 表示时间步,b[1->a] 是激活 a 的偏置,而 s[t-1]r[t] 是两个向量的逐元素相乘。

与 GRU 不同,MuFuRU 不是计算单个更新门(如 GRU 中的 z[t]),而是对任意数量的组合算子进行加权。

组合算子是指任何可微分的算子,它接受两个相同大小的向量——前一隐藏状态和新特征向量——并返回代表新隐藏状态的新向量。GRU 隐式定义了两种这样的操作,即 keepreplace,分别定义为 keep(s[t-1], v[t]) = s[t-1]replace(s[t-1], v[t]) = v[t]

参考文献 A 提出了 6 种额外的算子,它们都是逐元素操作:

  • mul(x,y) = x * y
  • diff(x,y) = x - y
  • forget(x,y) = 0
  • sqrt_diff(x,y) = 0.25 * sqrt(|x - y|)
  • max(x,y)
  • min(x,y)

每个操作的权重通过当前输入和前一隐藏状态的 softmax 计算得出,类似于 GRU 中的更新门。然后,生成的隐藏状态是每个操作输出的逐元素加权和。


p^[t][j] = W[x->pj]x[t] + W[s->pj]s[t−1] + b[1->pj])         (3)
(p[t][1], ... p[t][J])  = softmax (p^[t][1], ..., p^[t][J])  (4)
s[t] = sum(p[t][j] * op[j](s[t-1], v[t]))                    (5)

其中 p[t][j] 是时间步 t 时操作 j 的权重,而等式 5 中的 sum 是对所有算子 J 的求和。

递归器

该模块用于装饰一个 module,以便在 AbstractSequencer 实例中使用。 它通过使被装饰的模块符合 AbstractRecurrent 接口来实现这一点, 而该类本身也继承自 LSTMRecurrent 类。

rec = nn.Recursor(module[, rho])

对于每次连续调用 updateOutput(即 forward),这个装饰器都会创建被装饰模块的一个 stepClone()。 因此,在每个时间步长上,它都会克隆该模块。克隆的模块和原始模块共享参数以及相对于这些参数的梯度。 然而,对于已经符合 AbstractRecurrent 接口的模块,克隆的模块和原始模块是同一个(即不进行克隆)。

示例:

假设我想堆叠两个 LSTM。我可以使用两个序列器:

lstm = nn.Sequential()
   :add(nn.Sequencer(nn.LSTM(100,100)))
   :add(nn.Sequencer(nn.LSTM(100,100)))

使用 Recursor,我可以用一个 Sequencer 实现相同的模型:

lstm = nn.Sequencer(
   nn.Recursor(
      nn.Sequential()
         :add(nn.LSTM(100,100))
         :add(nn.LSTM(100,100))
      )
   )

实际上,Sequencer 会自动包装任何非 AbstractRecurrent 模块, 因此我可以进一步简化为:

lstm = nn.Sequencer(
   nn.Sequential()
      :add(nn.LSTM(100,100))
      :add(nn.LSTM(100,100))
   )

我还可以在两个 LSTM 之间添加一个 Linear 层。在这种情况下, Linear 层会在每个时间步长上被克隆(并共享其参数), 而 LSTM 层则会在内部自行处理克隆操作:

lstm = nn.Sequencer(
   nn.Sequential()
      :add(nn.LSTM(100,100))
      :add(nn.Linear(100,100))
      :add(nn.LSTM(100,100))
   )

RecursorRecurrentLSTM 这样的 AbstractRecurrent 实例 应该在内部管理时间步长。非 AbstractRecurrent 实例可以通过 Recursor 包装, 以获得相同的行为。

对像 Recursor 这样的 AbstractRecurrent 实例每次调用 forward 时, 都会将 self.step 属性增加 1,并为每个后续时间步长使用共享参数的克隆版本 (最多可达到 rho 个时间步长,默认值为 9999999)。这样, backward 可以按照与 forward 调用相反的顺序被调用, 从而执行时间反向传播(BPTT)。这正是 AbstractSequencer 实例 在内部所做的事情。backward 调用实际上分为对 updateGradInputaccGradParameters 的调用,分别从 self.step 开始,依次递减 self.updateGradInputStepself.accGradParametersStep。连续的 backward 调用会递减这些计数器, 并利用它们通过相应的内部逐层共享参数的克隆版本进行反向传播。

无论如何,在大多数情况下,你不需要直接操作 Recursor 对象, 因为 AbstractSequencer 实例会在其构造函数中自动使用 Recursor 装饰非 AbstractRecurrent 实例。

有关其具体使用的示例,请参阅 simple-recurrent-network.lua 训练脚本中的示例。

循环单元

一个极其通用的容器,可用于实现几乎任何类型的循环结构。

rnn = nn.Recurrence(recurrentModule, outputSize, nInputDim, [rho])

Recurrent 不同,该模块不管理单独的 inputModulestartModulemergeModule 等模块。 相反,它只管理一个 recurrentModule,该模块应根据输入表: {input(t), output(t-1)},输出张量或表:output(t)。 通过结合使用 Recursor(例如通过 Sequencer)和 Recurrence, 可以实现几乎任何类型的循环神经网络,包括 LSTM 和 RNN。

在第一步中,Recurrence 会将零张量(或其表)传递给循环层(如 LSTM,不同于 Recurrent)。 因此,它需要知道 outputSize,它可以是数字或 torch.LongStorage, 也可以是其表。批次维度应排除在 outputSize 之外。相反, 批次维度的大小(即样本数量)将通过 nInputDim 参数从输入中推断出来。 例如,假设我们的输入是一个大小为 4 x 3 的张量,其中 4 是样本数量, 那么 nInputDim 应该为 1。再举一个例子,如果我们的输入是一系列表 [...] 组成的张量,其中第一个张量(深度优先)与前一个例子相同, 那么我们的 nInputDim 也是 1

作为一个示例,让我们使用 SequencerRecurrence 来构建一个用于语言建模的简单 RNN:

rho = 5
hiddenSize = 10
outputSize = 5 -- 类别数
nIndex = 10000

-- 循环模块
rm = nn.Sequential()
   :add(nn.ParallelTable()
      :add(nn.LookupTable(nIndex, hiddenSize))
      :add(nn.Linear(hiddenSize, hiddenSize)))
   :add(nn.CAddTable())
   :add(nn.Sigmoid())

rnn = nn.Sequencer(
   nn.Sequential()
      :add(nn.Recurrence(rm, hiddenSize, 1))
      :add(nn.Linear(hiddenSize, outputSize))
      :add(nn.LogSoftMax())
)

注意:我们完全可以用新的 RecursorRecurrence 模块重新实现 LSTM 模块, 但这将破坏现有已保存在磁盘上的模型的向后兼容性。

范数稳定器

参考文献 A:通过稳定激活来正则化 RNN

该模块实现了 范数稳定化 准则:

ns = nn.NormStabilizer([beta])

该模块通过最小化连续步骤之间 L2 范数的差异来正则化 RNN 的隐藏状态。 成本函数定义如下:

loss = beta * 1/T sum_t( ||h[t]|| - ||h[t-1]|| )^2

其中 T 是时间步长的数量。请注意,我们不会将梯度除以 T, 这样选择的 beta 就可以在不同序列长度下进行缩放,而无需更改。

唯一参数 beta 在参考文献 A 中定义。由于我们不将梯度除以 时间步长的数量,因此默认值 beta=1 应适用于大多数情况。

该模块应添加到 RNN(或 LSTM 或 GRU)之间,以更好地正则化隐藏状态。 例如:

local stepmodule = nn.Sequential()
   :add(nn.FastLSTM(10,10))
   :add(nn.NormStabilizer())
   :add(nn.FastLSTM(10,10))
   :add(nn.NormStabilizer())
local rnn = nn.Sequencer(stepmodule)

要将其与 SeqLSTM 一起使用,你可以这样做:

local rnn = nn.Sequential()
   :add(nn.SeqLSTM(10,10))
   :add(nn.Sequencer(nn.NormStabilizer()))
   :add(nn.SeqLSTM(10,10))
   :add(nn.Sequencer(nn.NormStabilizer()))

抽象序列器

这个抽象类实现了一个轻量级接口,供诸如 SequencerRepeaterRecurrentAttentionBiSequencer 等子类共享。

序列器

nn.Sequencer(module) 构造函数接受一个参数 module,即要从左到右依次应用于输入序列中每个元素的模块。

seq = nn.Sequencer(module)

该模块是一种装饰器,用于抽象出 AbstractRecurrent 模块的复杂性。虽然 AbstractRecurrent 实例要求按时间步逐个输入数据,并且每次都需要调用 forward(以及 backward),但 Sequencer 会将一个 input 序列(表)传递为一个 output 序列(长度相同的表)。它还会负责在 AbstractRecurrent 实例上调用 forget 方法。

输入/输出格式

Sequencer 要求输入和输出的形状为 seqlen x batchsize x featsize

  • seqlen 是将被送入 Sequencer 的时间步数。
  • batchsize 是批次中的样本数量。每个样本都是独立的序列。
  • featsize 是除批次维度之外的其余维度大小。例如,对于语言模型来说可能是 1,而对于卷积模型来说可能是 c x h x w 等。

Hello Fuzzy

以上是一个字符级别的语言模型的示例输入序列。它的 seqlen 为 5,意味着它包含 5 个时间步的序列。开头的 { 和结尾的 } 表明这些时间步是 Lua 表的元素,不过它也接受形状为 seqlen x batchsize x featsize 的完整张量。batchsize 为 2,因为有两个独立的序列:{ H, E, L, L, O }{ F, U, Z, Z, Y, }featsize 为 1,因为每个字符只有一个特征维度,且每个字符的大小为 1。因此,在这种情况下,输入是一个包含 seqlen 个时间步的表,其中每个时间步由一个 batchsize x featsize 的张量表示。

Sequence

以上是另一个序列(输入或输出)的示例。它有 4 个时间步 (seqlen)。batchsize 再次为 2,这意味着有两个序列。featsize 为 3,因为每个序列的每个时间步都有 3 个变量。因此,每个时间步(表的元素)再次由一个大小为 batchsize x featsize 的张量表示。需要注意的是,在这两个示例中,featsize 编码的是一个维度,但它也可以编码更多维度。

示例

例如,rnn:一个 nn.AbstractRecurrent 的实例,可以一次向前传递一个 input 序列:

input = {torch.randn(3,4), torch.randn(3,4), torch.randn(3,4)}
rnn:forward(input[1])
rnn:forward(input[2])
rnn:forward(input[3])

同样地,我们也可以使用 Sequencer 来一次性向前传递整个 input 序列:

seq = nn.Sequencer(rnn)
seq:forward(input)

我们也可以传递张量而不是表:

-- seqlen x batchsize x featsize
input = torch.randn(3,3,4)
seq:forward(input)

详情

Sequencer 也可以接受非递归模块(即非 AbstractRecurrent 实例),并将其应用于每个输入,以生成一个长度相同的输出表。这对于处理可变长度序列(表)特别有用。

在内部,Sequencer 假设被装饰的 module 是一个 AbstractRecurrent 实例。如果情况并非如此,module 将自动被 Recursor 模块装饰,使其符合 AbstractRecurrent 接口。

注意:这是由于最近的一次更新(2015年10月27日)所致,在此之前,AbstractRecurrent 和非 AbstractRecurrent 实例需要分别由各自的 Sequencer 进行装饰。而这次引入了 Recursor 装饰器的更新,使得单个 Sequencer 可以包装任何类型的模块,无论是 AbstractRecurrent、非 AbstractRecurrent,还是两者的复合结构。尽管如此,现有代码不应受到此次更改的影响。

有关其使用的简明示例,请参阅训练脚本 simple-sequencer-network.lua

remember([mode])

mode='neither'(类的默认行为)时,Sequencer 在每次调用 forward 之前还会额外调用 forget。当 mode='both'(调用此函数时的默认值)时,Sequencer 将永远不会调用 forget。在这种情况下,用户需要在独立序列之间手动调用 forget。此行为仅适用于被装饰的 AbstractRecurrent modules。参数 mode 的有效值如下:

  • 'eval' 仅影响评估阶段(推荐用于 RNN)
  • 'train' 仅影响训练阶段
  • 'neither' 既不影响训练也不影响评估(类的默认行为)
  • 'both' 同时影响训练和评估(推荐用于 LSTM)

forget()

调用被装饰的 AbstractRecurrent 模块的 forget 方法。

SeqLSTM

该模块是 nn.Sequencer(nn.FastLSTM(inputsize, outputsize)) 的更快版本:

seqlstm = nn.SeqLSTM(inputsize, outputsize)

每个时间步的计算方式如下(与 FastLSTM 相同):

i[t] = σ(W[x->i]x[t] + W[h->i]h[t−1] + b[1->i])                      (1)
f[t] = σ(W[x->f]x[t] + W[h->f]h[t−1] + b[1->f])                      (2)
z[t] = tanh(W[x->c]x[t] + W[h->c]h[t−1] + b[1->c])                   (3)
c[t] = f[t]c[t−1] + i[t]z[t]                                         (4)
o[t] = σ(W[x->o]x[t] + W[h->o]h[t−1] + b[1->o])                      (5)
h[t] = o[t]tanh(c[t])                                                (6)

一个显著的区别是,该模块期望 inputgradOutput 是张量而不是表。默认形状为:inputseqlen x batchsize x inputsizeoutputseqlen x batchsize x outputsize

input = torch.randn(seqlen, batchsize, inputsize)
gradOutput = torch.randn(seqlen, batchsize, outputsize)

output = seqlstm:forward(input)
gradInput = seqlstm:backward(input, gradOutput)

请注意,如果您希望交换前两个维度(即 batchsize x seqlen 而不是默认的 seqlen x batchsize),可以在初始化后设置 seqlstm.batchfirst = true

对于可变长度序列,设置 seqlstm.maskzero = true。这等价于在由 Sequencer 包装的 FastLSTM 上调用 maskZero(1)

fastlstm = nn.FastLSTM(inputsize, outputsize)
fastlstm:maskZero(1)
seqfastlstm = nn.Sequencer(fastlstm)

maskzero = true 时,输入序列应以零张量分隔各个时间步。

seqlstm:toFastLSTM() 方法会生成一个使用 seqlstm 实例参数初始化的 FastLSTM 实例。需要注意的是,生成的参数不会共享(也无法共享)。

FastLSTM 一样,SeqLSTM 不使用单元和门之间的窥孔连接(详情请参阅 FastLSTM)。

Sequencer 一样,SeqLSTM 提供一个 remember 方法。

请注意,SeqLSTM 不能替换代码中被 AbstractSequencerRecursor 装饰的 FastLSTM,因为那样就相当于 Sequencer(Sequencer(FastLSTM))。特此提醒。

SeqLSTMP

参考文献:

lstmp = nn.SeqLSTMP(inputsize, hiddensize, outputsize)

SeqLSTMPSeqLSTM 的子类。其不同之处在于,在计算隐藏状态 h[t](公式 6)之后,它会通过一个简单的线性变换将其投影到 r[t] 上(公式 7)。门的计算也使用了前一次的这种投影 r[t-1](公式 1、2、3、5)。这与 SeqLSTM 使用 h[t-1] 而不是 r[t-1] 的情况不同。

SeqLSTM 中概述的时间步计算被以下内容取代:

i[t] = σ(W[x->i]x[t] + W[r->i]r[t−1] + b[1->i])                      (1)
f[t] = σ(W[x->f]x[t] + W[r->f]r[t−1] + b[1->f])                      (2)
z[t] = tanh(W[x->c]x[t] + W[h->c]r[t−1] + b[1->c])                   (3)
c[t] = f[t]c[t−1] + i[t]z[t]                                         (4)
o[t] = σ(W[x->o]x[t] + W[r->o]r[t−1] + b[1->o])                      (5)
h[t] = o[t]tanh(c[t])                                                (6)
r[t] = W[h->r]h[t]                                                   (7)

该算法在参考文献 A 中有详细介绍,并在参考文献 B 中使用 Google 十亿词数据集进行了基准测试,取得了最先进的结果。SeqLSTMP 可以与 hiddensize >> outputsize 一起使用,这样记忆单元 c[t] 以及门 i[t]f[t]o[t] 的有效尺寸可以远大于实际输入 x[t] 和输出 r[t]。对于固定的 inputsizeoutputsizeSeqLSTMP 将能够记住比 SeqLSTM 更多的信息。

SeqGRU

该模块是 nn.Sequencer(nn.GRU(inputsize, outputsize)) 的更快版本:

seqGRU = nn.SeqGRU(inputsize, outputsize)

SeqGRU 的使用方式与 GRU 的区别类似于 SeqLSTM 与 LSTM 的区别。因此,请参阅 SeqLSTM 以获取更多详细信息。

SeqBRNN

brnn = nn.SeqBRNN(inputSize, outputSize, [batchFirst], [merge])

一种使用 SeqLSTM 的双向 RNN。内部包含一个正向和一个反向的 SeqLSTM 模块。期望输入形状为 seqlen x batchsize x inputsize。通过将 [batchFirst] 设置为 true,输入形状可以变为 batchsize x seqLen x inputsize。合并模块默认为 CAddTable(),对来自每个输出层的输出进行求和。

示例:

input = torch.rand(1, 1, 5)
brnn = nn.SeqBRNN(5, 5)
print(brnn:forward(input))

打印出一个 1x1x5 张量的输出。

BiSequencer

将封装的正向和反向 RNN 分别按正向和反向顺序应用于输入序列。它用于实现双向 RNN 和 LSTM。

brnn = nn.BiSequencer(fwd, [bwd, merge])

该模块的输入是一个张量序列(表),输出也是一个相同长度的张量序列(表)。它按照正向顺序将一个正向 RNN(一个 AbstractRecurrent 实例)应用于序列中的每个元素,并按照反向顺序(从最后一个元素到第一个元素)应用反向 RNN。反向 RNN 默认为:

bwd = fwd:clone()
bwd:reset()

对于原始序列中的每个步骤,两个 RNN 的输出会通过合并模块合并在一起(默认为 nn.JoinTable(1,1))。如果 merge 是一个数字,则指定 JoinTable 构造函数的 nInputDim 参数。因此,合并模块会被初始化为:

merge = nn.JoinTable(1,merge)

在内部,BiSequencer 是通过装饰一个由三个 Sequencer 组成的结构来实现的,分别用于正向、反向和合并模块。

Sequencer 类似,批次中的序列必须具有相同的大小。但每个批次的序列长度可以不同。

注意:每次调用 updateParameters() 后,请务必调用 brnn:forget()。或者也可以只调用 brnn.bwdSeq:forget(),使只有反向 RNN 忘记状态。这是最低要求,因为让反向 RNN 记住未来的序列是没有意义的。

BiSequencerLM

将封装的 fwdbwd RNN 按照正向和反向顺序应用于输入序列。 它用于实现语言模型(LM)中的双向 RNN 和 LSTM。

brnn = nn.BiSequencerLM(fwd, [bwd, merge])

该模块的输入是一个张量序列(表),输出也是一个长度相同的张量序列(表)。 它会按照正向顺序将一个 fwd RNN(一个 AbstractRecurrent 实例)应用于序列中的前 N-1 个元素。 然后,它会以反向顺序将 bwd RNN 应用于最后 N-1 个元素(从倒数第二个元素到第一个元素)。 这是该模块与 BiSequencer 的主要区别。后者不能用于语言建模,因为 bwd RNN 会被训练去预测它刚刚接收到的输入。

BiDirectionalLM

bwd RNN 的默认设置是:

bwd = fwd:clone()
bwd:reset()

fwd RNN 将为最后 N-1 个时间步生成表示,而 bwd RNN 则会为前 N-1 个时间步生成表示。 每个 RNN 缺失的时间步输出(fwd 的第一个时间步和 bwd 的最后一个时间步)将用与相应 RNN 输出大小相同的零张量填充。 这样它们就可以被合并。如果使用 nn.JoinTable(默认值),则第一个和最后一个输出元素将分别用零来填充缺失的 fwdbwd RNN 输出。

对于原始序列中的每一个时间步,两个 RNN 的输出都会使用 merge 模块进行合并(默认为 nn.JoinTable(1,1))。如果 merge 是一个数字,则它指定了 JoinTable 构造函数的 nInputDim 参数。因此,merge 模块会被初始化为:

merge = nn.JoinTable(1,merge)

Sequencer 类似,批次中的序列必须具有相同的大小,但每个批次的序列长度可以不同。

需要注意的是,使用此模块实现的语言模型并不是传统的语言模型,因为它们不会计算给定先前词的情况下某个词的概率。相反,它们计算的是在上下文(即周围词)给定的情况下某个词的概率。虽然出于数学原因,你可能无法用它来计算一串词(如一句话)的概率,但你仍然可以计算这种序列的伪似然性(有关讨论,请参阅 this)。

Repeater

该模块是一个类似于 Sequencer装饰器。 它的不同之处在于,序列长度是预先固定的,输入会反复通过被包装的 module,从而产生一个长度为 nStep 的输出表:

r = nn.Repeater(module, nStep)

参数 module 应该是一个 AbstractRecurrent 实例。 这对于实现像 RCNNs 这样的模型非常有用,这些模型会反复接收相同的输入。

RecurrentAttention

参考文献:

该模块可用于实现参考文献 A 中提出的循环注意力模型(RAM):

ram = nn.RecurrentAttention(rnn, action, nStep, hiddenSize)

rnn 是一个 AbstractRecurrent 实例。 它的输入是 {x, z},其中 x 是 RAM 的输入,z 是从 action 模块中采样得到的动作。 rnn 的输出大小必须等于 hiddenSize

action 是一个 Module,它使用一个 REINFORCE 模块(参考文献 B),例如 ReinforceNormalReinforceCategoricalReinforceBernoulli,根据 rnn 上一个时间步的输出来采样动作。 在第一个时间步,action 模块会接收到一个大小为 input:size(1) x hiddenSize 的零张量。 重要的是要理解,采样的动作不会接收来自训练准则的反向传播梯度。 相反,奖励会从一个奖励准则(如 VRClassReward)广播到 action 的 REINFORCE 模块,该模块会根据 output 样本和 reward 计算并反向传播梯度。 因此,action 模块的输出仅在循环注意力模块内部使用。

nStep 是要采样的动作数量,即 output 表中的元素数量。

hiddenSizernn 的输出大小。这个变量对于生成用于第一个时间步采样动作的零张量是必要的(见上文)。

参考文献 A 的完整实现可以在 这里 找到。

MaskZero

该模块会将被装饰模块的 output 行清零,前提是对应的 input 行是零张量。

mz = nn.MaskZero(module, nInputDim)

当被装饰模块的 input 对应行是零张量时,其 output 张量(或张量表)的每一行(样本)都会被清零。

nInputDim 参数必须指定 input 中第一个张量的非批处理维度的数量。如果是 input 表,则第一个张量是指在深度优先搜索中遇到的第一个张量。 这个装饰器使得在同一批次中对不同长度的序列用零向量进行填充成为可能。

注意:MaskZero 并不能保证当 input 为零时,被装饰模块内部的 outputgradInput 张量也会被清零。MaskZero 只会影响它所封装模块的直接 gradInputoutput。 然而,对于大多数模块而言,该时间步的梯度更新将会是零,因为反向传播零梯度通常会导致整个路径上的梯度都为零。在这方面,不建议将 AbsractRecurrent 实例封装在 MaskZero 内,因为它们会在不同时间步之间传递梯度。相反,应该调用 AbstractRecurrent.maskZero 方法来封装内部的 recurrentModule

TrimZero

警告:仅当您的输入包含大量零时才使用此模块。在几乎所有情况下,MaskZero 都会更快,尤其是在使用 CUDA 时。

参考 A:TrimZero:用于高效自然语言处理的 Torch 循环神经网络模块

其用法与 MaskZero 相同。

mz = nn.TrimZero(module, nInputDim)

MaskZero 的唯一区别在于,当输入中存在不同长度的序列时,它可以通过调整批次大小来降低计算成本。请注意,当序列长度一致时,MaskZero 会更快,因为 TrimZero 存在一定的操作开销。

简而言之,结果与 MaskZero 相同,但只有在句子长度变化较大时,TrimZero 才会比 MaskZero 更快。

在实践中,例如在语言模型中,TrimZero 的速度预计比 MaskZero 快约 30%。您可以通过 test/test_trimzero.lua 进行测试。

LookupTableMaskZero

该模块扩展了 nn.LookupTable,以支持零索引。零索引会被传递为零张量。

lt = nn.LookupTableMaskZero(nIndex, nOutput)

input 中对应行的索引为零时,输出张量的相应行将被置零。

此查找表使得在同一批次中对不同长度的序列用零向量进行填充成为可能。

MaskZeroCriterion

该准则会对被装饰准则中的 errgradInput 行进行置零处理,这些行对应于 input 中为零张量的行。

mzc = nn.MaskZeroCriterion(criterion, nInputDim)

被装饰 criteriongradInput 张量(或其表)中的每一行(样本)都会在 input 中对应行为零张量时被置零。同时,err 也会忽略这些零行。

nInputDim 参数必须指定 input 中第一个张量的非批次维度数量。如果 input 是一个表,则第一个张量是指在深度优先搜索中遇到的第一个张量。

此装饰器使得在同一批次中对不同长度的序列用零向量进行填充成为可能。

SeqReverseSequence

reverseSeq = nn.SeqReverseSequence(dim)

沿指定维度反转输入张量。反转维度不能超过三。

示例:

input = torch.Tensor({{1,2,3,4,5}, {6,7,8,9,10}})
reverseSeq = nn.SeqReverseSequence(1)
print(reverseSeq:forward(input))

输出为 torch.Tensor({{6,7,8,9,10},{1,2,3,4,5}})

SequencerCriterion

该准则是一种 装饰器模式

c = nn.SequencerCriterion(criterion, [sizeAverage])

inputtarget 预期都是一系列数据,可以是表或张量。对于序列中的每一步,inputtarget 中对应的元素都会被应用于 criterionforward 的输出是序列中所有单个损失的总和。这在与 Sequencer 结合使用时非常有用。

如果 sizeAveragetrue(默认为 false),则 output 损失和 gradInput 会在每个时间步上取平均值。

RepeaterCriterion

该准则是一种 装饰器模式

c = nn.RepeaterCriterion(criterion)

input 预期是一个序列(表或张量)。单个 target 会使用相同的 criterion 反复应用于 input 序列中的每个元素。 forward 的输出是序列中所有单个损失的总和。这对于实现如 RCNNs 等模型非常有用,这些模型会反复接收相同的目标。

常见问题

相似工具推荐

openclaw

OpenClaw 是一款专为个人打造的本地化 AI 助手,旨在让你在自己的设备上拥有完全可控的智能伙伴。它打破了传统 AI 助手局限于特定网页或应用的束缚,能够直接接入你日常使用的各类通讯渠道,包括微信、WhatsApp、Telegram、Discord、iMessage 等数十种平台。无论你在哪个聊天软件中发送消息,OpenClaw 都能即时响应,甚至支持在 macOS、iOS 和 Android 设备上进行语音交互,并提供实时的画布渲染功能供你操控。 这款工具主要解决了用户对数据隐私、响应速度以及“始终在线”体验的需求。通过将 AI 部署在本地,用户无需依赖云端服务即可享受快速、私密的智能辅助,真正实现了“你的数据,你做主”。其独特的技术亮点在于强大的网关架构,将控制平面与核心助手分离,确保跨平台通信的流畅性与扩展性。 OpenClaw 非常适合希望构建个性化工作流的技术爱好者、开发者,以及注重隐私保护且不愿被单一生态绑定的普通用户。只要具备基础的终端操作能力(支持 macOS、Linux 及 Windows WSL2),即可通过简单的命令行引导完成部署。如果你渴望拥有一个懂你

349.3k|★★★☆☆|1周前
Agent开发框架图像

stable-diffusion-webui

stable-diffusion-webui 是一个基于 Gradio 构建的网页版操作界面,旨在让用户能够轻松地在本地运行和使用强大的 Stable Diffusion 图像生成模型。它解决了原始模型依赖命令行、操作门槛高且功能分散的痛点,将复杂的 AI 绘图流程整合进一个直观易用的图形化平台。 无论是希望快速上手的普通创作者、需要精细控制画面细节的设计师,还是想要深入探索模型潜力的开发者与研究人员,都能从中获益。其核心亮点在于极高的功能丰富度:不仅支持文生图、图生图、局部重绘(Inpainting)和外绘(Outpainting)等基础模式,还独创了注意力机制调整、提示词矩阵、负向提示词以及“高清修复”等高级功能。此外,它内置了 GFPGAN 和 CodeFormer 等人脸修复工具,支持多种神经网络放大算法,并允许用户通过插件系统无限扩展能力。即使是显存有限的设备,stable-diffusion-webui 也提供了相应的优化选项,让高质量的 AI 艺术创作变得触手可及。

162.1k|★★★☆☆|1周前
开发框架图像Agent

everything-claude-code

everything-claude-code 是一套专为 AI 编程助手(如 Claude Code、Codex、Cursor 等)打造的高性能优化系统。它不仅仅是一组配置文件,而是一个经过长期实战打磨的完整框架,旨在解决 AI 代理在实际开发中面临的效率低下、记忆丢失、安全隐患及缺乏持续学习能力等核心痛点。 通过引入技能模块化、直觉增强、记忆持久化机制以及内置的安全扫描功能,everything-claude-code 能显著提升 AI 在复杂任务中的表现,帮助开发者构建更稳定、更智能的生产级 AI 代理。其独特的“研究优先”开发理念和针对 Token 消耗的优化策略,使得模型响应更快、成本更低,同时有效防御潜在的攻击向量。 这套工具特别适合软件开发者、AI 研究人员以及希望深度定制 AI 工作流的技术团队使用。无论您是在构建大型代码库,还是需要 AI 协助进行安全审计与自动化测试,everything-claude-code 都能提供强大的底层支持。作为一个曾荣获 Anthropic 黑客大奖的开源项目,它融合了多语言支持与丰富的实战钩子(hooks),让 AI 真正成长为懂上

160k|★★☆☆☆|今天
开发框架Agent语言模型

ComfyUI

ComfyUI 是一款功能强大且高度模块化的视觉 AI 引擎,专为设计和执行复杂的 Stable Diffusion 图像生成流程而打造。它摒弃了传统的代码编写模式,采用直观的节点式流程图界面,让用户通过连接不同的功能模块即可构建个性化的生成管线。 这一设计巧妙解决了高级 AI 绘图工作流配置复杂、灵活性不足的痛点。用户无需具备编程背景,也能自由组合模型、调整参数并实时预览效果,轻松实现从基础文生图到多步骤高清修复等各类复杂任务。ComfyUI 拥有极佳的兼容性,不仅支持 Windows、macOS 和 Linux 全平台,还广泛适配 NVIDIA、AMD、Intel 及苹果 Silicon 等多种硬件架构,并率先支持 SDXL、Flux、SD3 等前沿模型。 无论是希望深入探索算法潜力的研究人员和开发者,还是追求极致创作自由度的设计师与资深 AI 绘画爱好者,ComfyUI 都能提供强大的支持。其独特的模块化架构允许社区不断扩展新功能,使其成为当前最灵活、生态最丰富的开源扩散模型工具之一,帮助用户将创意高效转化为现实。

109.2k|★★☆☆☆|今天
开发框架图像Agent

gemini-cli

gemini-cli 是一款由谷歌推出的开源 AI 命令行工具,它将强大的 Gemini 大模型能力直接集成到用户的终端环境中。对于习惯在命令行工作的开发者而言,它提供了一条从输入提示词到获取模型响应的最短路径,无需切换窗口即可享受智能辅助。 这款工具主要解决了开发过程中频繁上下文切换的痛点,让用户能在熟悉的终端界面内直接完成代码理解、生成、调试以及自动化运维任务。无论是查询大型代码库、根据草图生成应用,还是执行复杂的 Git 操作,gemini-cli 都能通过自然语言指令高效处理。 它特别适合广大软件工程师、DevOps 人员及技术研究人员使用。其核心亮点包括支持高达 100 万 token 的超长上下文窗口,具备出色的逻辑推理能力;内置 Google 搜索、文件操作及 Shell 命令执行等实用工具;更独特的是,它支持 MCP(模型上下文协议),允许用户灵活扩展自定义集成,连接如图像生成等外部能力。此外,个人谷歌账号即可享受免费的额度支持,且项目基于 Apache 2.0 协议完全开源,是提升终端工作效率的理想助手。

100.8k|★★☆☆☆|1周前
插件Agent图像

markitdown

MarkItDown 是一款由微软 AutoGen 团队打造的轻量级 Python 工具,专为将各类文件高效转换为 Markdown 格式而设计。它支持 PDF、Word、Excel、PPT、图片(含 OCR)、音频(含语音转录)、HTML 乃至 YouTube 链接等多种格式的解析,能够精准提取文档中的标题、列表、表格和链接等关键结构信息。 在人工智能应用日益普及的今天,大语言模型(LLM)虽擅长处理文本,却难以直接读取复杂的二进制办公文档。MarkItDown 恰好解决了这一痛点,它将非结构化或半结构化的文件转化为模型“原生理解”且 Token 效率极高的 Markdown 格式,成为连接本地文件与 AI 分析 pipeline 的理想桥梁。此外,它还提供了 MCP(模型上下文协议)服务器,可无缝集成到 Claude Desktop 等 LLM 应用中。 这款工具特别适合开发者、数据科学家及 AI 研究人员使用,尤其是那些需要构建文档检索增强生成(RAG)系统、进行批量文本分析或希望让 AI 助手直接“阅读”本地文件的用户。虽然生成的内容也具备一定可读性,但其核心优势在于为机器

93.4k|★★☆☆☆|1周前
插件开发框架