Merge pull request #1034 from ShangtongZhang/master

Support batched forward and backward for FFN.
This commit is contained in:
Marcus Edel
2017-07-10 12:51:30 +02:00
committed by GitHub
10 changed files with 194 additions and 60 deletions
+25 -10
View File
@@ -176,16 +176,6 @@ class FFN
const size_t i,
arma::mat& gradient);
/**
* Compute the gradient of the feedforward network based on given input and target.
*
* @param predictors Input training variables.
* @param responses Outputs results from input training variables.
* @return Desired gradients of the feedforward network.
*/
arma::mat Gradient(const arma::mat& predictors,
const arma::mat& responses);
/*
* Add a new module to the model.
*
@@ -218,6 +208,31 @@ class FFN
template<typename Archive>
void Serialize(Archive& ar, const unsigned int /* version */);
/**
* Perform the forward pass of the data in real batch mode.
*
* Forward and Backward should be used as a pair, and they are designed mainly
* for advanced users. User should try to use Predict and Train unless those
* two functions can't satisfy some special requirements.
*
* @param inputs The input data.
* @param results The predicted results.
*/
void Forward(arma::mat inputs, arma::mat& results);
/**
* Perform the backward pass of the data in real batch mode.
*
* Forward and Backward should be used as a pair, and they are designed mainly
* for advanced users. User should try to use Predict and Train unless those
* two functions can't satisfy some special requirements.
*
* @param targets The training target.
* @param gradients Computed gradients.
* @return Training error of the current pass.
*/
double Backward(arma::mat targets, arma::mat& gradients);
private:
// Helper functions.
/**
+38 -10
View File
@@ -127,6 +127,44 @@ void FFN<OutputLayerType, InitializationRuleType>::Train(
<< "." << std::endl;
}
template<typename OutputLayerType, typename InitializationRuleType>
void FFN<OutputLayerType, InitializationRuleType>::Forward(
arma::mat inputs, arma::mat& results)
{
if (parameter.is_empty())
ResetParameters();
if (!deterministic)
{
deterministic = true;
ResetDeterministic();
}
currentInput = std::move(inputs);
Forward(std::move(currentInput));
results = boost::apply_visitor(outputParameterVisitor, network.back());
}
template<typename OutputLayerType, typename InitializationRuleType>
double FFN<OutputLayerType, InitializationRuleType>::Backward(
arma::mat targets, arma::mat& gradients)
{
currentTarget = std::move(targets);
double res = outputLayer.Forward(std::move(boost::apply_visitor(
outputParameterVisitor, network.back())), std::move(currentTarget));
outputLayer.Backward(std::move(boost::apply_visitor(outputParameterVisitor,
network.back())), std::move(currentTarget), std::move(error));
gradients = arma::zeros<arma::mat>(parameter.n_rows, parameter.n_cols);
Backward();
ResetGradients(gradients);
Gradient();
return res;
}
template<typename OutputLayerType, typename InitializationRuleType>
void FFN<OutputLayerType, InitializationRuleType>::Predict(
arma::mat predictors, arma::mat& results)
@@ -215,16 +253,6 @@ void FFN<OutputLayerType, InitializationRuleType>::Gradient(
Gradient();
}
template<typename OutputLayerType, typename InitializationRuleType>
arma::mat FFN<OutputLayerType, InitializationRuleType>::Gradient(
const arma::mat& predictors, const arma::mat& responses)
{
ResetData(predictors, responses);
arma::mat gradients;
Gradient(Parameters(), 0, gradients);
return gradients;
};
template<typename OutputLayerType, typename InitializationRuleType>
void FFN<OutputLayerType, InitializationRuleType>::ResetParameters()
{
+2 -1
View File
@@ -31,7 +31,8 @@ template<typename eT>
void Add<InputDataType, OutputDataType>::Forward(
const arma::Mat<eT>&& input, arma::Mat<eT>&& output)
{
output = input + weights;
output = input;
output.each_col() += weights;
}
template<typename InputDataType, typename OutputDataType>
+4 -2
View File
@@ -48,7 +48,8 @@ template<typename eT>
void Linear<InputDataType, OutputDataType>::Forward(
const arma::Mat<eT>&& input, arma::Mat<eT>&& output)
{
output = (weight * input) + bias;
output = weight * input;
output.each_col() += bias;
}
template<typename InputDataType, typename OutputDataType>
@@ -68,7 +69,8 @@ void Linear<InputDataType, OutputDataType>::Gradient(
{
gradient.submat(0, 0, weight.n_elem - 1, 0) = arma::vectorise(
error * input.t());
gradient.submat(weight.n_elem, 0, gradient.n_elem - 1, 0) = error;
gradient.submat(weight.n_elem, 0, gradient.n_elem - 1, 0) =
arma::mean(error, 1);
}
template<typename InputDataType, typename OutputDataType>
@@ -57,7 +57,8 @@ void LogSoftMax<InputDataType, OutputDataType>::Forward(
return 0.0;
});
output = input - (maxInput + std::log(arma::accu(output)));
maxInput.each_row() += arma::log(arma::sum(output));
output = input - maxInput;
}
template<typename InputDataType, typename OutputDataType>
@@ -67,7 +68,7 @@ void LogSoftMax<InputDataType, OutputDataType>::Backward(
arma::Mat<eT>&& gy,
arma::Mat<eT>&& g)
{
g = gy - arma::exp(input) * arma::accu(gy);
g = arma::exp(input) + gy;
}
template<typename InputDataType, typename OutputDataType>
@@ -42,14 +42,14 @@ namespace rl {
*
* @tparam EnvironmentType The environment of the reinforcement learning task.
* @tparam NetworkType The network to compute action value.
* @tparam OptimizerType The optimizer to train the network.
* @tparam UpdaterType How to apply gradients when training.
* @tparam PolicyType Behavior policy of the agent.
* @tparam ReplayType Experience replay method.
*/
template <
typename EnvironmentType,
typename NetworkType,
typename OptimizerType,
typename UpdaterType,
typename PolicyType,
typename ReplayType = RandomReplay<EnvironmentType>
>
@@ -69,7 +69,7 @@ class QLearning
* object, be sure to use std::move to avoid unnecessary copy.
*
* @param network The network to compute action value.
* @param optimizer The optimizer to train the network.
* @param stepSize Learning rate.
* @param discount Discount for future return.
* @param policy Behavior policy of the agent.
* @param replayMethod Experience replay method.
@@ -78,10 +78,11 @@ class QLearning
* @param explorationSteps Steps before starting to learn.
* @param doubleQLearning Whether to use double Q-Learning.
* @param stepLimit Maximum steps in each episode, 0 means no limit.
* @param updater How to apply gradients when training.
* @param environment Reinforcement learning task.
*/
QLearning(NetworkType network,
OptimizerType optimizer,
const double stepSize,
const double discount,
PolicyType policy,
ReplayType replayMethod,
@@ -89,6 +90,7 @@ class QLearning
const size_t explorationSteps,
const bool doubleQLearning = false,
const size_t stepLimit = 0,
UpdaterType updater = UpdaterType(),
EnvironmentType environment = EnvironmentType());
/**
@@ -127,8 +129,11 @@ class QLearning
//! Locally-stored target network.
NetworkType targetNetwork;
//! Locally-stored optimizer.
OptimizerType optimizer;
//! Locally-stored learning rate.
double stepSize;
//! Locally-stored updater.
UpdaterType updater;
//! Discount factor of future return.
double discount;
@@ -20,18 +20,18 @@ namespace rl {
template <
typename EnvironmentType,
typename NetworkType,
typename OptimizerType,
typename UpdaterType,
typename PolicyType,
typename ReplayType
>
QLearning<
EnvironmentType,
NetworkType,
OptimizerType,
UpdaterType,
PolicyType,
ReplayType
>::QLearning(NetworkType network,
OptimizerType optimizer,
const double stepSize,
const double discount,
PolicyType policy,
ReplayType replayMethod,
@@ -39,9 +39,11 @@ QLearning<
const size_t explorationsSteps,
const bool doubleQLearning,
const size_t stepLimit,
UpdaterType updater,
EnvironmentType environment):
learningNetwork(std::move(network)),
optimizer(std::move(optimizer)),
stepSize(stepSize),
updater(std::move(updater)),
discount(discount),
policy(std::move(policy)),
replayMethod(std::move(replayMethod)),
@@ -54,20 +56,22 @@ QLearning<
deterministic(false)
{
learningNetwork.ResetParameters();
this->updater.Initialize(learningNetwork.Parameters().n_rows,
learningNetwork.Parameters().n_cols);
targetNetwork = learningNetwork;
}
template <
typename EnvironmentType,
typename NetworkType,
typename OptimizerType,
typename UpdaterType,
typename PolicyType,
typename ReplayType
>
arma::Col<size_t> QLearning<
EnvironmentType,
NetworkType,
OptimizerType,
UpdaterType,
PolicyType,
ReplayType
>::BestAction(const arma::mat& actionValues)
@@ -85,14 +89,14 @@ arma::Col<size_t> QLearning<
template <
typename EnvironmentType,
typename NetworkType,
typename OptimizerType,
typename UpdaterType,
typename BehaviorPolicyType,
typename ReplayType
>
double QLearning<
EnvironmentType,
NetworkType,
OptimizerType,
UpdaterType,
BehaviorPolicyType,
ReplayType
>::Step()
@@ -148,7 +152,7 @@ double QLearning<
// Compute the update target.
arma::mat target;
learningNetwork.Predict(sampledStates, target);
learningNetwork.Forward(sampledStates, target);
for (size_t i = 0; i < sampledNextStates.n_cols; ++i)
{
target(sampledActions[i], i) = sampledRewards[i] +
@@ -156,7 +160,9 @@ double QLearning<
}
// Learn form experience.
learningNetwork.Train(sampledStates, target, optimizer);
arma::mat gradients;
learningNetwork.Backward(target, gradients);
updater.Update(learningNetwork.Parameters(), stepSize, gradients);
return reward;
}
@@ -164,14 +170,14 @@ double QLearning<
template <
typename EnvironmentType,
typename NetworkType,
typename OptimizerType,
typename UpdaterType,
typename BehaviorPolicyType,
typename ReplayType
>
double QLearning<
EnvironmentType,
NetworkType,
OptimizerType,
UpdaterType,
BehaviorPolicyType,
ReplayType
>::Episode()
+6 -3
View File
@@ -912,15 +912,18 @@ BOOST_AUTO_TEST_CASE(SimpleLogSoftmaxLayerTest)
LogSoftMax<> module;
// Test the Forward function.
input = arma::mat("0.5 0.5");
input = arma::mat("0.5; 0.5");
module.Forward(std::move(input), std::move(output));
BOOST_REQUIRE_SMALL(arma::accu(arma::abs(
arma::mat("-0.6931 -0.6931") - output)), 1e-3);
arma::mat("-0.6931; -0.6931") - output)), 1e-3);
// Test the Backward function.
error = arma::zeros(input.n_rows, input.n_cols);
// Assume LogSoftmax layer is always associated with NLL output layer.
error(1, 0) = -1;
module.Backward(std::move(input), std::move(error), std::move(delta));
BOOST_REQUIRE_EQUAL(arma::accu(delta), 0);
BOOST_REQUIRE_SMALL(arma::accu(arma::abs(
arma::mat("1.6487; 0.6487") - delta)), 1e-3);
}
BOOST_AUTO_TEST_SUITE_END();
@@ -13,6 +13,7 @@
#include <mlpack/core.hpp>
#include <mlpack/core/optimizers/rmsprop/rmsprop.hpp>
#include <mlpack/core/optimizers/sgd/update_policies/vanilla_update.hpp>
#include <mlpack/methods/ann/layer/layer.hpp>
#include <mlpack/methods/ann/ffn.hpp>
@@ -151,6 +152,81 @@ BOOST_AUTO_TEST_CASE(VanillaNetworkTest)
(dataset, labels, dataset, labels, 2, 10, 50, 0.2);
}
BOOST_AUTO_TEST_CASE(ForwardBackwardTest)
{
arma::mat dataset;
dataset.load("mnist_first250_training_4s_and_9s.arm");
// Normalize each point since these are images.
for (size_t i = 0; i < dataset.n_cols; ++i)
dataset.col(i) /= norm(dataset.col(i), 2);
arma::mat labels = arma::zeros(1, dataset.n_cols);
labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1);
labels += 1;
FFN<NegativeLogLikelihood<> > model;
model.Add<Linear<> >(dataset.n_rows, 50);
model.Add<SigmoidLayer<> >();
model.Add<Linear<> >(50, 10);
model.Add<LogSoftMax<> >();
VanillaUpdate opt;
model.ResetParameters();
opt.Initialize(model.Parameters().n_rows, model.Parameters().n_cols);
double stepSize = 0.01;
size_t batchSize = 10;
size_t iteration = 0;
bool converged = false;
while (iteration < 1000)
{
arma::running_stat<double> error;
size_t batchStart = 0;
while (batchStart < dataset.n_cols)
{
size_t batchEnd = std::min(batchStart + batchSize,
(size_t)dataset.n_cols);
arma::mat currentData = dataset.cols(batchStart, batchEnd - 1);
arma::mat currentLabels = labels.cols(batchStart, batchEnd - 1);
arma::mat currentResuls;
model.Forward(currentData, currentResuls);
arma::mat gradients;
model.Backward(currentLabels, gradients);
opt.Update(model.Parameters(), stepSize, gradients);
batchStart = batchEnd;
arma::mat prediction = arma::zeros<arma::mat>(1, currentResuls.n_cols);
for (size_t i = 0; i < currentResuls.n_cols; ++i)
{
prediction(i) = arma::as_scalar(arma::find(
arma::max(currentResuls.col(i)) == currentResuls.col(i), 1)) + 1;
}
size_t correct = 0;
for (size_t i = 0; i < currentLabels.n_cols; i++)
{
if (int(arma::as_scalar(prediction.col(i))) ==
int(arma::as_scalar(currentLabels.col(i))))
{
correct++;
}
}
error(1 - (double)correct / batchSize);
}
Log::Debug << "Current training error: " << error.mean() << std::endl;
iteration++;
if (error.mean() < 0.01)
{
converged = true;
break;
}
}
BOOST_REQUIRE(converged);
}
/**
* Train and evaluate a Dropout network with the specified structure.
*/
+11 -14
View File
@@ -12,7 +12,6 @@
#include <mlpack/core.hpp>
#include <mlpack/core/optimizers/gradient_descent/gradient_descent.hpp>
#include <mlpack/methods/ann/ffn.hpp>
#include <mlpack/methods/ann/init_rules/gaussian_init.hpp>
#include <mlpack/methods/ann/layer/layer.hpp>
@@ -20,6 +19,8 @@
#include <mlpack/methods/reinforcement_learning/environment/mountain_car.hpp>
#include <mlpack/methods/reinforcement_learning/environment/cart_pole.hpp>
#include <mlpack/methods/reinforcement_learning/policy/greedy_policy.hpp>
#include <mlpack/core/optimizers/adam/adam_update.hpp>
#include <mlpack/core/optimizers/rmsprop/rmsprop_update.hpp>
#include <boost/test/unit_test.hpp>
#include "test_tools.hpp"
@@ -35,23 +36,21 @@ BOOST_AUTO_TEST_SUITE(QLearningTest);
BOOST_AUTO_TEST_CASE(CartPoleWithDQN)
{
// Set up the network.
FFN<MeanSquaredError<>, GaussianInitialization> model;
FFN<MeanSquaredError<>, GaussianInitialization> model(MeanSquaredError<>(),
GaussianInitialization(0, 0.001));
model.Add<Linear<>>(4, 128);
model.Add<ReLULayer<>>();
model.Add<Linear<>>(128, 128);
model.Add<ReLULayer<>>();
model.Add<Linear<>>(128, 2);
// Set up the optimizer generator.
StandardSGD opt(0.0001, 2);
// Set up the policy and replay method.
GreedyPolicy<CartPole> policy(1.0, 1000, 0.1);
RandomReplay<CartPole> replayMethod(10, 10000);
// Set up DQN agent.
QLearning<CartPole, decltype(model), decltype(opt), decltype(policy)>
agent(std::move(model), std::move(opt), 0.9, std::move(policy),
QLearning<CartPole, decltype(model), AdamUpdate, decltype(policy)>
agent(std::move(model), 0.01, 0.9, std::move(policy),
std::move(replayMethod), 100, 100, false, 200);
arma::running_stat<double> averageReturn;
@@ -102,23 +101,21 @@ BOOST_AUTO_TEST_CASE(CartPoleWithDoubleDQN)
for (size_t trial = 0; trial < 4; ++trial)
{
// Set up the network.
FFN<MeanSquaredError<>, GaussianInitialization> model;
FFN<MeanSquaredError<>, GaussianInitialization> model(MeanSquaredError<>(),
GaussianInitialization(0, 0.001));
model.Add<Linear<>>(4, 20);
model.Add<ReLULayer<>>();
model.Add<Linear<>>(20, 20);
model.Add<ReLULayer<>>();
model.Add<Linear<>>(20, 2);
// Set up the optimizer.
StandardSGD opt(0.0001, 2);
// Set up the policy and replay method.
GreedyPolicy<CartPole> policy(1.0, 1000, 0.1);
RandomReplay<CartPole> replayMethod(10, 10000);
// Set up the DQN agent.
QLearning<CartPole, decltype(model), decltype(opt), decltype(policy)>
agent(std::move(model), std::move(opt), 0.9, std::move(policy),
QLearning<CartPole, decltype(model), RMSPropUpdate, decltype(policy)>
agent(std::move(model), 0.01, 0.9, std::move(policy),
std::move(replayMethod), 100, 100, true, 200);
arma::running_stat<double> averageReturn;
@@ -134,7 +131,7 @@ BOOST_AUTO_TEST_CASE(CartPoleWithDoubleDQN)
*/
Log::Debug << "Average return: " << averageReturn.mean()
<< " Episode return: " << episodeReturn << std::endl;
if (averageReturn.mean() > 30)
if (averageReturn.mean() > 40)
{
agent.Deterministic() = true;
arma::running_stat<double> testReturn;