Merge pull request #1389 from ShikharJ/LayerNorm

Implement Layer Normalization.
This commit is contained in:
Marcus Edel
2018-05-18 22:44:44 -07:00
committed by GitHub
8 changed files with 414 additions and 6 deletions
@@ -41,6 +41,8 @@ set(SOURCES
join.hpp
join_impl.hpp
layer.hpp
layer_norm.hpp
layer_norm_impl.hpp
layer_traits.hpp
layer_types.hpp
leaky_relu.hpp
+1 -1
View File
@@ -19,7 +19,7 @@ namespace mlpack {
namespace ann /** Artificial Neural Network. */ {
/**
* Declaration of the Batch Normalization layer class. The layer tranforms
* Declaration of the Batch Normalization layer class. The layer transforms
* the input data into zero mean and unit variance and then scales and shifts
* the data by parameters, gamma and beta respectively. These parameters are
* learnt by the network.
@@ -99,7 +99,7 @@ void BatchNorm<InputDataType, OutputDataType>::Backward(
const arma::mat inputMean = input.each_col() - mean;
const arma::mat stdInv = 1.0 / arma::sqrt(variance + eps);
// Step 1: dl / dxhat *
// Step 1: dl / dxhat
const arma::mat norm = gy.each_col() % gamma;
// Step 2: sum dl / dxhat * (x - mu) * -0.5 * stdInv^3.
@@ -113,8 +113,8 @@ void BatchNorm<InputDataType, OutputDataType>::Backward(
// Step 3: sum (dl / dxhat * -1 / stdInv) + variance *
// (sum -2 * (x - mu)) / m.
g.each_col() += arma::sum(norm.each_col() % -stdInv, 1) + var %
arma::mean(-2 * inputMean, 1) / input.n_cols;
g.each_col() += (arma::sum(norm.each_col() % -stdInv, 1) + (var %
arma::mean(-2 * inputMean, 1))) / input.n_cols;
}
template<typename InputDataType, typename OutputDataType>
+1
View File
@@ -19,6 +19,7 @@
#include "convolution.hpp"
#include "dropconnect.hpp"
#include "glimpse.hpp"
#include "layer_norm.hpp"
#include "layer_types.hpp"
#include "linear.hpp"
#include "linear_no_bias.hpp"
+200
View File
@@ -0,0 +1,200 @@
/**
* @file layer_norm.hpp
* @author Shikhar Jaiswal
*
* Definition of the Layer Normalization class.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_ANN_LAYER_LAYERNORM_HPP
#define MLPACK_METHODS_ANN_LAYER_LAYERNORM_HPP
#include <mlpack/prereqs.hpp>
namespace mlpack {
namespace ann /** Artificial Neural Network. */ {
/**
* Declaration of the Layer Normalization class. The layer transforms
* the input data into zero mean and unit variance and then scales and shifts
* the data by parameters, gamma and beta respectively over a single training
* data. These parameters are learnt by the network. Layer Normalization is
* different from Batch Normalization in the way that normalization is done
* for individual training cases, and the mean and standard deviations are
* computed across the layer dimensions, as opposed to across the batch.
*
* For more information, refer to the following papers,
*
* @code
* @article{Ba16,
* author = {Jimmy Lei Ba, Jamie Ryan Kiros and Geoffrey E. Hinton},
* title = {Layer Normalization},
* volume = {abs/1607.06450},
* year = {2016},
* url = {http://arxiv.org/abs/1607.06450},
* eprint = {1607.06450},
* }
* @endcode
*
* @code
* @article{Ioffe15,
* author = {Sergey Ioffe and
* Christian Szegedy},
* title = {Batch Normalization: Accelerating Deep Network Training by
* Reducing Internal Covariate Shift},
* journal = {CoRR},
* volume = {abs/1502.03167},
* year = {2015},
* url = {http://arxiv.org/abs/1502.03167},
* eprint = {1502.03167},
* }
* @endcode
*
* @tparam InputDataType Type of the input data (arma::colvec, arma::mat,
* arma::sp_mat or arma::cube).
* @tparam OutputDataType Type of the output data (arma::colvec, arma::mat,
* arma::sp_mat or arma::cube).
*/
template <
typename InputDataType = arma::mat,
typename OutputDataType = arma::mat
>
class LayerNorm
{
public:
//! Create the LayerNorm object.
LayerNorm();
/**
* Create the LayerNorm object for a specified number of input units.
*
* @param size The number of input units.
* @param eps The epsilon added to variance to ensure numerical stability.
*/
LayerNorm(const size_t size, const double eps = 1e-8);
/**
* Reset the layer parameters.
*/
void Reset();
/**
* Forward pass of Layer Normalization. Transforms the input data
* into zero mean and unit variance, scales the data by a factor gamma and
* shifts it by beta.
*
* @param input Input data for the layer.
* @param output Resulting output activations.
*/
template<typename eT>
void Forward(const arma::Mat<eT>&& input, arma::Mat<eT>&& output);
/**
* Backward pass through the layer.
*
* @param input The input activations.
* @param gy The backpropagated error.
* @param g The calculated gradient.
*/
template<typename eT>
void Backward(const arma::Mat<eT>&& input,
arma::Mat<eT>&& gy,
arma::Mat<eT>&& g);
/**
* Calculate the gradient using the output delta and the input activations.
*
* @param input The input activations.
* @param error The calculated error.
* @param gradient The calculated gradient.
*/
template<typename eT>
void Gradient(const arma::Mat<eT>&& input,
arma::Mat<eT>&& error,
arma::Mat<eT>&& gradient);
//! Get the parameters.
OutputDataType const& Parameters() const { return weights; }
//! Modify the parameters.
OutputDataType& Parameters() { return weights; }
//! Get the input parameter.
InputDataType const& InputParameter() const { return inputParameter; }
//! Modify the input parameter.
InputDataType& InputParameter() { return inputParameter; }
//! Get the output parameter.
OutputDataType const& OutputParameter() const { return outputParameter; }
//! Modify the output parameter.
OutputDataType& OutputParameter() { return outputParameter; }
//! Get the delta.
OutputDataType const& Delta() const { return delta; }
//! Modify the delta.
OutputDataType& Delta() { return delta; }
//! Get the gradient.
OutputDataType const& Gradient() const { return gradient; }
//! Modify the gradient.
OutputDataType& Gradient() { return gradient; }
//! Get the mean across single training data.
OutputDataType Mean() { return mean; }
//! Get the variance across single training data.
OutputDataType Variance() { return variance; }
/**
* Serialize the layer.
*/
template<typename Archive>
void serialize(Archive& ar, const unsigned int /* version */);
private:
//! Locally-stored number of input units.
size_t size;
//! Locally-stored epsilon value.
double eps;
//! Locally-stored scale parameter.
OutputDataType gamma;
//! Locally-stored shift parameter.
OutputDataType beta;
//! Locally-stored parameters.
OutputDataType weights;
//! Locally-stored mean object.
OutputDataType mean;
//! Locally-stored variance object.
OutputDataType variance;
//! Locally-stored gradient object.
OutputDataType gradient;
//! Locally-stored delta object.
OutputDataType delta;
//! Locally-stored input parameter object.
InputDataType inputParameter;
//! Locally-stored output parameter object.
OutputDataType outputParameter;
//! Locally-stored normalized input.
OutputDataType normalized;
}; // class LayerNorm
} // namespace ann
} // namespace mlpack
// Include the implementation.
#include "layer_norm_impl.hpp"
#endif
@@ -0,0 +1,124 @@
/**
* @file layer_norm_impl.hpp
* @author Shikhar Jaiswal
*
* Implementation of the Layer Normalization class.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_ANN_LAYER_LAYERNORM_IMPL_HPP
#define MLPACK_METHODS_ANN_LAYER_LAYERNORM_IMPL_HPP
// In case it is not included.
#include "layer_norm.hpp"
namespace mlpack {
namespace ann { /** Artificial Neural Network. */
template<typename InputDataType, typename OutputDataType>
LayerNorm<InputDataType, OutputDataType>::LayerNorm() :
size(10),
eps(1e-8)
{
// Nothing to do here.
}
template <typename InputDataType, typename OutputDataType>
LayerNorm<InputDataType, OutputDataType>::LayerNorm(
const size_t size, const double eps) :
size(size),
eps(eps)
{
weights.set_size(size + size, 1);
}
template<typename InputDataType, typename OutputDataType>
void LayerNorm<InputDataType, OutputDataType>::Reset()
{
gamma = arma::mat(weights.memptr(), 1, size, false, false);
beta = arma::mat(weights.memptr() + gamma.n_elem, 1, size, false, false);
gamma.fill(1.0);
beta.fill(0.0);
}
template<typename InputDataType, typename OutputDataType>
template<typename eT>
void LayerNorm<InputDataType, OutputDataType>::Forward(
const arma::Mat<eT>&& input, arma::Mat<eT>&& output)
{
mean = arma::mean(input, 0);
variance = arma::var(input, 1, 0);
// Normalize the input.
output = input.each_row() - mean;
output.each_row() /= arma::sqrt(variance + eps);
// Reused in the backward and gradient step.
normalized = output;
// Scale and shift the output.
output.each_row() %= gamma;
output.each_row() += beta;
}
template<typename InputDataType, typename OutputDataType>
template<typename eT>
void LayerNorm<InputDataType, OutputDataType>::Backward(
const arma::Mat<eT>&& input, arma::Mat<eT>&& gy, arma::Mat<eT>&& g)
{
const arma::mat inputMean = input.each_row() - mean;
const arma::mat stdInv = 1.0 / arma::sqrt(variance + eps);
// dl / dxhat
const arma::mat norm = gy.each_row() % gamma;
// sum dl / dxhat * (x - mu) * -0.5 * stdInv^3.
const arma::mat var = arma::sum(norm % inputMean, 0) %
arma::pow(stdInv, 3.0) * -0.5;
// dl / dxhat * 1 / stdInv + variance * 2 * (x - mu) / m +
// dl / dmu * 1 / m.
g = (norm.each_row() % stdInv) + (inputMean.each_row() %
var * 2 / input.n_rows);
// sum (dl / dxhat * -1 / stdInv) + variance *
// (sum -2 * (x - mu)) / m.
g.each_row() += (arma::sum(norm.each_row() % -stdInv, 0) + (var %
arma::mean(-2 * inputMean, 0))) / input.n_rows;
}
template<typename InputDataType, typename OutputDataType>
template<typename eT>
void LayerNorm<InputDataType, OutputDataType>::Gradient(
const arma::Mat<eT>&& /* input */,
arma::Mat<eT>&& error,
arma::Mat<eT>&& gradient)
{
gradient.set_size(size + size, 1);
// Step 5: dl / dy * xhat.
gradient.submat(0, 0, gamma.n_elem - 1, 0) = arma::reshape(arma::sum(
normalized % error, 0), normalized.n_cols, 1);
// Step 6: dl / dy.
gradient.submat(gamma.n_elem, 0, gradient.n_elem - 1, 0) =
arma::reshape(arma::sum(error, 0), error.n_cols, 1);
}
template<typename InputDataType, typename OutputDataType>
template<typename Archive>
void LayerNorm<InputDataType, OutputDataType>::serialize(
Archive& ar, const unsigned int /* version */)
{
ar & BOOST_SERIALIZATION_NVP(gamma);
ar & BOOST_SERIALIZATION_NVP(beta);
}
} // namespace ann
} // namespace mlpack
#endif
+3 -1
View File
@@ -25,6 +25,7 @@
#include <mlpack/methods/ann/layer/elu.hpp>
#include <mlpack/methods/ann/layer/hard_tanh.hpp>
#include <mlpack/methods/ann/layer/join.hpp>
#include <mlpack/methods/ann/layer/layer_norm.hpp>
#include <mlpack/methods/ann/layer/leaky_relu.hpp>
#include <mlpack/methods/ann/layer/flexible_relu.hpp>
#include <mlpack/methods/ann/layer/log_softmax.hpp>
@@ -45,10 +46,10 @@
namespace mlpack {
namespace ann {
template<typename InputDataType, typename OutputDataType> class BatchNorm;
template<typename InputDataType, typename OutputDataType> class DropConnect;
template<typename InputDataType, typename OutputDataType> class Glimpse;
template<typename InputDataType, typename OutputDataType> class LayerNorm;
template<typename InputDataType, typename OutputDataType> class Linear;
template<typename InputDataType, typename OutputDataType> class LinearNoBias;
template<typename InputDataType, typename OutputDataType> class LSTM;
@@ -158,6 +159,7 @@ using LayerTypes = boost::variant<
Glimpse<arma::mat, arma::mat>*,
HardTanH<arma::mat, arma::mat>*,
Join<arma::mat, arma::mat>*,
LayerNorm<arma::mat, arma::mat>*,
LeakyReLU<arma::mat, arma::mat>*,
Linear<arma::mat, arma::mat>*,
LinearNoBias<arma::mat, arma::mat>*,
+80 -1
View File
@@ -1396,7 +1396,7 @@ BOOST_AUTO_TEST_CASE(BatchNormTest)
/**
* BatchNorm layer numerically gradient test.
*/
BOOST_AUTO_TEST_CASE(GradientBatchNormLayerTest)
BOOST_AUTO_TEST_CASE(GradientBatchNormTest)
{
// Add function gradient instantiation.
struct GradientFunction
@@ -1719,4 +1719,83 @@ BOOST_AUTO_TEST_CASE(GradientAtrousConvolutionLayerTest)
BOOST_REQUIRE_LE(CheckGradient(function), 1e-3);
}
/**
* Tests the LayerNorm layer.
*/
BOOST_AUTO_TEST_CASE(LayerNormTest)
{
arma::mat input, output;
input << 5.1 << 3.5 << arma::endr
<< 4.9 << 3.0 << arma::endr
<< 4.7 << 3.2 << arma::endr;
LayerNorm<> model(input.n_cols);
model.Reset();
model.Forward(std::move(input), std::move(output));
arma::mat result;
result << 1.2247 << 1.2978 << arma::endr
<< 0 << -1.1355 << arma::endr
<< -1.2247 << -0.1622 << arma::endr;
CheckMatrices(output, result, 1e-1);
result.clear();
output = model.Mean();
result << 4.9000 << 3.2333 << arma::endr;
CheckMatrices(output, result, 1e-1);
result.clear();
output = model.Variance();
result << 0.0267 << 0.0422 << arma::endr;
CheckMatrices(output, result, 1e-1);
}
/**
* LayerNorm layer numerically gradient test.
*/
BOOST_AUTO_TEST_CASE(GradientLayerNormTest)
{
// Add function gradient instantiation.
struct GradientFunction
{
GradientFunction()
{
input = arma::randn(10, 256);
arma::mat target;
target.ones(1, 256);
model = new FFN<NegativeLogLikelihood<>, NguyenWidrowInitialization>();
model->Predictors() = input;
model->Responses() = target;
model->Add<IdentityLayer<> >();
model->Add<LayerNorm<> >(256);
model->Add<Linear<> >(10, 2);
model->Add<LogSoftMax<> >();
}
~GradientFunction()
{
delete model;
}
double Gradient(arma::mat& gradient) const
{
arma::mat output;
double error = model->Evaluate(model->Parameters(), 0, 256, false);
model->Gradient(model->Parameters(), 0, gradient, 256);
return error;
}
arma::mat& Parameters() { return model->Parameters(); }
FFN<NegativeLogLikelihood<>, NguyenWidrowInitialization>* model;
arma::mat input, target;
} function;
BOOST_REQUIRE_LE(CheckGradient(function), 1e-4);
}
BOOST_AUTO_TEST_SUITE_END();