先看一段代码,一般来说传入VGG的数据要先经过预处理,无非是先将BGR变成RGB,然后减去整个训练集的平均RGB值。写的最精简的可能是下面这样写。
function preprocess(img)
local mean_pixel = torch.FloatTensor({103.939, 116.779, 123.68}):type(img:type())
local perm = torch.LongTensor{3, 2, 1}
img = img:index(1, perm):mul(255.0)
mean_pixel = mean_pixel:view(3, 1, 1):expandAs(img)
img:add(-1, mean_pixel)
return img
end
然而。。请看下面的这种写法:
function getPreprocessConv()
local mean_pixel = torch.Tensor({103.939, 116.779, 123.68})
local conv = nn.SpatialConvolution(3,3, 1,1)
conv.weight:zero()
conv.weight[{1,3}] = 255
conv.weight[{2,2}] = 255
conv.weight[{3,1}] = 255
conv.bias = -mean_pixel
conv.gradBias = nil
conv.gradWeight = nil
conv.parameters = function() --[[nop]] end
conv.accGradParameters = function() --[[nop]] end
return conv
end
没错,直接用内置的卷积层进行操作!这启发我们一件事情:很多常见操作,我们都可以用内置的已有的各种层进行!而不是自己单独写,这就相当于nn层式编码!。。自创的词,大概知道就行。
Simple Layers就是一些常见的层操作,利用这些函数可以便捷的在某层后面加入各种操作层,这些操作层可以直接进行反向传播。
Simple Layers主要分为4个部分:
带参的Module—-> 基本Tensor变换——->数学Tensor运算——>其他Module
前面两篇写的就是Tensor和Math的,而在这里第二三正是利用这些简单层来替代Tensor变换和Math操作。
从名字就可以大概看出这些层的作用,比如Linear就是全连接层。Bilinear就是进行双线性插值。
module= nn.Dropout(p)
训练前向:使得输入每个值按照p的概率丢弃,即使得该位置的值为0。同时输出大小的值被放大1/(1-p)
,主要是用来进行正规化的。
训练反向:被前向丢弃的位置仍旧为0,gradOutput
和input
都被放大1/(1-p)
倍。
module = nn.Dropout()
> module:forward(x)
0 4 0 0
10 12 0 16
[torch.DoubleTensor of dimension 2x4]
> module:backward(x, x:clone():fill(1))
0 2 0 0
2 2 0 2
[torch.DoubleTensor of dimension 2x4]
evaluation时:直接将输入的东西原封不动传出
> module:evaluate()
> module:forward(x)
1 2 3 4
5 6 7 8
[torch.DoubleTensor of dimension 2x4]
文档竟然这么简洁,Euclidean ,Cosine用法一个例子都没有。
module=nn.Cosine(inputSize, outputSize)
require "nn"
require "torch"
local t = torch.randn(6):mul(3):floor()
print('input:')
print(t)
local net = nn.Sequential()
modual = nn.Cosine(6,4)
net:add(modual)
local w = torch.randn(4,6):mul(3):floor()
net:get(1).weight = w
print('weight:')
print(net:get(1).weight)
local result = net:forward(t)
print('result:')
print(result)
for i = 1,(#w)[1] do
print('time:'..i)
local tm = t:norm()
local wm = net:get(1).weight[i]:norm()
print('input'..i..'\tnorm:'..tm)
print('weight'..i..'\tnorm:'..wm)
print(torch.dot(t,w[i])/(tm*wm))
print(result[i])
assert((torch.dot(t,w[i])/(tm*wm) - result[i]) < 1e-10)
print('')
end
input:
-2
-5
-4
-2
0
-1
[torch.DoubleTensor of size 6]
weight:
-2 6 -1 -1 4 1
5 2 0 -2 4 2
2 6 1 -4 1 5
1 0 0 -2 -1 -3
[torch.DoubleTensor of size 4x6]
result:
-0.3866
-0.3497
-0.5433
0.1826
[torch.DoubleTensor of size 4]
time:1
input1 norm:7.0710678118655
weight1 norm:7.6811457478686
-0.3866413395173
-0.38664133951719
time:2
input2 norm:7.0710678118655
weight2 norm:7.2801098892805
-0.34966291044862
-0.34966291044852
time:3
input3 norm:7.0710678118655
weight3 norm:9.1104335791443
-0.54330536799443
-0.5433053679943
Input是6x1,必须是vector; Cosine层的weight是N*6, 然后每个weight[i]与input进行内积,然后再normalize一下。简单来说就是:
inputDim: a
weightDim: N*a
ouput: N
这个是输入什么,直接输出什么。在用ParallelTable或是ConcatTable时特别有用。
-- Returns a network that computes the CxC Gram matrix from inputs
-- of size C x H x W
function GramMatrix()
local net = nn.Sequential()
net:add(nn.View(-1):setNumInputDims(2))
local concat = nn.ConcatTable()
concat:add(nn.Identity())
concat:add(nn.Identity())
net:add(concat)
net:add(nn.MM(false, true))
return net
end
module = nn.Replicate(nFeature [, dim, ndim])
默认在第一维度进行复制,这个和以前的Tensor操作一样,没有内存的复制,只是将stride置为0了。
> x = torch.linspace(1, 5, 5)
1
2
3
4
5
[torch.DoubleTensor of dimension 5]
> m = nn.Replicate(3)
> o = m:forward(x)
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
[torch.DoubleTensor of dimension 3x5]
Reshape可以被View代替
> x = torch.Tensor(4, 4)
> for i = 1, 4 do
> for j = 1, 4 do
> x[i][j] = (i-1)*4+j
> end
> end
> print(x)
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
[torch.Tensor of dimension 4x4]
> print(nn.View(2, 8):forward(x))
1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
[torch.DoubleTensor of dimension 2x8]
-- size可以是numbers或是LongStorage都行。
> print(nn.View(torch.LongStorage{8,2}):forward(x))
1 2
3 4
5 6
7 8
9 10
11 12
13 14
15 16
[torch.DoubleTensor of dimension 8x2]
> print(nn.View(16):forward(x))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[torch.DoubleTensor of dimension 16]
不想说什么了,Torch的文档真垃圾!写都写不明白!看的我火大!
View的功能更加强大,其中setNumInputDims()可以指定希望输入的维度的数量,一般是和view(-1)连用,这样可以实现minibatch 的输入
> input = torch.Tensor(2, 3)
> minibatch = torch.Tensor(5, 2, 3)
> m = nn.View(-1):setNumInputDims(2)
> print(#m:forward(input))
6
[torch.LongStorage of size 1]
> print(#m:forward(minibatch))
5
6
[torch.LongStorage of size 2]
重点:
View(-1):setNumInputDims(2)这两个是连用的!
应该这样考虑,
1. 首先获取原始输入的维度,view(-1)是将输入全部变成1维的,相当于1*6.
2. 再根据setNumInputDims(2)知道,要把原始大小为2x3的input的倒数2个维度看成是一个sample。
3. 然后我input被拉成向量后总共就1x6个数,这里把倒数2个维度看成是一个sample,也就是每个sample是6个数,所以input就一个sample,或是说batchsize=1
再看看minibatch,minibatch是5x2x3,setNumInputDims(2)是将2x3看成一个sample,所以,这里有5个sample。输出的大小就变成 5x6。
module = nn.Unsqueeze(pos [, numInputDims])
unsqueeze是在pos位置插入1.
pos = 1: 1 x p x q x r
pos = 2: p x 1 x q x r
pos = 3: p x q x 1 x r
pos = 4: p x q x r x 1
一般来说在第一个位置进行Unsqueeze,同时要指定numInputDims才可以实现minibatch
b = 5 -- batch size 5
input = torch.Tensor(b, 2, 4, 3) -- input: b x 2 x 4 x 3
numInputDims = 3 -- input feature map should be the last 3 dims
m = nn.Unsqueeze(4, numInputDims)
m:forward(input) -- output: b x 2 x 4 x 3 x 1
m = nn.Unsqueeze(2):setNumInputDims(numInputDims)
m:forward(input) -- output: b x 2 x 1 x 4 x 3