Preface

因为要复现前面阅读的一篇论文:《论文笔记:Deep Relative Distance Learning: Tell the Difference Between Similar Vehicles》 中提到的用来区分相似图像的两个损失函数:Triplet Loss、Coupled Cluster Loss 。上面的那篇论文没有提供源代码,因此得自己去写这两个损失函数模块,然后 require '***' 到 Torch 中。

这里学习一下该怎么样在 Torch 中加自己的模块层。Torch 有了官方教程:http://torch.ch/docs/developer-docs.html,中科院自动化所的博士@beanfrog 也写过一篇专栏:《如何在torch中添加新的层?》,下面是阅读及总结。

另,我还没到自己写 C/CUDA 代码的程度上,仅仅在用 lua、Tensor 实现的很浅的程度上,理解的不对请多多批评。

Torch 中如何实现自己的 nn 模块

首先,Torch 中实现自己的 nn modules 有两种层次: 
1. 如果要实现的目标比较简单,对性能要求不是那么高,那么几行简单的 Lua 代码就可以实现; 
2. 如果对计算性能要求较高,或者你想在用 CPU、GPU 计算时做特殊的处理、优化,就需要写 C/CUDA 层次的代码。

Modules 包含两种状态变量(state variables):output 以及 gradInput。先回顾一下,一个 Module 需要去实现一些基础的函数。 
第一个是如下的函数:

[output] forward(input)
  • 1

这个函数是输入一个数据,计算模型相应的输出。通常来说,这里的 input 以及 output 都是 Tensors 。但是,一些特殊的 sub-classes,如 table layers 输入输出可能并不是 Tensors,而是其他什么东西。这个具体的需要去查阅每个 Module 的具体说明。 
在 forward() 之后,变量 output 的值就需要更新到一个新的值。 
但是,并不建议这么去 override 这个 forward() 函数。相反的,我们需要去实现 updateOutput(input) 这个函数。抽象父类 Module 中的 forward(input) 函数,会去调用 updateOutput(input)

第二个需要去实现的函数是:

[gradInput] backward(input, gradOutput)
  • 1

这个函数根据输入的 input,实现相应的 back-propagation 过程。一般来说,当调用这个函数时,应该是已经执行了 forward(input) 这个函数。

If you do not respect this rule, backend() will compute incorrect gradients.

gradOutputgradInput都是 Tensors,同上,有些并不是,如 table 类,所以需要查每一个 Module 具体的说明。

一个 back-propagation 过程在给定的 gradOutputModule 关于输出 output 的求导) 之上,包括了两个梯度计算。

这个函数调用了两个函数:

  • 调用了 updateGradInput(input, gradOutput)
  • 调用了 accGradParameters(input, gradOutput)

同样也不建议直接去重写 backward(input, gradOutput) 函数,更好的方式是去重写 updateGradInput(input, gradOutput) 、 accGradParameters(input, gradOutput)这两个函数。

好了,总结一下。 
当定义一个新的 module 的时候,需要我们必须实现两种操作:forward、backward 。但并不建议去直接 override 这两个函数,而是去重写下面两个函数。第一个就是:

[output] updateOutput(input)
  • 1

当定义一个 Module 的时候,上面的这个函数需要被重写。用当前的模型(模型里的参数不变),输入一个 input 值,得到输出。这个函数的返回值存储在 output 变量中。

第二个就是:

[gradInput] updateGradInput(input, gradOutput)
  • 1

这个函数在新定义 Module 的时候也需要被重写,计算 Module 关于输入 input 的导数,函数的返回值为 gradInput,同时,变量 gradInput 的值需要进行更新。

接着要注意了,当定义一个新的 Module 时,如果这个 Module 有需要去训练的参数时(如激活函数层,就没有需要去训练的参数),下面的这个函数需要被 override 。

accGradParameters(input, gradOutput)
  • 1

这是计算一个 Module 相对于权重的导数,许多 Module 不需要执行这一步,因为没有权重参数,如激活函数层。

将这些 累积(accumulation) 归零(Zeroing)可以通过函数:zeroGradParameters() 来实现,根据 累积 更新这些参数可以通过函数:updateParameters() 来实现。

还可以定义函数:reset() ,这个函数定义怎么样去重置训练参数,如在训练前初始化参数。

如果你想使用 optim package ,modules 还提供了其他你可以去自定义的函数。

下面是一个定义新类的模板,每当我们要去定义一个新的 nn Module 的时候,我们只需要去填补下面函数体。

local NewClass, Parent = torch.class('nn.NewClass', 'nn.Module')function NewClass:__init()Parent.__init(self)endfunction NewClass:updateOutput(input)
endfunction NewClass:updateGradInput(input, gradOutput)
endfunction NewClass:accGradParameters(input, gradOutput)
endfunction NewClass:reset()
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

要注意的是,当定义 __init() 这个 “构造函数(constructor)” 后,我们都首先要去调用这个 “构造函数(constructor)”,进行初始化操作。如我们常用的 nn.Linear(3, 4),其表示 3 个输入,4 个输出,其初始化函数形式为 function Linear:__init(inputSize, outputSize, bias), 其中 bias 表示偏置项,默认没有。

updateOutput(input),前向传播时调用该函数,计算 Y=F(X)Y=F(X)XX 为输入,YY 为输出。

updateGradInput(input, gradOutput),反向传播是调用该函数,计算损失 lossloss 相对于输入 XX 的偏导:EX=EY×YX∂E∂X=∂E∂Y×∂Y∂X,其中,EY∂E∂Y 为 lossloss 相对于输出 YY 的偏导。

accGradParameters(input, gradOutput),当 模块(层) 中没有需要学习的 权重(参数) 时, 不需要写这个函数(如 nn.Dropout、nn.ReLU 等)。该函数计算输出 lossloss 对 weightweightbiasbias (如果有的话)的偏导:

EW=EY×YWEB=EY×YB∂E∂W=∂E∂Y×∂Y∂W∂E∂B=∂E∂Y×∂Y∂B

以上函数的具体实现,若能通过 Tensor 自带的计算完成,则直接一个 Lua 文件即可,因为 Tensor 的运算自动支持 CPU 和 GPU,故该层也能直接支持 CPU 和 GPU了。

实现自己的 Dropout 层

Dropout 模块代码

所谓的 Dropout ,是指随机的将网络中的神经元归零,具体的可以去看 Hinton 的 JMLR 的这篇论文:《Dropout - A Simple Way to Prevent Neural Networks from Overfitting》,已经实验证实,这种策略可以有效的防止过拟合。

那么 Dropout 该如何实现呢?

local Dropout, Parent = torch.class('nn.Dropout', 'nn.Module')function Dropout:__init(p)Parent.__init(self)self.p = p or 0.5if self.p >= 1 or self.p < 0 thenerror('<Dropout> illegal percentage, must be 0 <= p < 1')endself.noise = torch.Tensor()
endfunction 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
endfunction Dropout:updateGradInput(input, gradOutput)self.gradInput:resizeAs(gradOutput):copy(gradOutput)self.gradInput:cmul(self.noise) -- simply mask the gradients with the noise vectorreturn self.gradInput
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

这里还有一篇添加自己的 ReLU 层的例子,可以参考:http://blog.csdn.net/lanran2/article/details/50494570

Jacobian 矩阵检验

Jacobian 矩阵 是一阶偏导数以一定方式排列成的矩阵,类似于多元函数的导数。 
假设 F:RnRmF:Rn→Rm 是一个从欧式 nn 维空间转换到欧式 mm 维空间的函数。这个函数由 mm 个实函数组成:

y1(x1,...,xn)...ym(x1,...,xn)y1(x1,...,xn)...ym(x1,...,xn)

这些函数的偏导数(如果存在)可以组成一个  mm 行  nn 列的矩阵,这就是所谓的 Jacobian 矩阵:

y1x1ymx1y1xnymxn[∂y1∂x1⋯∂y1∂xn⋮⋱⋮∂ym∂x1⋯∂ym∂xn]

此矩阵表示为: JF(x1,,xn)JF(x1,…,xn) 或者  (y1,,ym)(x1,,xn)∂(y1,…,ym)∂(x1,…,xn)

当实现完自己的 module 后,最好还需要测试验证。这可以用 nn 中提供的 Jacobian 类来检验:

-- 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 thenprint('==> module OK')
elseprint('==> err too large, incorrect implementation')
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上部分检验代码中,用了函数:nn.Jacobian.testJacobian(),这个函数的代码在这里:https://github.com/torch/nn/blob/master/Jacobian.lua#L240,翻进去看代码:

function nn.Jacobian.testJacobianParameters(module, input, param, dparam, minval, maxval, perturbation)minval = minval or -2maxval = maxval or 2local inrange = maxval - minvalinput:copy(torch.rand(input:nElement()):mul(inrange):add(minval))param:copy(torch.rand(param:nElement()):mul(inrange):add(minval))local jac_bprop = nn.Jacobian.backward(module, input, param, dparam)local jac_fprop = nn.Jacobian.forward(module, input, param, perturbation)local error = jac_fprop - jac_bpropreturn error:abs():max()
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

实现 Triplet Loss

Triplet Loss 示意图及其 Loss Function

Triplet Loss 的输入是“三元”的:{<xa,xp,xn>}{<xa,xp,xn>},其中,xaxa 与 xpxp 属于正样本,xnxn 属于负样本。通过训练,使得 xaxa 与 xpxp 之间“拉的更近”xaxa 与 xnxn 之间“推的更远”。示意图如下:

其损失函数为:

L=Nmax{f(xa)f(xp)22+αf(xa)f(xn)22,0}L=∑Nmax{‖f(xa)−f(xp)‖22+α−‖f(xa)−f(xn)‖22, 0}

Triplet Loss 模块 Torch 实现

Github 上这里有人实现了 Triplet Loss:https://github.com/Atcold/torch-TripletEmbedding,具体的实现很简单,就写了一个 TripletEmbedding.lua 文件:

local TripletEmbeddingCriterion, parent = torch.class('nn.TripletEmbeddingCriterion', 'nn.Criterion')
  • 1

这是 Triplet Loss 类的实现声明,一般自定义 Loss Function 继承自 nn.Criterion.

function TripletEmbeddingCriterion:__init(alpha)parent.__init(self)self.alpha = alpha or 0.2self.Li = torch.Tensor()self.gradInput = {}
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这是 Triplet Loss 的初始化函数,当使用 Triplet Loss 的时候,这个函数首先被调用,进行初始化。

  • self.alpha 是 Triplet Loss 公式中的 αα,默认为 0.20.2
  • self.Li 表示每一个计算得到的 LossLoss,先声明为 torch.Tensor(),大小维数、类型都待定
  • self.gradInput 表示输入,输入为 table: {},其实是为 {aImgs, pImgs, nImgs}
function TripletEmbeddingCriterion:updateOutput(input)local a = input[1] -- ancorlocal p = input[2] -- positivelocal n = input[3] -- negativelocal N = a:size(1) self.Li = torch.max(torch.cat(torch.Tensor(N):zero():type(torch.type(a)) , (a - p):norm(2,2):pow(2) -  (a - n):norm(2,2):pow(2) + self.alpha, 2), 2)self.output = self.Li:sum() / Nreturn self.output
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这是需要我们自己去实现的 forward 过程,但是因为不建议去直接 override forward 函数,而是去实现 updateOutput() 函数。

输入的 input 实际上为 table{aImgs, pImgs, nImgs}。所以创建 3 个 local 变量:local alocal plocal n

local N 为 input 中传递过来的三元组的个数;

torch.max(0, x) 函数很好理解;

torch.Tensor(N):zero():type(torch.type(a)),创建一列 N 个 0 元素 Tensor,类型与 a 的一致;

(a - p):norm(2,2):pow(2),因为若 a、p 的 Size 是 N×1024N×1024 ,10241024 是最后输出的特征维数(我选择的是 10241024维),所以这句话(a - p):norm(2,2)求的是在第二个维度(10241024)上的 2 范数,并 :pow(2)一下,即平方一下;

self.output = self.Li:sum() / N,求一个均值;

return self.output,返回结果。

function TripletEmbeddingCriterion:updateGradInput(input)local a = input[1] -- ancorlocal p = input[2] -- positivelocal n = input[3] -- negativelocal N = a:size(1)self.gradInput[1] = (n - p):cmul(self.Li:gt(0):repeatTensor(a:size(2),1):t():type(a:type()) * 2/N)self.gradInput[2] = (p - a):cmul(self.Li:gt(0):repeatTensor(a:size(2),1):t():type(a:type()) * 2/N)self.gradInput[3] = (a - n):cmul(self.Li:gt(0):repeatTensor(a:size(2),1):t():type(a:type()) * 2/N)return self.gradInput
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这是反向传播过程需要实现了 updateGradInput函数,因为 Triplet Loss 的损失函数,对三个输入的求导分别为:

这是截取的 2015 年 CVPR 的一篇论文:《Simultaneous feature learning and hash coding with deep neural networks》上的公式。上面函数体里面就实现的是上图中  公式(3)的内容。

Reference

最后,在贴出几个有价值的链接,可供参考:

  1. Google Groups 中的讨论:Custom softmax loss function
  2. Oxford Machine Learning 教程:Implementing your own layer
  3. http://code.madbits.com/wiki/doku.php?id=tutorial_morestuff
  4. Stackoverflow 上的讨论:Add my custom loss function to torch
  5. Github 上 Torch 的 Modules 文档,实现一个自己的 Modules,文档得看看
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010167269/article/details/51966427

Torch 中添加自己的 nn Modules:以添加 Dropout、 Triplet Loss 为例相关推荐

  1. yolov5 v3.0训练报错: torch.nn.modules.module.ModuleAttributeError: ‘BatchNorm2d‘ object has no attribute

    欢迎大家关注笔者,你的关注是我持续更博的最大动力 原创文章,转载告知,盗版必究 yolov5 v3.0版本训练报错:torch.nn.modules.module.ModuleAttributeErr ...

  2. Can‘t get attribute ‘SiLU‘ on <module ‘torch.nn.modules.activation

    在yolov5 4.0版本测试的时候,加载模型报错: Can't get attribute 'SiLU' on <module 'torch.nn.modules.activation 报错代 ...

  3. TypeError: torch.nn.modules.batchnorm.BatchNorm2d is not a Module subclass

    TypeError: torch.nn.modules.batchnorm.BatchNorm2d is not a Module subclass torch.nn.modules.batchnor ...

  4. torch.nn.modules.activation.ReLU is not a Module subclass

    torch.nn.modules.activation.ReLU is not a Module subclass 定位到问题代码: 修改为: fc_layers.append(nn.ReLU())

  5. einops包中的rearrange,reduce, repeat及einops.layers.torch中的Rearrange,Reduce。对高维数据的处理方式

    from einops import rearrange, reduce, repeat from einops.layers.torch import Rearrange, Reduce 一.rea ...

  6. (原)torch中微调某层参数

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/6221664.html 参考网址: https://github.com/torch/nn/issues ...

  7. torch中乘法整理,*torch.mul()torch.mv()torch.mm()torch.dot()@torch.mutmal()

    目录 *位置乘 torch.mul():数乘 torch.mv():矩阵向量乘法 torch.mm() 矩阵乘法 torch.dot() 点乘积 @操作 torch.matmul() *位置乘 符号* ...

  8. python语言中ch用法_pytorch 中pad函数toch.nn.functional.pad()的用法

    padding操作是给图像外围加像素点. 为了实际说明操作过程,这里我们使用一张实际的图片来做一下处理. 这张图片是大小是(256,256),使用pad来给它加上一个黑色的边框.具体代码如下: imp ...

  9. 【增强学习】Torch中的增强学习层

    要想在Torch框架下解决计算机视觉中的增强学习问题(例如Visual Attention),可以使用Nicholas Leonard提供的dpnn包.这个包对Torch中原有nn包进行了强大的扩展, ...

最新文章

  1. 关于unityengine.dll, unityengine.coremodule.dll
  2. idea git 在文件上点了revert怎么复原_在 IntelliJ IDEA 中使用 Git,太方便了
  3. python中的@property(get与set作用
  4. 三、Linux 开机、重启和用户登录注销
  5. ROS笔记(27) 机械臂的组装
  6. (16)FPGA面试题MOORE 与 MEELEY状态机
  7. c++ windows下declspec
  8. [导入]基于Spring+zk的WebDisk系统研究.pdf(462.84 KB)
  9. html返回顶部开始隐藏,回到顶部并且监听顶部按钮显示或隐藏
  10. argparse模块中的参数action、dest使用
  11. 分布式Zookeeper-基础
  12. 仿淘宝头像上传功能(三)——兼容 IE6 浏览器。
  13. 微生物组β-多样性——PCoA分析及可视化
  14. 社群空间站一键发布微信群精品优质社群的搜索和发布平台源码
  15. Excel | 替换特定大小的单元格值(如:小于5000的值)为指定值
  16. Java虚拟机:Java虚拟机结构
  17. PHP:安装fileinfo扩展
  18. 无线学习:srsRAN环境搭建【无线学习笔记二】
  19. 第二十次csp认证 第四题 星际旅行题解
  20. 【敏捷研发系列】前端DevOps流水线实践

热门文章

  1. asp.net datagrid 自定义分页
  2. 论文大讲堂-2014-IJCV-A Comprehensive Survey to Face Hallucination-Part1
  3. vue中的.env文件
  4. linux如何挂载swap分区,Linux挂载新硬盘和创建Swap分区的方法
  5. 基于VMware的虚拟机资源池实现(下)-运营资源池
  6. 华为云RDS数据库测评:性能超出预期,双11优惠还在继续
  7. Android APT
  8. 2022csdn收藏夹在哪里找--图文
  9. ElGamal公钥密码算法及ElGamal数字签名方案实现
  10. 火山引擎A/B测试平台设计思路与技术实现