blob: 2ddd9e764a3695bc2e8b3e5112cbc4ce3fea42f3 [file]
#-------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#-------------------------------------------------------------
# This builtin function trains LeNet CNN. The architecture of the
# networks is:conv1 -> relu1 -> pool1 -> conv2 -> relu2 -> pool2 ->
# affine3 -> relu3 -> affine4 -> softmax
#
# INPUT:
# ----------------------------------------------------------
# X Input data matrix, of shape (N, C*Hin*Win)
# Y Target matrix, of shape (N, K)
# X_val Validation data matrix, of shape (N, C*Hin*Win)
# Y_val Validation target matrix, of shape (N, K)
# C Number of input channels (dimensionality of input depth)
# Hin Input width
# Win Input height
# batch_size Batch size
# epochs Number of epochs
# lr Learning rate
# mu Momentum value
# decay Learning rate decay
# reg Regularization strength
# seed Seed for model initialization
# verbose Flag indicates if function should print to stdout
# ------------------------------------------------------------------------------------
#
# OUTPUT:
# -------------------------------------------------------------------------------
# model Trained model which can be used in lenetPredict
# -------------------------------------------------------------------------------
source("nn/layers/affine.dml") as affine
source("nn/layers/conv2d_builtin.dml") as conv2d
source("nn/layers/cross_entropy_loss.dml") as cross_entropy_loss
source("nn/layers/dropout.dml") as dropout
source("nn/layers/l2_reg.dml") as l2_reg
source("nn/layers/max_pool2d_builtin.dml") as max_pool2d
source("nn/layers/relu.dml") as relu
source("nn/layers/softmax.dml") as softmax
source("nn/optim/sgd_nesterov.dml") as sgd_nesterov
source("nn/layers/lenetForwardPass.dml") as lenet_fw
m_lenetTrain = function(Matrix[Double] X, Matrix[Double] Y, Matrix[Double] X_val,
Matrix[Double] Y_val, Integer C, Integer Hin, Integer Win, Integer batch_size=64,
Integer epochs=20, Double lr=0.01, Double mu=0.9, Double decay=0.95, Double reg=5e-04,
Boolean verbose=FALSE, Integer seed=-1)
return (List[unknown] model)
{
N = nrow(X)
K = ncol(Y)
# Create network:
# conv1 -> relu1 -> pool1 -> conv2 -> relu2 -> pool2 -> affine3 -> relu3 -> affine4 -> softmax
Hf = 5 # filter height
Wf = 5 # filter width
stride = 1
pad = 2 # For same dimensions, (Hf - stride) / 2
F1 = 32 # num conv filters in conv1
F2 = 64 # num conv filters in conv2
N3 = 512 # num nodes in affine3
# Note: affine4 has K nodes, which is equal to the number of target dimensions (num classes)
[W1, b1] = conv2d::init(F1, C, Hf, Wf, seed) # inputs: (N, C*Hin*Win)
[W2, b2] = conv2d::init(F2, F1, Hf, Wf, seed) # inputs: (N, F1*(Hin/2)*(Win/2))
[W3, b3] = affine::init(F2*(Hin/2/2)*(Win/2/2), N3, seed) # inputs: (N, F2*(Hin/2/2)*(Win/2/2))
[W4, b4] = affine::init(N3, K, seed) # inputs: (N, N3)
W4 = W4 / sqrt(2) # different initialization, since being fed into softmax, instead of relu
# Initialize SGD w/ Nesterov momentum optimizer
vW1 = sgd_nesterov::init(W1); vb1 = sgd_nesterov::init(b1)
vW2 = sgd_nesterov::init(W2); vb2 = sgd_nesterov::init(b2)
vW3 = sgd_nesterov::init(W3); vb3 = sgd_nesterov::init(b3)
vW4 = sgd_nesterov::init(W4); vb4 = sgd_nesterov::init(b4)
# Optimize
iters = ceil(N / batch_size)
for (e in 1:epochs) {
loss = 0
val_loss = 0
val_acc = 0
for(i in 1:iters) {
# Get next batch
beg = ((i-1) * batch_size) %% N + 1
end = min(N, beg + batch_size - 1)
X_batch = X[beg:end,]
y_batch = Y[beg:end,]
model = list(W1=W1, b1=b1, W2=W2, b2=b2, W3=W3, b3=b3, W4=W4, b4=b4)
cache = lenet_fw::lenetForward(X=X_batch, C=C, Hin=Hin, Win=Win, model=model)
# loss
if(verbose){
loss = loss + cross_entropy_loss::forward(as.matrix(cache["probs"]), y_batch)
val_cache = lenet_fw::lenetForward(X=X_val, C=C, Hin=Hin, Win=Win, model=model)
val_loss = val_loss + cross_entropy_loss::forward(as.matrix(val_cache["probs"]), Y_val)
val_acc = val_acc + mean(rowIndexMax(as.matrix(val_cache["probs"])) == rowIndexMax(Y_val))
}
dprobs = cross_entropy_loss::backward(as.matrix(cache["probs"]), y_batch)
# Compute data backward pass
[dW1, db1, dW2, db2, dW3, db3, dW4, db4] = feed_backward(
X_batch, C, Hin, Win, reg, model, dprobs, cache)
# Optimize with SGD w/ Nesterov momentum
[W1, vW1] = sgd_nesterov::update(W1, dW1, lr, mu, vW1)
[b1, vb1] = sgd_nesterov::update(b1, db1, lr, mu, vb1)
[W2, vW2] = sgd_nesterov::update(W2, dW2, lr, mu, vW2)
[b2, vb2] = sgd_nesterov::update(b2, db2, lr, mu, vb2)
[W3, vW3] = sgd_nesterov::update(W3, dW3, lr, mu, vW3)
[b3, vb3] = sgd_nesterov::update(b3, db3, lr, mu, vb3)
[W4, vW4] = sgd_nesterov::update(W4, dW4, lr, mu, vW4)
[b4, vb4] = sgd_nesterov::update(b4, db4, lr, mu, vb4)
}
if(verbose) {
print("Epoch: " + e + ", Train loss: " + loss/iters + ", Validation loss: " +
val_loss/iters + ", Validation accuracy: " + val_acc/iters)
}
# Anneal momentum towards 0.999
mu = mu + (0.999 - mu)/(1+epochs-e)
# Decay learning rate
lr = lr * decay
}
model = list(W1=W1, b1=b1, W2=W2, b2=b2, W3=W3, b3=b3, W4=W4, b4=b4)
}
feed_backward = function(Matrix[Double] X, Integer C, Integer Hin, Integer Win,
Double reg,list[unknown] model, matrix[Double] dprobs, list[unknown] cache)
return (Matrix[Double] dW1, Matrix[Double] db1,
Matrix[Double] dW2, Matrix[Double] db2,
Matrix[Double] dW3, Matrix[Double] db3,
Matrix[Double] dW4, Matrix[Double] db4)
{
Hf = 5 # filter height
Wf = 5 # filter width
stride = 1
pad = 2 # For same dimensions, (Hf - stride) / 2
F1 = 32 # num conv filters in conv1
F2 = 64 # num conv filters in conv2
# Compute data backward pass
## layer 4: affine4 -> softmax
douta4 = softmax::backward(dprobs, as.matrix(cache["outa4"]))
[doutd3, dW4, db4] = affine::backward(douta4, as.matrix(cache["outd3"]), as.matrix(model["W4"]), as.matrix(model["b4"]))
## layer 3: affine3 -> relu3 -> dropout
doutr3 = dropout::backward(doutd3, as.matrix(cache["outr3"]), 0.5, as.matrix(cache["maskd3"]))
douta3 = relu::backward(doutr3, as.matrix(cache["outa3"]))
[doutp2, dW3, db3] = affine::backward(douta3, as.matrix(cache["outp2"]), as.matrix(model["W3"]), as.matrix(model["b3"]))
## layer 2: conv2 -> relu2 -> pool2
doutr2 = max_pool2d::backward(doutp2, as.integer(as.scalar(cache["Houtp2"])), as.integer(as.scalar(cache["Woutp2"])),
as.matrix(cache["outr2"]), F2, as.integer(as.scalar(cache["Houtc2"])), as.integer(as.scalar(cache["Woutc2"])), Hf=2, Wf=2,
strideh=2, stridew=2, padh=0, padw=0)
doutc2 = relu::backward(doutr2, as.matrix(cache["outc2"]))
[doutp1, dW2, db2] = conv2d::backward(doutc2, as.integer(as.scalar(cache["Houtc2"])), as.integer(as.scalar(cache["Woutc2"])),
as.matrix(cache["outp1"]), as.matrix(model["W2"]), as.matrix(model["b2"]), F1, as.integer(as.scalar(cache["Houtp1"])),
as.integer(as.scalar(cache["Woutp1"])), Hf, Wf, stride, stride, pad, pad)
## layer 1: conv1 -> relu1 -> pool1
doutr1 = max_pool2d::backward(doutp1, as.integer(as.scalar(cache["Houtp1"])), as.integer(as.scalar(cache["Woutp1"])),
as.matrix(cache["outr1"]), F1, as.integer(as.scalar(cache["Houtc1"])), as.integer(as.scalar(cache["Woutc1"])), Hf=2, Wf=2,
strideh=2, stridew=2, padh=0, padw=0)
doutc1 = relu::backward(doutr1, as.matrix(cache["outc1"]))
[dX_batch, dW1, db1] = conv2d::backward(doutc1, as.integer(as.scalar(cache["Houtc1"])), as.integer(as.scalar(cache["Woutc1"])),
X, as.matrix(model["W1"]), as.matrix(model["b1"]), C, Hin, Win, Hf, Wf, stride, stride, pad, pad)
# Compute regularization backward pass
dW1_reg = l2_reg::backward(as.matrix(model["W1"]), reg)
dW2_reg = l2_reg::backward(as.matrix(model["W2"]), reg)
dW3_reg = l2_reg::backward(as.matrix(model["W3"]), reg)
dW4_reg = l2_reg::backward(as.matrix(model["W4"]), reg)
dW1 = dW1 + dW1_reg
dW2 = dW2 + dW2_reg
dW3 = dW3 + dW3_reg
dW4 = dW4 + dW4_reg
}