在 Torch 的 GitHub 上编辑的开发文档
一、编写自己的 NN 模块
我们将看到如何创建自己的新模块,并测试它们。您应该能够无缝地将它们插入现有的神经网络中。 如果模块简单、性能要求不高,那么你可以简单地写几行 Lua(1节)。 如果模块在计算上较重,或者您想要为 CPU 或 GPU 创建专门化和优化的代码,那么您可能希望在 CUDA 级创建模块(第 2 节)。
模块是构建神经网络的砖。模块本身是一个神经网络,但它可以与其他网络结合使用容器类来创建复杂的神经网络。模块是一个抽象类,它定义了训练神经网络所必需的基本方法。所有的模块都是可序列化的。
模块包含两个状态变量:output
和 gradinput
。这里我们回顾一个 Module
必须实现的一组基本函数:
[output] forward(input)
获取一个输入对象,并计算模块的相应输出。一般来说,输入和输出都是张量。然而,一些特殊的子类如表层可能会期待其他的东西。请参考每个模块规范以获得进一步的信息。
在 forward()
,output
状态变量应该被更新为新值。 不建议重写此函数。相反,要实现 updateoutput(input)
功能。forward(input)
在抽象父类Module
函数将调用 updateoutput(input)
。
[gradInput] backward(input, gradOutput)
对给定的输入通过模块执行反向传播步骤。一般来说,这种方法使前面的 forward(input)
被调用,具有相同的输入。这对于优化原因是必要的。
如果你不尊重这个规则,backward()将计算错误的梯度。 在一般的
input
,gradoutput
和gradinput
是Tensors
张量。然而,一些特殊的子类如表层可能会期待其他的东西。请参考每个模块规范以获得进一步的信息。 反向传播算法的步骤包括在计算两种梯度输入给定的gradoutput(相对于模块的输出梯度)。 此函数只使用两个函数调用执行此任务:
- 一个函数调用
updateGradInput(input, gradOutput)
. - 一个函数调用
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论