在 Torch 的 GitHub 上编辑的开发文档

发布于 2021-07-13 21:07:03 字数 9765 浏览 1242 评论 0

一、编写自己的 NN 模块

我们将看到如何创建自己的新模块,并测试它们。您应该能够无缝地将它们插入现有的神经网络中。 如果模块简单、性能要求不高,那么你可以简单地写几行 Lua(1节)。 如果模块在计算上较重,或者您想要为 CPU 或 GPU 创建专门化和优化的代码,那么您可能希望在 CUDA 级创建模块(第 2 节)。

模块是构建神经网络的砖。模块本身是一个神经网络,但它可以与其他网络结合使用容器类来创建复杂的神经网络。模块是一个抽象类,它定义了训练神经网络所必需的基本方法。所有的模块都是可序列化的。

模块包含两个状态变量:outputgradinput。这里我们回顾一个 Module 必须实现的一组基本函数:

[output] forward(input)

获取一个输入对象,并计算模块的相应输出。一般来说,输入和输出都是张量。然而,一些特殊的子类如表层可能会期待其他的东西。请参考每个模块规范以获得进一步的信息。

forward()output 状态变量应该被更新为新值。 不建议重写此函数。相反,要实现 updateoutput(input) 功能。forward(input) 在抽象父类Module 函数将调用 updateoutput(input)

[gradInput] backward(input, gradOutput)

对给定的输入通过模块执行反向传播步骤。一般来说,这种方法使前面的 forward(input) 被调用,具有相同的输入。这对于优化原因是必要的。

如果你不尊重这个规则,backward()将计算错误的梯度。 在一般的 inputgradoutputgradinputTensors 张量。然而,一些特殊的子类如表层可能会期待其他的东西。请参考每个模块规范以获得进一步的信息。 反向传播算法的步骤包括在计算两种梯度输入给定的gradoutput(相对于模块的输出梯度)。 此函数只使用两个函数调用执行此任务:

  1. 一个函数调用 updateGradInput(input, gradOutput).
  2. 一个函数调用 accGradParameters(input,gradOutput).

不建议在自定义类中覆盖此函数调用。这是更好地覆盖 updateGradInput(input, gradOutput)accGradParameters(input, gradOutput) 功能。

[output] updateOutput(input)

定义新模块时,应重载此方法。

使用类和输入的当前参数集计算输出。此函数返回存储在 output 字段中的结果。

[gradInput] updateGradInput(input, gradOutput)

定义新模块时,应重载此方法。 计算模块相对于自身 input 的梯度。这是在 gradinput 返回。同时,该 gradinput 状态变量相应的更新。

accGradParameters(input, gradOutput)

定义一个新的模块时,该方法可能需要重载,如果模块具有可训练参数。 计算模块相对于自身参数的梯度。许多模块没有执行这一步,因为它们没有任何参数。参数的状态变量名是依赖于模块的。预计模块将积累一些参数中的梯度。 归零这积累是 zerogradparameters() 更新参数根据这种积累是做 updateparameters() 实现。

reset()

该方法定义了可训练的参数初始化复位,即训练前。 模块提供了一些其他的方法,你可以定义,如果你不打算使用的optim。这些方法有助于zero()参数,并更新他们使用非常基本的技术。 在代码结构方面,Torch提供了一个类模型,我们用它来继承,通常用于定义神经网络中的所有模块。

这是一个典型的新类的空持有者:

local NewClass, Parent = torch.class('nn.NewClass', 'nn.Module')

function NewClass:__init()
  Parent.__init(self)
  end

function NewClass:updateOutput(input)
end

function NewClass:updateGradInput(input, gradOutput)
end

function NewClass:accGradParameters(input, gradOutput)
end

function NewClass:reset()
end

定义一个新类时,我们需要做的就是填充这些空函数。注意,__init()定义构造函数时,我们总是先调用父构造器。

现在让我们看一些实际例子。

1、在 Lua 写模块:实现激活水平差的单位

差的单位有一个中心思想就是得到隐单元的激活,通过随机零有些单位。

可以这样定义类:

local Dropout, Parent = torch.class('nn.Dropout', 'nn.Module')

function Dropout:__init(p)
  Parent.__init(self)
  self.p = p or 0.5
  if self.p >= 1 or self.p < 0 then
    error('<Dropout> illegal percentage, must be 0 <= p < 1')
  end
  self.noise = torch.Tensor()
end

function Dropout:updateOutput(input)
  self.output:resizeAs(input):copy(input)
  self.noise:resizeAs(input)
  self.noise:bernoulli(1-self.p)
  self.output:cmul(self.noise)
  return self.output
end

function Dropout:updateGradInput(input, gradOutput)
  self.gradInput:resizeAs(gradOutput):copy(gradOutput)
  self.gradInput:cmul(self.noise) -- simply mask the gradients with the noise vector
  return self.gradInput
end

在编写具有梯度估计的模块时,测试您的实现总是非常重要的。这可以通过提供网络雅可比上课容易做,比较梯度方法的实施(updategradinput()accgradparameters())用有限差得到的雅可比矩阵(扰动的模块,输入和输出的增量估计)。可以这样做:


-- parameters
local precision = 1e-5
local jac = nn.Jacobian

-- define inputs and module
local ini = math.random(10,20)
local inj = math.random(10,20)
local ink = math.random(10,20)
local percentage = 0.5
local input = torch.Tensor(ini,inj,ink):zero()
local module = nn.Dropout(percentage)

-- test backprop, with Jacobian
local err = jac.testJacobian(module,input)
print('==> error: ' .. err)
if err<precision then
  print('==> module OK')
else
    print('==> error too large, incorrect implementation')
end

一个小问题的雅可比类的事实是,它假定一个模块的输出相对于输入的确定性。对于特定的模块,情况并非如此,因此,为了这些测试的目的,我们需要冻结噪声的产生,即只做一次: 我们超负荷的updateoutput()函数生成噪声只有一次整体测试。

function Dropout:updateOutput(input)
  self.output:resizeAs(input):copy(input)
  self.noise = self.noise or input.new():resizeAs(input):bernoulli(1-self.p)
  self.output:cmul(self.noise)
  return self.output
end

2、在 C 或 CUDA 级别编写模块

C 使用模板 在使用C编写Torch之前,首先要熟悉在Torch和NN上C的使用语法。

例如,看看这个代码出现在THTensorMath.C

void THTensor_(add)(THTensor *r_, THTensor *t, real value)
{
  THTensor_(resizeAs)(r_, t);
  if (THTensor_(isContiguous)(r_) && THTensor_(isContiguous)(t) && THTensor_(nElement)(r_) == THTensor_(nElement)(t)) {
    real *tp = THTensor_(data)(t);
    real *rp = THTensor_(data)(r_);
    long sz = THTensor_(nElement)(t);
    long i;
    #pragma omp parallel for if(sz > TH_OMP_OVERHEAD_THRESHOLD) private(i)
    for (i=0; i<sz; i++)
       rp[i] = tp[i] + value;
  } else {
    TH_TENSOR_APPLY2(real, r_, real, t, *r__data = *t_data + value;);
  }
}

奇怪的_(add)(THTensor *r_ ....)的语法,您所看到的语法是一个预处理器。

lib/TH/THTensor.h:
#define THTensor_(NAME)  TH_CONCAT_4(TH,Real,Tensor_,NAME)

导致…

lib/TH/THGeneral.h.in:
#define TH_CONCAT_4(x,y,z,w) TH_CONCAT_4_EXPAND(x,y,z,w)

最后

lib/TH/THGeneral.h.in:
#define TH_CONCAT_4_EXPAND(x,y,z,w) x ## y ## z ## w

因此,在预处理之后,新加上几个宏,

void THTensor_(add)(THTensor *r_, THTensor *t, real value)

最终变成这样:

long THRealTensor_add(const THRealTensor *r_, THRealTensor *t, real value)

实数和实数被定义为特定类型,例如,用于浮点精度:

#define Real Float
#define real float

最后使该函数原型:

long THFloatTensor_add(const THFloatTensor *r_, THFloatTensor *t, float value)

不只是大的预处理程序? 您也会在NN库中看到类似的语法,所以请好好地为这个语法做好准备。

3、C nn module

了解如何编写一个新的NN模块的最好方法是查看现有的模块。 下面是编写NN阈值的演练: 步骤1:写Lua的一部分 https://github.com/torch/nn/blob/master/Threshold.lua

1、写的构造函数

2、写 updateoutput / updategradinput,简单地称为C 调用C有一个高效但有点奇怪的语法: https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/Threshold.lua#L20

input.nn.Threshold_updateOutput(self, input)

这一行只是调用这里定义的函数: https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/generic/Threshold.c#L5

的原因,是因为你需要登记的C函数下的input.nn。表在这里: https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/generic/Threshold.c#L61-L63

这有助于我们编写适用于任何定义的张量类型的通用代码,而不必执行任何复杂的动态函数分派逻辑。 完整的神经网络。阈值模块写成两个文件:- Lua:

https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/Threshold.lua

C part

https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/generic/Threshold.c

这些文件包含在这些行中的包中。: - init.lua :

https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/init.lua#L68 - init.c :

https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/init.c#L41-L42 - init.c :

https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/init.c#L153 - init.c :

https://github.com/torch/nn/blob/b80e26e8b849a69b8121acf62f3487095c2f10e8/init.c#L194

4、CUDA nn module

添加CUDA支持NN。阈值模块,类似于写作门槛。C,我们将写一threshold.cu

https://github.com/torch/cunn/blob/master/lib/THCUNN/Threshold.cu

把它包括在这里:

https://github.com/torch/cunn/blob/master/init.cu#L36 https://github.com/torch/cunn/blob/master/utils.h#L30

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文