torch-logo

Table of Contents

  1. Abtract
  2. Preliminaries
  3. Installation
  4. nn package
  5. torch-rnn package
  6. nngraph package

Abtract

Torch1(非PyTorch)作为一个远古的深度学习框架,在2016年前后有着非常广泛的应用。 其最大的缺点莫过于使用了LUA语言作为基础语言。 由于LUA过于小众,因此学习成本比PyTorch要高很多。 此外,由于其仅支持LUA语言,debugger的过程跟pdb也是相距甚远。

但是,Torch作为最初的一批基于脚本语言编程的深度学习框架,一度给研究者们带来了极大的方便。 而且从名字上看,PyTorch也是在致敬Torch了(其实真实的原因是最初PyTorch和Torch共享了部分底层的C语言的源代码,只是上层支持的脚本语言不同)。 由于最近需要复现DenseCap,而其只有Torch版本的源程序,因此重拾Torch。 现在看来,Torch的设计也仍然不落后,属于能用的范畴。 但是相比于动态计算图来说,确实不甚方便。

本篇博客的主要目的是记录自己Torch的复习过程。 具体来讲,我会将Torch中搭建网络的常用技巧,变量,模型,以及支持计算图的nngraph包都进行一个简单的介绍。 相比于官方教程,本篇博客更像是一个学习笔记,目的在于帮助自己快速回忆起Torch相关的开发细节。 阅读时间:30分钟。

Preliminaries

符合如下条件之一:

  1. 了解过Torch,或对Torch有过开发经验。
  2. 了解其他某个深度学习框架,并掌握了LUA语言的基本语法。

Installation

安装教程: http://torch.ch/docs/getting-started.html#_

注意Torch不支持9.2之后的CUDA版本。因此,不能再RTX 20XX系列及其之后的显卡上安装。

nn package

torch中所有的网络模型都在nn包中。包括了:

  1. Module类:一个抽象类,是所有官方及自定义的模型的父类。
  2. Containers:主要包括了Sequential,Parallel,Concat三个类。
  3. Activation Functions:包含了常用激活函数
  4. Simple Layers:包含了一些简单的层,如线性层,BatchNorm等
  5. Table layers:包括ConcatTable, ParallelTable, JoinTable, MapTable, SplitTable等
  6. Convolution layers:包括了常用的卷积层
  7. Criterions:包括了常用的损失函数

Module

所有module的父类。其中重要的几个方法:

  1. output=updateOutput(input):前向传播方法。
  2. gradInput=updateGradInput(input, gradOutput):用于反向传播中计算输入梯度,链式法则中的一环。
  3. accGradParameters(input, gradOutput, scale):用于反向传播中更新模型梯度,如有多个loss可多次调用。
  4. zeroGradParameters():模型梯度清零。
  5. updateParameters(learningRate):根据当前模型梯度和输入的学习速率更新参数。

与Pytorch相似,Module中也有forward和backward方法。 但这两个方法都不建议override。 其中forward方法会调用updateOutput方法进行前项传播。 backward方法会调用updateGradInput和accGradParameters计算模型的输入梯度和参数梯度,并返回输入梯度,以便使用链式法则计算前边层的梯度。

Containers

Sequential

一进一出,与pytorch中的nn.Sequential()相似,结构:

input -> nn.Sequential()[1] -> nn.Sequential()[2] -> ... -> output

Parallel(indim, outdim)

不太常用。该层会先根据indim拆分输入tensor,用包含的sub-modules分别处理切分后的tensor,最后再根据outdim进行concat。

举例如nn.Parallel(1, 2)。若其包括n个子模块,则输入tensor在维度1上必须等于n,例如对于:

input=torch.randn(n, 100)。

在前项传播时,input会被首先拆分为:

(input_1, ..., input_n)

其中每一个都是100维。然后调用其中的n个子模块分别处理这n个输入,最后再在维度2上concat。

input_1 -> nn.Parallel(1,2)[1] -> output_1
input_2 -> nn.Parallel(1,2)[2] -> output_2     concat on dim 2
...                                        ----------------------->  output
input_n -> nn.Parallel(1,2)[n] -> output_n

如果输出数据没有维度2,或每个模块的output在其他维度上尺寸不匹配,则会报错。

Concat(dim)

一进多出后根据dim参数来concat成一个tensor。结构:

                     -> output_1      concat
input -> nn.Concat() -> output_2 ---------------> output
                     ...
                     -> output_n

Table Layers

处理Table的数据层。该类数据层的输入为table。常用的包括:

ParallelTable

多进多出。输入输出都为table。中间包含多个子模块。 每个子模块负责处理table中对应位置的数据。后输出一个table。 与nn.Parallel虽然名字相似但是功能完全不同。 该模块不会对输入和输出做任何切分或concat的操作:

input_table = { input_1,  -> nn.ParallelTable()[1] -> { output_1, 
                input_2,  -> nn.ParallelTable()[2] ->   output_2,
                input_3 } -> nn.ParallelTable()[3] ->   output_3 } = output_table

ConcatTable

一进多出。输入为tensor或table。

如果输入为tensor,则其为其中包含的所有子模块的共同输入。 输出为一个table,包含了每个子模块各自的输出:

input ->   nn.ConcatTable()[1] -> { output_1,
      ->   nn.ConcatTable()[2] ->   output_2,
      ...                           ...
      ->   nn.ConcatTable()[n] ->   output_n } = output_table

如果输入为table,则输出为一个table的table。 其中每个输出table中的元素(一个table)对应输入中的一个元素。 对输入的table支持递归:

input_table = { input_1,  -> nn.ConcatTable() -> { output_table_1, 
                input_2,  -> nn.ConcatTable() ->   output_table_2, 
                ...          ...                   ...
                input_n } -> nn.ConcatTable() ->   output_table_n } 

JoinTable(dimension, nInputDims)

多进一出。输入为一个tensor的table。 输出为根据dimension参数将输入的tensor全部concat起来。 nInputDims指定了输入tensor应该有的维度,该参数的存在是为了支持mini-batch。

input_table = { input_1,                           concat along dim d
                input_2,   ---> nn.JoinTable(d) ------------------------> output
                ...     
                input_n }

CAddTable

多进一出。输入为一个tensor的table。输出为所有输入tensor的逐元素和。

input_table = { input_1, ---> nn.CAddTable() -> output = input_1 + input_2 + ... + input_n
                input_2,
                ...     
                input_n }

其他的可参考:https://github.com/torch/nn/blob/master/doc/table.md#nn.TableLayers 总结来讲Table layers对于数据的操作更为直观,没有对于tensor数据更为复杂或者多余的操作,因此相比于container,使用更多。

Simple demos
mlp = nn.Sequential()       -- Create a network that takes a Tensor as input
mlp:add(nn.SplitTable(2))
c = nn.ParallelTable()      -- The two Tensor slices go through two different Linear
c:add(nn.Linear(10, 3))     -- Layers in Parallel
c:add(nn.Linear(10, 7))
mlp:add(c)                  -- Outputing a table with 2 elements
p = nn.ParallelTable()      -- These tables go through two more linear layers separately
p:add(nn.Linear(3, 2))
p:add(nn.Linear(7, 1))
mlp:add(p)
mlp:add(nn.JoinTable(1))    -- Finally, the tables are joined together and output.

pred = mlp:forward(torch.randn(10, 2))
print(pred)

Simple Layers

简单层的初始化与使用与Pytorch相似。这里罗列一些常用的作为示例。

--- 全连接
nn.Linear(inputDimension, outputDimension, [bias = true])
--- 取最大
nn.Max(dimension, nInputDim)
--- 取最小
nn.Min(dimension, nInputDim)
--- 取平均
nn.Mean(dimension, nInputDim)
--- 取和
nn.Sum(dimension, nInputDim, sizeAverage, squeeze)
--- 取cos
nn.Cosine(inputSize,outputSize)
--- Skip Connection
nn.Identity()
--- Reshape
nn.Reshape(dimension1, dimension2, ... [, batchMode])
nn.View(sizes)
--- 消除冗余维度
nn.Squeeze([dim, numInputDims])
--- 增加冗余维度
nn.Unsqueeze(pos [, numInputDims])
--- 维度置换
nn.Transpose({dim1, dim2} [, {dim3, dim4}, ...])
--- Dropout
nn.Dropout()
--- BatchNorm
nn.BatchNormalization(N [, eps] [, momentum] [,affine])

Convolution Layers

卷积层的初始化与使用与Pytorch相似。这里罗列一些常用的作为示例。

--- 普通卷积层
nn.SpatialConvolution(nInputPlane, nOutputPlane, kW, kH, [dW], [dH], [padW], [padH])
--- 膨胀卷积层
nn.SpatialDilatedConvolution(nInputPlane, nOutputPlane, kW, kH, [dW], [dH], [padW], [padH], [dilationW], [dilationH])
--- 最大池化
nn.SpatialMaxPooling(kW, kH [, dW, dH, padW, padH])
--- 膨胀最大池化
nn.SpatialDilatedMaxPooling(kW, kH [, dW, dH, padW, padH, dilationW, dilationH])
--- 全局自适应最大值池化
nn.SpatialAdaptiveMaxPooling(W, H)
--- 全局自适应均值池化
nn.SpatialAdaptiveAveragePooling(W, H)
--- 下采样
nn.SpatialSubSampling(nInputPlane, kW, kH, [dW], [dH])
--- 双线性插值上采样
nn.SpatialUpSamplingBilinear(scale)
nn.SpatialUpSamplingBilinear({oheight=H, owidth=W})

torch-rnn package

torch-rnn是DenseCap作者Justin Johnson写的torch框架下的递归网络的包。 里边包含了一些主流递归网络结构。如RNN,LSTM等。 使用方法与pytorch中也类似。这里列出两个常用的模块。

普通的RNN:

--- h[t] = tanh(Wh h[t- 1] + Wx x[t] + b)
nn.VanillaRNN(inputDim, hiddenDim)

--- 代码示例,隐变量h可以显式输入也可以使用默认值。
h = rnn:forward({h0, x})
grad_h0, grad_x = unpack(rnn:backward({h0, x}, grad_h))

h = rnn:forward(x)
grad_x = rnn:backward(x, grad_h)

LSTM:

nn.LSTM(inputDim, hiddenDim)

---代码示例,其中Cell state和Hidden state都可以使用默认值。
h = lstm:forward({c0, h0, x})
grad_c0, grad_h0, grad_x = unpack(lstm:backward({c0, h0, x}, grad_h))

h = lstm:forward({h0, x})
grad_h0, grad_x = unpack(lstm:backward({h0, x}, grad_h))

h = lstm:forward(x)
grad_x = lstm:backward(x, grad_h)

nngraph

nngraph是torch的计算图包。 通过使用nngraph,能够使torch支持类似于静态计算图(如初代TensorFlow)的功能。

计算图中的输入节点的定义方式为:

input_node = nn.Module_name(Parameters)()

而中间节点的定义方式为:

next_node = nn.Module_name(Parameters)(previous_nodes)

在定义完成所有的几点后,通过nn.gModule来初始化整个计算图:

nn.gModule({input_nodes}, {output_nodes})

可视化计算图:

graph.dot(mlp.fg, 'MLP')

--- 保存至文件夹myMLP
graph.dot(mlp.fg, 'MLP', 'myMLP')

其他信息(画图时添加指定注释,debug等)可参考https://github.com/torch/nngraph

Simple demos
--- h1: 输入节点, h2: 输出节点
--- h1 -> nn.Linear(20,10) -> nn.Tanh() -> nn.Linear(10,10) -> nn.Tanh() -> nn.Linear(10,1) -> h2
h1 = nn.Linear(20, 10)()
h2 = nn.Linear(10, 1)(nn.Tanh()(nn.Linear(10, 10)(nn.Tanh()(h1))))
mlp = nn.gModule({h1}, {h2})

--- 前向传播与反向传播示例
x = torch.rand(20)
dx = torch.rand(1)
mlp:updateOutput(x)
mlp:updateGradInput(x, dx)
mlp:accGradParameters(x, dx)

上述计算图也可通过如下等价方式进行定义

h1 = - nn.Linear(20,10)
h2 = h1
     - nn.Tanh()
     - nn.Linear(10,10)
     - nn.Tanh()
     - nn.Linear(10, 1)
mlp = nn.gModule({h1}, {h2})

双输入节点示例,其中计算图的输入为输入节点实例化后对应的table:

h1 = nn.Linear(20, 20)()
h2 = nn.Linear(10, 10)()
hh1 = nn.Linear(20, 1)(nn.Tanh()(h1))
hh2 = nn.Linear(10, 1)(nn.Tanh()(h2))
madd = nn.CAddTable()({hh1, hh2})
oA = nn.Sigmoid()(madd)
oB = nn.Tanh()(madd)
gmod = nn.gModule({h1, h2}, {oA, oB})

x1 = torch.rand(20)
x2 = torch.rand(10)

gmod:updateOutput({x1, x2})
gmod:updateGradInput({x1, x2}, {torch.rand(1), torch.rand(1)})

Footnote

  1. http://torch.ch/.