PyTorch使用合成梯度的解耦神经接口(dni-pytorch)

Song • 783 次浏览 • 0 个回复 • 2018年02月06日

用于PyTorch的解耦神经接口

这个小型库主要是为了是在PyTorch中实现用合成梯度解耦神经接口 。使用起来非常简单,因为它旨在使研究人员能够以最少量的代码将DNI集成到现有模型中。

运行如下代码安装:

$ python setup.py install

下面提供了一些典型情况下库的描述以及如何使用它。欲了解更多信息,请阅读代码。

专门名词

这个库使用了文中介绍的消息传递抽象。API中使用的一些专门名词(尽可能与论文中使用的专门名词相匹配):

  • Interface - 一个解耦的神经接口,将网络的两部分(让我们称之为A部分和B部分)分离,并让它们通过message传递进行通信 。它可能是ForwardBackward或者Bidirectional
  • BackwardInterface- Interface本文重点介绍的一种类型。可以通过基于最后一层的激活预测解耦网络的A部分的梯度来防止更新锁定。
  • ForwardInterface- 根据Interface两部分已知的一些信息,通过预测网络B部分的输入,可以用来防止前向锁定。在本文中,它是整个网络的输入。
  • BidirectionalInterface-的组合ForwardInterfaceBackwardInterface,可被用来实现一个完整的解锁。
  • message- 通过Interface激活最后一层ForwardInterface或梯度激活的信息BackwardInterface。请注意,没有原始信息通过。A message被消费的一端Interface和用来更新一个Synthesizer。那么Synthesizer可以使用message在另一端产生合成Interface
  • trigger- 基于哪个信息message被合成。它需要被网络的两个部分访问。因为BackwardInterface它是激活要合成梯度的层。因为 ForwardInterface它可以是任何东西 - 在本文中它是整个网络的输入。

  • context- 正常情况下通常不会向网络显示的附加信息,可以调整Interface以提供更好的估计message。本文为此使用标签,并使用上下文cDNI调用DNI
  • send- Interface的一个方法,即messagetrigger作为输入,在此基础上该message应生成和更新Synthesizer,以改进估计。
  • receive- Interface的一个方法,trigger作为输入内容并返回一个messagea生成的Synthesizer

  • Synthesizer- 一个回归模型评估message基于triggercontext

典型的用例

前馈网络的合成梯度

在这种情况下,我们要解耦神经网络的两个部分A和B以实现更新解锁,以便从A部分到B部分具有正常的正向通道,但是部分A使用由DNI生成的合成梯度学习。

图像/前馈更新

遵循本文的惯例,纯黑箭头是更新锁定正向连接,黑色箭头是更新 - 解锁正向连接,绿色箭头是实际错误梯度,蓝色箭头是合成错误梯度。整圆表示合成梯度损失计算和Synthesizer更新。

我们可以用一个BackwardInterface来做到这一点:

class Network(torch.nn.Module):

    def __init__(self):
        # ...

        # 1. create a BackwardInterface, assuming that dimensionality of
        #    the activation for which we want to synthesize gradients is
        #    activation_dim
        self.backward_interface = dni.BackwardInterface(
            dni.BasicSynthesizer(output_dim=activation_dim, n_hidden=1)
        )

        # ...

    def forward(self, x):
        # ...

        # 2. call the BackwardInterface at the point where we want to
        #    decouple the network
        x = self.backward_interface(x)

        # ...

        return x

在正向通过期间,BackwardInterface将使用一个Synthesizer来产生合成梯度wrt激活,反向传播它,并向计算图中添加一个节点,该节点将在反向通过期间截取实际梯度,并使用它来更新Synthesizer估计。

这里Synthesizer使用的是BasicSynthesizer一个具有ReLU激活功能的多层感知器。编写自定义Synthesizer的描述编写自定义合成。

您可以通过contextcontext_dim(上下文向量的维度)传递给BasicSynthesizer构造函数并在dni.synthesizer_context上下文管理器中包装所有的DNI调用来指定:

class Network(torch.nn.Module):

    def __init__(self):
        # ...

        self.backward_interface = dni.BackwardInterface(
            dni.BasicSynthesizer(
                output_dim=activation_dim, n_hidden=1,
                context_dim=context_dim
            )
        )

        # ...

    def forward(self, x, y):
        # ...

        # assuming that context is labels given in variable y
        with dni.synthesizer_context(y):
            x = self.backward_interface(x)

        # ...

        return x

MNIST上的数字分类示例代码示例/mnist-mlp

为前馈网络完全解锁

在这种情况下,我们要解耦神经网络的两部分A和B以实现前向和更新解锁,以便部分B接收合成输入,部分A使用由DNI生成的合成梯度学习。

为前馈网络完全解锁

红色箭头是合成的输入。

我们可以用一个BidirectionalInterface来做到这一点:

class Network(torch.nn.Module):

    def __init__(self):
        # ...

        # 1. create a BidirectionalInterface, assuming that dimensionality of
        #    the activation for which we want to synthesize gradients is
        #    activation_dim and dimensionality of the input of the whole
        #    network is input_dim
        self.bidirectional_interface = dni.BidirectionalInterface(
            # Synthesizer generating synthetic inputs for part B, trigger
            # here is the input of the network
            dni.BasicSynthesizer(
                output_dim=activation_dim, n_hidden=1,
                trigger_dim=input_dim
            ),
            # Synthesizer generating synthetic gradients for part A,
            # trigger here is the last activation of part A (no need to
            # specify dimensionality)
            dni.BasicSynthesizer(
                output_dim=activation_dim, n_hidden=1
            )
        )

        # ...

    def forward(self, input):
        x = input

        # ...

        # 2. call the BidirectionalInterface at the point where we want to
        #    decouple the network, need to pass both the last activation
        #    and the trigger, which in this case is the input of the whole
        #    network
        x = self.backward_interface(x, input)

        # ...

        return x

在正向通过期间,BidirectionalInterface将接收真正的激活,使用它来更新输入Synthesizer,生成合成梯度wrt,使用梯度激活Synthesizer,反向传播它,使用输入生成合成输入,Synthesizer并附加一个计算图形节点,将拦截真正的梯度使用合成输入并使用它来更新梯度Synthesizer

MNIST上的数字分类示例代码示例/mnist-full-unlock

编写自定义合成器

这个库仅包含BasicSynthesizer一个Synthesizer基于具有ReLU激活功能的多层感知器的非常简单的库。对于所有情况来说,这可能是不够的,例如使用CNNMNIST数字进行分类,该论文使用的Synthesizer也是CNN

您可以轻松地编写自定义Synthesizer通过继承torch.nn.Module与方法forwardtrigger,并context作为参数和返回合成message

class CustomSynthesizer(torch.nn.Module):

    def forward(self, trigger, context):
        # synthesize the message
        return message

trigger将是一个torch.autograd.Variablecontext将被传递给dni.synthesizer_context上下文管理器,如果dni.synthesizer_context不被使用返回None

使用CNNMNIST上进行数字分类的示例代码示例/mnist-cnn

递归网络的合成梯度

在这种情况下我们希望使用DNI从无限展开的递归神经网络I近似梯度,并将其馈送到由截断的BPTT展开的RNN的最后一步。

递归网络的合成梯度

我们可以使用BackwardInterfacemake_triggerbackward方法做到这一点:

lass Network(torch.nn.module):

    def __init__(self):
        # ...

        # 1. create a BackwardInterface, assuming that dimensionality of
        #    the RNN hidden state is hidden_dim
        self.backward_interface = dni.BackwardInterface(
            dni.BasicSynthesizer(output_dim=hidden_dim, n_hidden=1)
        )

        # ...

    def forward(self, input, hidden):
        # ...

        # 2. call make_trigger on the first state of the unrolled RNN
        hidden = self.backward_interface.make_trigger(hidden)
        # run the RNN
        (output, hidden) = self.rnn(input, hidden)
        # 3. call backward on the last state of the unrolled RNN
        self.backward_interface.backward(hidden)

        # ...

# in the training loop:
with dni.defer_backward():
    (output, hidden) = model(input, hidden)
    loss = criterion(output, target)
    dni.backward(loss)

BackwardInterface.make_trigger将第一个隐藏状态标记为trigger用来更新梯度估计值。在向后传递过程中,通过的梯度trigger将与基于相同的合成梯度进行比较,trigger并且Synthesizer将被更新。BackwardInterface.backward计算基于最后一个隐藏状态的合成梯度并反向传播它。

因为我们通过计算图中的相同节点传递真实和合成梯度,我们需要使用dni.defer_backwarddni.backwarddni.defer_backward是一个上下文管理器,累积所有传递给dni.backward(包括那些产生的 Interfaces)梯度,并且最终一次反向传播它们。如果我们不这样做,PyTorch会通过相同的计算图表抱怨两次反向传播。

Penn Treebank上的单词级语言建模示例代码/rnn

具有完全解锁的分布式训练

本文介绍了复杂的神经结构的分布式训练为一体的DNI的潜在用途。在这种情况下,我们将网络划分为独立训练的部分AB,也许在不同的机器上通过DNI进行通信。我们可以使用BidirectionalInterfacesendreceive方法做到这一点:

class PartA(torch.nn.Module):

    def forward(self, input):
        x = input

        # ...

        # send the intermediate results computed by part A via DNI
        self.bidirectional_interface.send(x, input)

class PartB(torch.nn.Module):

    def forward(self, input):
        # receive the intermediate results computed by part A via DNI
        x = self.bidirectional_interface.receive(input)

        # ...

        return x

项目源码:koz4k/dni-pytorch


原创文章,转载请注明 :PyTorch使用合成梯度的解耦神经接口(dni-pytorch) - pytorch中文网
原文出处: https://ptorch.com/news/125.html
问题交流群 :168117787
提交评论
要回复文章请先登录注册
用户评论
  • 没有评论
Pytorch是什么?关于Pytorch! pytorch使用Arnold通过DOOM代理玩毁灭战士(获得ViZDoom AI比赛冠军)