地坪漆厂家
免费服务热线

Free service

hotline

010-00000000
地坪漆厂家
热门搜索:
行业资讯
当前位置:首页 > 行业资讯

从原理到实战英伟达教你用PyTorch搭建RNN下_[#第一枪]

发布时间:2021-06-07 12:27:31 阅读: 来源:地坪漆厂家

雷锋网按:本文为《从原理到实战 英伟达教你用PyTorch搭建RNN》的下篇,阅读上篇请点击这里。文章原载于英伟达博客,编译。

代码实操

在开始创建神经网络之前,我需要设置一个 data loader。对于深度学习而言,在数据样例的 batch 上运行模型十分常见,这能通过并行计算加速训练,并在每一步有更加平滑的梯度。现在我们就开始,下文会解释上篇描述的如何对 stack-manipulation 进行 batch。 PyTorch text library 内置的系统,能把相近长度的样例组合起来自动生成 batch,以下 Python 代码便向该系统加载了一些数据。运行这些代码之后,, train_iter、dev_iter、test_iter 中的迭代器,会在 SNLI 训练、验证、测试阶段在 batch 上循环。

from torchtext import data, datasets

TEXT = datasets.snli.ParsedTextField(lower=True)

TRANSITIONS = datasets.snli.ShiftReduceField()

LABELS = data.Field(sequential=False)

train, dev, test = datasets.SNLI.splits(

TEXT, TRANSITIONS, LABELS, wv_type='glove.42B')

TEXT.build_vocab(train, dev, test)

train_iter, dev_iter, test_iter = data.BucketIterator.splits(

(train, dev, test), batch_size=64)

你可以在 train.py 找到其余代码,包括训练循环(loop)的和衡量精度的。现在讲模型。如同上篇所描述,一个 SPINN 编码器包含一个参数化的 Reduce 层,以及可选的 recurrent Tracker,以追踪语境。这通过在神经网络每读取一个词语、或应用 Reduce 的时候,更新隐藏状态来实现。下面的代码其实表示了,创建一个 SPINN 只是意味着创建这两个子模块而已,以及把它们放到容器里面以日后使用。

import torch

from torch import nn

# subclass the Module class from PyTorch’s neural network package

class SPINN(nn.Module):

def __init__(self, config):

super(SPINN, self).__init__()

self.config = config

self.reduce = Reduce(config.d_hidden, config.d_tracker)

if config.d_tracker is not None:

self.tracker = Tracker(config.d_hidden, config.d_tracker)

创建模型时,SPINN.__init__被调用一次。它分配、初始化参数,但不进行任何神经网络运算,也不涉及创建计算图。每组新数据 batch 上运行的代码,在 SPINN 中定义。PyTorch 里,用户定义模型前馈通道的方法名为 “forward”。事实上,它是对上文提到的 stack-manipulation 算法的实现,在普通 Python 里,它运行于 Buffer 和堆栈的 batch 上——对每个样例使用两者之一。 在转换过程包含的“shift” 和 “reduce” op 上迭代,如果它存在,就运行 Tracker,并运行于 batch 中的每个样例以应用 “shift”op,或加入需要 “reduce” op 的样例列表。然后在列表所有的样例上运行 Reduce 层,把结果 push 回相关堆栈。

def forward(self, buffers, transitions):

# The input comes in as a single tensor of word embeddings;

# I need it to be a list of stacks, one for each example in

# the batch, that we can pop from independently. The words in

# each example have already been reversed, so that they can

# be read from left to right by popping from the end of each

# list; they have also been prefixed with a null value.

buffers = [list(torch.split(b.squeeze(1), 1, 0))

for b in torch.split(buffers, 1, 1)]

# we also need two null values at the bottom of each stack,

# so we can copy from the nulls in the input; these nulls

# are all needed so that the tracker can run even if the

# buffer or stack is empty

stacks = [[buf[0], buf[0]] for buf in buffers]

if hasattr(self, 'tracker'):

self.tracker.reset_state()

for trans_batch in transitions:

if hasattr(self, 'tracker'):

# I described the Tracker earlier as taking 4

# arguments (context_t, b, s1, s2), but here I

# provide the stack contents as a single argument

# while storing the context inside the Tracker

# object itself.

tracker_states, _ = self.tracker(buffers, stacks)

else:

tracker_states = itertools.repeat(None)

lefts, rights, trackings = [], [], []

batch = zip(trans_batch, buffers, stacks, tracker_states)

for transition, buf, stack, tracking in batch:

if transition == SHIFT:

stack.append(buf.pop())

elif transition == REDUCE:

rights.append(stack.pop())

lefts.append(stack.pop())

trackings.append(tracking)

if rights:

reduced = iter(self.reduce(lefts, rights, trackings))

for transition, stack in zip(trans_batch, stacks):

if transition == REDUCE:

stack.append(next(reduced))

return [stack.pop() for stack in stacks]

调用 self.tracker 或 self.reduce,会相对应地运行 Tracker 中的“forward”方式,或 Reduce 子模块。这需要在一个样例列表来执行该 op。所有数学运算密集、用 GPU 加速、收益用 batch 的 op 都发生在 Tracker 和 Reduce 之中。因此,在主要的“forward”方式中,单独在不同样例上运行;对 batch 中的每个样例保持独立的 buffer 和堆栈,都是意义的。为了更干净地写这些函数,我会用一些辅助,把这些样例列表转为 batch 化的张量,反之亦然。

我倾向于让 Reduce 模块自动 batch 参数来加速计算,然后 unbatch 它们,这样之后能单独地 push、pop。把每一组左右子短语放到一起,来表示母短语的合成函数是 TreeLSTM,一个常规 LSTM 的变种。此合成函数要求,所有子树的状态要由两个张量组成,一个隐藏状态 h 和一个内存单元状态 c。定义该函数的因素有两个:运行于子树隐藏状态中的两个线性层 (nn.Linear),以及非线性合成函数 tree_lstm,后者把线性层的结果和子树内存单元的状态组合起来。在 SPINN 中,这通过加入第三个运行于 Tracker 隐藏状态的 线性层来拓展。

def tree_lstm(c1, c2, lstm_in):

# Takes the memory cell states (c1, c2) of the two children, as

# well as the sum of linear transformations of the children’s

# hidden states (lstm_in)

# That sum of transformed hidden states is broken up into a

# candidate output a and four gates (i, f1, f2, and o).

a, i, f1, f2, o = lstm_in.chunk(5, 1)

c = a.tanh() * i.sigmoid() + f1.sigmoid() * c1 + f2.sigmoid() * c2

h = o.sigmoid() * c.tanh()

return h, c

class Reduce(nn.Module):

def __init__(self, size, tracker_size=None):

super(Reduce, self).__init__()

self.left = nn.Linear(size, 5 * size)

self.right = nn.Linear(size, 5 * size, bias=False)

if tracker_size is not None:

self.track = nn.Linear(tracker_size, 5 * size, bias=False)

def forward(self, left_in, right_in, tracking=None):

left, right = batch(left_in), batch(right_in)

tracking = batch(tracking)

lstm_in = self.left(left[0])

lstm_in += self.right(right[0])

if hasattr(self, 'track'):

lstm_in += self.track(tracking[0])

return unbatch(tree_lstm(left[1], right[1], lstm_in))

由于 Reduce 层和以与之类似方式执行的 Tracker 都在 LSTM 上运行,batch 和 unbatch 辅助函数会在成对隐藏、内存状态上运行。

def batch(states):

if states is None:

return None

states = tuple(states)

if states[0] is None:

return None

# states is a list of B tensors of dimension (1, 2H)

# this returns two tensors of dimension (B, H)

return torch.cat(states, 0).chunk(2, 1)

def unbatch(state):

if state is None:

return itertools.repeat(None)

# state is a pair of tensors of dimension (B, H)

# this returns a list of B tensors of dimension (1, 2H)

return torch.split(torch.cat(state, 1), 1, 0)

这就是全部的实操讲解了。其余代码,包含 Tracker,都在 spinn.py 里。至于从两个句子编码上计算 SNLI 类别、并把结果与目标做对比,以给出最终损失变量的分类层,在 model.py 里。 SPINN 的 “forward”代码及其子模块,所产生的是极度复杂的计算图(下图),在损失上达到高潮。其细节与数据集中的每一个 batch 都完全不同,但每次都可简单地调用 loss.backward() 以自动反向传播,其成本很低。loss.backward() 是 PyTorch 内置的一个函数,能在计算图的任意一个点上进行反向传播。

完整代码里的模型和超参数,其性能可与原始 SPINN 论文相提并论。但在 GPU 上,它更快好几倍——它的实现充分利用了 batch 和以及 Pytorch 的高效率。原始的 SPINN 编译计算图花费了 21 分钟(意味着执行时的修补漏洞周期至少也这么长),训练花了大约五天。本文描述的这一版本并没有便宜步骤,在 Tesla K40 GPU 上训练只用了 13 小时,相当于 Quadro GP100 上的九个小时。

整合强化学习

上文描述的、该模型不含 Tracker 的版本,其实特别适合 TensorFlow 的 tf.fold,针对动态计算图特殊情形的 TensorFlow 新专用语言。包含 Tracker 的版本实现起来要难得多。这背后的原因是:加入 Tracker,就意味着从 recursive 模式切换为基于堆栈的模式。在上面的代码里,这以最直观的形式表现了出来,这使用的是取决于输入值的 conditional branches。 Fold 并没有内建的 conditional branch op,所以模型里的图结构只取决于输入的结构而非值。另外,创建一个由 Tracker 决定如何解析输入语句的 SPINN 实际上是不可能的。这是因为 Fold 里的图结构——虽然它们取决于输入样例的结构,在一个输入样例加载之后,它必须完全固定下来。

DeepMind 和谷歌大脑的研究人员正在摸索一个类似的模型。他们用强化学习来训练一个 SPINN 的 Tracker,来解析输入语句,而不需要任何外部解析数据。本质上,这样的模型以随机的猜想开始,当它的解析在整体分类任务上生成较好精度时,奖励它自己,以此来学习。研究人员们写道,他们“使用 batch size 1,因为取决于 policy network [Tracker] 的样本, 对于每个样例,计算图需要在每次迭代后重建。”但即便在像本文这么复杂、结构有随机变化特性的神经网络上,在 PyTorch 上,研究人员们也能只用 batch 训练。

PyTorch 还是第一个在算法库内置了强化学习的框架,即它的 stochastic computation graphs (随机计算图)。这使得 policy gradient 强化学习像反向传播一样易于使用。若想要把它加入上面描述的模型,你只需要像重写主 SPINN 的头几行代码,生成下面一样的循环,让 Tracker 来定义做任何一种解析器(parser)转换的概率。

!# nn.functional contains neural network operations without parameters

from torch.nn import functional as F

transitions = []

for i in range(len(buffers[0]) * 2 - 3): # we know how many steps

# obtain raw scores for each kind of parser transition

tracker_states, transition_scores = self.tracker(buffers, stacks)

# use a softmax function to normalize scores into probabilities,

# then sample from the distribution these probabilities define

transition_batch = F.softmax(transition_scores).multinomial()

transitions.append(transition_batch

当 batch 一路运行下来,模型知道了它的类别预测精确程度之后,我可以在反向传播之外,用传统方式通过图的其余部分把奖励信号传回这些随机计算图节点:

# losses should contain a loss per example, while mean and std

# represent averages across many batches

rewards = (-losses - mean) / std

for transition in transitions:

transition.reinforce(rewards)

# connect the stochastic nodes to the final loss variable

# so that backpropagation can find them, multiplying by zero

# because this trick shouldn’t change the loss value

loss = losses.mean() + 0 * sum(transitions).sum()

# perform backpropagation through deterministic nodes and

# policy gradient RL for stochastic nodes

loss.backward()

谷歌研究人员从 SPINN+增强学习报告的结果,比在 SNLI 获得的原始 SPINN 要好一点,虽然它的增强学习版并没有预计算语法树。深度增强学习在 NLP 的应用是一个全新的领域,其中的研究问题十分广泛。通过把增强学习整合到框架里,PyTorch 极大降低了使用门槛。

vianvidia,雷锋网编译。

“TensorFlow & 神经网络算法高级应用班”要开课啦!

从初级到高级,理论+实战,一站式深度了解 TensorFlow!

本课程面向深度学习开发者,讲授如何利用 TensorFlow 解决图像识别、文本分析等具体问题。课程跨度为 10 周,将从 TensorFlow 的原理与基础实战技巧开始,一步步教授学员如何在 TensorFlow 上搭建 CNN、自编码、RNN、GAN 等模型,并最终掌握一整套基于 TensorFlow 做深度学习开发的专业技能。

两名授课老师佟达、白发川身为 ThoughtWorks 的资深技术专家,具有丰富的大数据平台搭建、深度学习系统开发项目经验。

时间:每周二、四晚 20:00-21:00

开课时长:总学时 20 小时,分 10 周完成,每周2次,每次 1 小时

线上授课地址:http://www.leiphone.com/special/custom/mooc04.html

相关文章:

GAN 很复杂?如何用不到 50 行代码训练 GAN(基于 PyTorch)

PyTorch 的预训练,是时候学习一下了

从原理到实战 英伟达教你用PyTorch搭建RNN(上)

雷锋网版权文章,未经授权禁止转载。详情见转载须知。

振筛激振器图片

槽式镀锌桥架价格

锡铅焊料

相关阅读